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 definieert Invite::Accept terwijl app/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 de call methode is de de facto methode voor lambda , 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

Adam Niedzielski Blog

Brew House Blog

Code Klimaatblog



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow