Sök…


Anmärkningar

Detta verkar vara en enkel sak att göra, men när du klasser börjar ballong i storlek kommer du att vara tacksam att du tog dig tid att organisera dem.

Modellklass

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

Modeller är vanligtvis ansvariga för:

  • inrätta relationer
  • validera data
  • tillhandahålla åtkomst till data via omfattning och metoder
  • Utföra åtgärder kring persistens av data.

På högsta nivå beskriver modeller domänbegrepp och hanterar deras uthållighet.

Serviceklass

Controller är en startpunkt för vår applikation. Men det är inte den enda möjliga startpunkten. Jag skulle vilja ha min logik tillgänglig från:

  • Rake uppgifter
  • bakgrund jobb
  • trösta
  • tester

Om jag kastar min logik i en controller är det inte tillgängligt från alla dessa platser. Så låt oss försöka "skinny controller, fat model" och flytta logiken till en modell. Men vilken? Om en viss del av logik innebär User , Cart och Product modeller - där ska det bor?

En klass som ärver från ActiveRecord::Base redan mycket ansvar. Den hanterar frågegränssnitt, associeringar och valideringar. Om du lägger till ännu mer kod till din modell blir det snabbt en ohållbar röra med hundratals offentliga metoder.

En tjänst är bara ett vanligt Ruby-objekt. Dess klass behöver inte ärva från någon specifik klass. Dess namn är en verbfras, till exempel CreateUserAccount snarare än UserCreation eller UserCreationService . Den lever i app / tjänster-katalogen. Du måste skapa den här katalogen själv, men Rails autolader klasser inuti för dig.

Ett tjänsteobjekt gör en sak

Ett serviceobjekt (aka metodobjekt) utför en åtgärd. Det har affärslogiken för att utföra den åtgärden. Här är ett exempel:

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

De tre konventionerna jag följer är:

Tjänster går under app/services directory . Jag uppmuntrar dig att använda underkataloger för domäner med tunga affärslogik. Till exempel:

  • Filen app/services/invite/accept.rb kommer att definiera Invite::Accept medan app/services/invite/create.rb kommer att definiera Invite::Create
  • Tjänster börjar med ett verb (och slutar inte med Service): ApproveTransaction , SendTestNewsletter , ImportUsersFromCsv
  • Tjänster svarar på call . Jag fann att ett annat verb gör det lite överflödigt: ApproveTransaction.approve() läser inte bra. Dessutom är call de facto-metoden för lambda , procs och procs .

fördelar

Serviceobjekt visar vad min ansökan gör

Jag kan bara titta över servicekatalogen för att se vad min ansökan gör: ApproveTransaction CancelTransaction , BlockAccount ApproveTransaction , CancelTransaction , BlockAccount SendTransactionApprovalReminder ...

En snabb undersökning av ett serviceobjekt och jag vet vilken affärslogik det handlar om. Jag behöver inte gå igenom kontrollerna, ActiveRecord återuppringningar och observatörer för att förstå vad ”godkänna en transaktion” innebär.

Rengöringsmodeller och styrenheter

Controllers förvandlar begäran (params, session, cookies) till argumenter, skickar dem till tjänsten och omdirigerar eller gör enligt servicens svar.

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

Modeller handlar endast om föreningar, omfattningar, valideringar och uthållighet.

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

Detta gör modeller och styrenheter mycket lättare att testa och underhålla!

När du ska använda serviceklass

Nå för serviceobjekt när en åtgärd uppfyller ett eller flera av dessa kriterier:

  • Åtgärden är komplex (t.ex. stänga böckerna i slutet av en bokföringsperiod)
  • Åtgärden når över flera modeller (t.ex. ett köp av e-handel med order, kreditkort och kundobjekt)
  • Åtgärden interagerar med en extern tjänst (t.ex. publicering på sociala nätverk)
  • Åtgärden är inte en grundläggande oro för den underliggande modellen (t.ex. att svepa upp föråldrade data efter en viss tidsperiod).
  • Det finns flera sätt att utföra åtgärden (t.ex. autentisering med ett åtkomsttoken eller lösenord).

källor

Adam Niedzielski Blogg

Brew House Blog

Kod Klimatblogg



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow