Ruby on Rails
Organizzazione di classe
Ricerca…
Osservazioni
Sembra una cosa semplice da fare, ma quando le lezioni inizieranno a gonfiarsi, sarai grato di aver trovato il tempo per organizzarle.
Classe di modello
class Post < ActiveRecord::Base
belongs_to :user
has_many :comments
validates :user, presence: true
validates :title, presence: true, length: { in: 6..40 }
scope :topic, -> (topic) { joins(:topics).where(topic: topic) }
before_save :update_slug
after_create :send_welcome_email
def publish!
update(published_at: Time.now, published: true)
end
def self.find_by_slug(slug)
find_by(slug: slug)
end
private
def update_slug
self.slug = title.join('-')
end
def send_welcome_email
WelcomeMailer.welcome(self).deliver_now
end
end
I modelli sono in genere responsabili di:
- instaurare relazioni
- dati di convalida
- fornendo accesso ai dati tramite ambiti e metodi
- Esecuzione di azioni sulla persistenza dei dati.
Al livello più alto, i modelli descrivono concetti di dominio e ne gestiscono la persistenza.
Classe di servizio
Il controller è un punto di accesso alla nostra applicazione. Tuttavia, non è l'unico punto di ingresso possibile. Mi piacerebbe avere la mia logica accessibile da:
- Rake tasks
- lavori in background
- consolle
- test
Se lancio la mia logica in un controller, non sarà accessibile da tutti questi posti. Quindi proviamo con l'approccio "skinny controller, fat model" e spostiamo la logica su un modello. Ma quale? Se un dato pezzo di logica coinvolge modelli User
, Cart
e Product
- dove dovrebbe vivere?
Una classe che eredita da ActiveRecord::Base
ha già molte responsabilità. Gestisce l'interfaccia di query, le associazioni e le convalide. Se aggiungi ancora più codice al tuo modello, diventerà rapidamente un disordine irraggiungibile con centinaia di metodi pubblici.
Un servizio è solo un normale oggetto Ruby. La sua classe non deve ereditare da una classe specifica. Il suo nome è una frase verbale, ad esempio CreateUserAccount
anziché UserCreation
o UserCreationService
. Vive nella directory app / servizi. Devi creare questa directory da solo, ma Rails caricherà automaticamente le lezioni per te.
Un oggetto di servizio fa una cosa
Un oggetto servizio (noto anche come oggetto metodo) esegue un'azione. Tiene la logica aziendale per eseguire quell'azione. Ecco un esempio:
# app/services/accept_invite.rb
class AcceptInvite
def self.call(invite, user)
invite.accept!(user)
UserMailer.invite_accepted(invite).deliver
end
end
Le tre convenzioni che seguo sono:
I servizi vanno nella app/services directory
. Ti incoraggio a utilizzare le sottodirectory per domini di logica aziendale-pesante. Per esempio:
- Il file
app/services/invite/accept.rb
definiràInvite::Accept
mentreapp/services/invite/create.rb
definiràInvite::Create
- I servizi iniziano con un verbo (e non terminano con il servizio):
ApproveTransaction
,SendTestNewsletter
,ImportUsersFromCsv
- I servizi rispondono al metodo di
call
. Ho trovato che usare un altro verbo lo rende un po 'ridondante:ApproveTransaction.approve()
non legge bene. Inoltre, il metodocall
è il metodo de facto perlambda
,procs
e gli oggetti metodo.
Benefici
Gli oggetti di servizio mostrano cosa fa la mia applicazione
Posso semplicemente dare un'occhiata alla directory dei servizi per vedere cosa fa la mia applicazione: ApproveTransaction
, CancelTransaction
, BlockAccount
, SendTransactionApprovalReminder
...
Un rapido sguardo su un oggetto di servizio e so quale logica aziendale è coinvolta. Non devo passare attraverso i controller, i callback e gli osservatori del modello ActiveRecord
per capire cosa implica "approvare una transazione".
Modelli e controller di pulizia
I controllori trasformano la richiesta (parametri, sessione, cookie) in argomenti, li inoltrano al servizio e reindirizzano o eseguono il rendering in base alla risposta del servizio.
class InviteController < ApplicationController
def accept
invite = Invite.find_by_token!(params[:token])
if AcceptInvite.call(invite, current_user)
redirect_to invite.item, notice: "Welcome!"
else
redirect_to '/', alert: "Oopsy!"
end
end
end
I modelli si occupano solo di associazioni, scopi, convalide e persistenza.
class Invite < ActiveRecord::Base
def accept!(user, time=Time.now)
update_attributes!(
accepted_by_user_id: user.id,
accepted_at: time
)
end
end
Questo rende i modelli e i controller molto più facili da testare e mantenere!
Quando utilizzare la classe di servizio
Raggiungi gli oggetti di servizio quando un'azione risponde a uno o più di questi criteri:
- L'azione è complessa (ad esempio chiudendo i libri alla fine di un periodo contabile)
- L'azione raggiunge più modelli (ad esempio un acquisto di e-commerce utilizzando oggetti Order, CreditCard e Customer)
- L'azione interagisce con un servizio esterno (ad es. Postare sui social network)
- L'azione non è una preoccupazione centrale del modello sottostante (ad es. Spazzare dati obsoleti dopo un certo periodo di tempo).
- Esistono diversi modi per eseguire l'azione (ad esempio l'autenticazione con un token di accesso o una password).
fonti