Ruby on Rails
Klassenorganisation
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
definiertInvite::Accept
währendapp/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 dercall
ist Methode der de - facto - Methode fürlambda
,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