Suche…


Bemerkungen

Dies scheint eine einfache Sache zu sein, aber wenn der Unterricht anfängt, in der Größe zu steigen, sind Sie dankbar, dass Sie sich die Zeit genommen haben, um sie zu organisieren.

Modellklasse

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

Modelle sind typischerweise verantwortlich für:

  • Beziehungen aufbauen
  • Daten validieren
  • Bereitstellung des Zugriffs auf Daten über Geltungsbereiche und Methoden
  • Durchführen von Aktionen zur Persistenz von Daten.

Auf höchster Ebene beschreiben Modelle Domänenkonzepte und verwalten deren Persistenz.

Serviceklasse

Controller ist ein Einstiegspunkt in unsere Anwendung. Es ist jedoch nicht der einzige mögliche Einstiegspunkt. Ich hätte gerne meine Logik von:

  • Rechenaufgaben
  • Hintergrundjobs
  • Konsole
  • Tests

Wenn ich meine Logik in eine Steuerung stecke, ist sie von all diesen Stellen nicht zugänglich. Versuchen wir also, den Ansatz „Skinny Controller, Fat Model“ auszuprobieren und die Logik auf ein Modell zu übertragen. Aber welcher? Wenn eine bestimmte Logik ein User , Cart und Product - wo sollte sie leben?

Eine Klasse, die von ActiveRecord::Base erbt, hat bereits viele Verantwortlichkeiten. Es behandelt die Abfrage-Schnittstelle, Zuordnungen und Validierungen. Wenn Sie Ihrem Modell noch mehr Code hinzufügen, wird es mit Hunderten öffentlicher Methoden schnell zu einem unauffälligen Durcheinander.

Ein Service ist nur ein reguläres Ruby-Objekt. Ihre Klasse muss nicht von einer bestimmten Klasse erben. Sein Name ist eine Verbphrase, z. B. CreateUserAccount anstelle von UserCreation oder UserCreationService . Es lebt im App / Services-Verzeichnis. Sie müssen dieses Verzeichnis selbst erstellen, Rails lädt jedoch automatisch Klassen für Sie.

Ein Serviceobjekt macht eine Sache

Ein Serviceobjekt (alias Methodenobjekt) führt eine Aktion aus. Es enthält die Geschäftslogik, um diese Aktion auszuführen. Hier ist ein Beispiel:

# app/services/accept_invite.rb
class AcceptInvite
  def self.call(invite, user)
    invite.accept!(user)
    UserMailer.invite_accepted(invite).deliver
  end
end

Die drei Konventionen, die ich befolge, sind:

Services gehen in das app/services directory . Ich empfehle Ihnen, Unterverzeichnisse für geschäftslogikreiche Domains zu verwenden. Zum Beispiel:

  • Die Datei app/services/invite/accept.rb definiert Invite::Accept während app/services/invite/create.rb Invite::Create definiert
  • Services beginnen mit einem Verb (und enden nicht mit Service): ApproveTransaction , SendTestNewsletter , ImportUsersFromCsv
  • Dienste reagieren auf die call . Ich fand es mit einem anderen Verb etwas überflüssig: ApproveTransaction.approve() liest nicht gut. Auch der call ist Methode der de - facto - Methode für lambda , procs und Methodenobjekte.

Leistungen

Serviceobjekte zeigen, was meine Anwendung macht

Ich kann nur einen Blick auf das ApproveTransaction CancelTransaction , BlockAccount zu sehen, was meine Anwendung bewirkt: ApproveTransaction , CancelTransaction , BlockAccount , SendTransactionApprovalReminder ...

Ein kurzer Blick auf ein Serviceobjekt und ich weiß, welche Geschäftslogik involviert ist. Ich muss nicht die Controller, ActiveRecord Modellrückrufe und Beobachter durchgehen, um zu verstehen, was das "Genehmigen einer Transaktion" bedeutet.

Aufräummodelle und Controller

Controller wandeln die Anfrage (Parameter, Sitzung, Cookies) in Argumente um, leiten sie an den Dienst weiter und leiten sie entsprechend der Antwort des Dienstes um oder rendern sie.

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

Modelle behandeln nur Assoziationen, Gültigkeitsbereiche, Validierungen und Persistenz.

class Invite < ActiveRecord::Base
  def accept!(user, time=Time.now)
    update_attributes!(
      accepted_by_user_id: user.id,
      accepted_at: time
    )
  end
end

Dies macht Modelle und Controller viel einfacher zu testen und zu warten!

Wann wird die Serviceklasse verwendet?

Reichweite für Serviceobjekte, wenn eine Aktion eines oder mehrere der folgenden Kriterien erfüllt:

  • Die Aktion ist komplex (zB das Schließen der Bücher am Ende einer Abrechnungsperiode)
  • Die Aktion erstreckt sich über mehrere Modelle (z. B. einen E-Commerce-Kauf mit Aufträgen, Kreditkarten und Kundenobjekten).
  • Die Aktion interagiert mit einem externen Dienst (z. B. Veröffentlichung in sozialen Netzwerken).
  • Die Aktion ist kein zentrales Anliegen des zugrunde liegenden Modells (z. B. das Aufholen veralteter Daten nach einem bestimmten Zeitraum).
  • Es gibt mehrere Möglichkeiten, die Aktion auszuführen (z. B. Authentifizierung mit einem Zugriffstoken oder Kennwort).

Quellen

Adam Niedzielski Blog

Brew House-Blog

Code Climate Blog



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow