Ruby on Rails
Klasse Organisatie
Zoeken…
Opmerkingen
Dit lijkt een eenvoudig iets om te doen, maar als je lessen in grootte beginnen te ballonvaren, ben je dankbaar dat je de tijd hebt genomen om ze te organiseren.
Model Klasse
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
Modellen zijn meestal verantwoordelijk voor:
- relaties opzetten
- gegevens valideren
- toegang bieden tot gegevens via scopes en methoden
- Acties uitvoeren rond persistentie van gegevens.
Op het hoogste niveau beschrijven modellen domeinconcepten en beheren ze hun doorzettingsvermogen.
Serviceklasse
Controller is een toegangspunt tot onze applicatie. Het is echter niet het enige mogelijke toegangspunt. Ik wil graag dat mijn logica toegankelijk is vanaf:
- Hark taken
- achtergrond banen
- troosten
- testen
Als ik mijn logica in een controller gooi, is deze niet vanaf al deze plaatsen toegankelijk. Dus laten we de "skinny controller, fat model" -benadering proberen en de logica naar een model verplaatsen. Maar welke? Als een bepaald stuk logica betrekking heeft op User
, Cart
en Product
- waar moet die dan leven?
Een klasse die overneemt van ActiveRecord::Base
al veel verantwoordelijkheden. Het behandelt query-interface, associaties en validaties. Als u nog meer code aan uw model toevoegt, wordt het snel een onbereikbare puinhoop met honderden openbare methoden.
Een service is gewoon een gewoon Ruby-object. De klasse hoeft niet te erven van een specifieke klasse. De naam is een werkwoordgroep, bijvoorbeeld CreateUserAccount
plaats van UserCreation
of UserCreationService
. Het leeft in de app / services-directory. U moet deze map zelf maken, maar Rails zal automatisch klassen voor u inladen.
Een serviceobject doet één ding
Een serviceobject (ook wel methode-object genoemd) voert één actie uit. Het bevat de bedrijfslogica om die actie uit te voeren. Hier is een voorbeeld:
# app/services/accept_invite.rb
class AcceptInvite
def self.call(invite, user)
invite.accept!(user)
UserMailer.invite_accepted(invite).deliver
end
end
De drie conventies die ik volg zijn:
Services gaan onder de app/services directory
. Ik moedig u aan om submappen te gebruiken voor bedrijfslogica-zware domeinen. Bijvoorbeeld:
- Het bestand
app/services/invite/accept.rb
definieertInvite::Accept
terwijlapp/services/invite/create.rb
Invite::Create
definieert - Services beginnen met een werkwoord (en eindigen niet met Service):
ApproveTransaction
,SendTestNewsletter
,ImportUsersFromCsv
- Services reageren op de
call
. Ik ontdekte dat het gebruik van een ander werkwoord het een beetje overbodig maakt:ApproveTransaction.approve()
leest niet goed. Ook is decall
methode is de de facto methode voorlambda
,procs
en methode objecten.
Voordelen
Serviceobjecten laten zien wat mijn applicatie doet
Ik kan gewoon door de services-directory ApproveTransaction
om te zien wat mijn applicatie doet: ApproveTransaction
, CancelTransaction
, BlockAccount
, SendTransactionApprovalReminder
...
Een snel overzicht van een serviceobject en ik weet wat de bedrijfslogica is. Ik hoef niet door de controllers, ActiveRecord
model callbacks en waarnemers te gaan om te begrijpen wat "het goedkeuren van een transactie" inhoudt.
Opruimmodellen en controllers
Controllers zetten de aanvraag (params, sessie, cookies) om in argumenten, geven deze door aan de service en sturen deze door of geven deze weer volgens de service-respons.
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
Modellen behandelen alleen associaties, scopes, validaties en persistentie.
class Invite < ActiveRecord::Base
def accept!(user, time=Time.now)
update_attributes!(
accepted_by_user_id: user.id,
accepted_at: time
)
end
end
Dit maakt modellen en controllers veel eenvoudiger te testen en te onderhouden!
Wanneer Serviceklasse te gebruiken
Bereik voor serviceobjecten wanneer een actie aan een of meer van deze criteria voldoet:
- De actie is complex (bijv. Het afsluiten van de boeken aan het einde van een boekhoudperiode)
- De actie reikt over meerdere modellen (bijv. Een e-commerce-aankoop met behulp van objecten Bestelling, CreditCard en Klant)
- De actie werkt samen met een externe service (bijvoorbeeld posten op sociale netwerken)
- De actie is geen kernprobleem van het onderliggende model (bijv. Verouderde gegevens na een bepaalde periode opvegen).
- Er zijn meerdere manieren om de actie uit te voeren (bijvoorbeeld authenticeren met een toegangstoken of wachtwoord).
bronnen