Ruby on Rails
Klassorganisation
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 definieraInvite::Accept
medanapp/services/invite/create.rb
kommer att definieraInvite::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 ärcall
de facto-metoden förlambda
,procs
ochprocs
.
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