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 mentre app/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 metodo call è il metodo de facto per lambda , 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

Adam Niedzielski Blog

Brew House Blog

Codice clima blog



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow