Ruby on Rails
Organizacja klasy
Szukaj…
Uwagi
Wydaje się to prostą rzeczą, ale kiedy zaczynasz zajęcia balonem, będziesz wdzięczny, że poświęciłeś czas na ich zorganizowanie.
Klasa modelu
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
Modele są zazwyczaj odpowiedzialne za:
- nawiązywanie relacji
- sprawdzanie poprawności danych
- zapewnianie dostępu do danych za pomocą zakresów i metod
- Wykonywanie działań związanych z trwałością danych.
Na najwyższym poziomie modele opisują koncepcje domen i zarządzają ich trwałością.
Klasa serwisowa
Kontroler jest punktem wejścia do naszej aplikacji. Nie jest to jednak jedyny możliwy punkt wejścia. Chciałbym mieć dostęp do mojej logiki z:
- Zadania prowizji
- zadania w tle
- konsola
- testy
Jeśli wrzucę moją logikę do kontrolera, nie będzie ona dostępna z tych wszystkich miejsc. Spróbujmy więc podejścia „chudy kontroler, model tłuszczu” i przenieś logikę do modelu. Ale który? Jeśli dana logika dotyczy modeli User
, Cart
i Product
- gdzie powinna ona istnieć?
Klasa, która dziedziczy z ActiveRecord::Base
ma już wiele obowiązków. Obsługuje interfejs zapytań, powiązania i sprawdzanie poprawności. Jeśli dodasz jeszcze więcej kodu do swojego modelu, szybko stanie się nieusuwalnym bałaganem dzięki setkom publicznych metod.
Usługa to zwykły obiekt Ruby. Jego klasa nie musi dziedziczyć od żadnej konkretnej klasy. Jego nazwa jest wyrażeniem czasownikowym, na przykład CreateUserAccount
a nie UserCreation
lub UserCreationService
. Żyje w katalogu aplikacji / usług. Musisz sam stworzyć ten katalog, ale Railsy automatycznie załadują dla ciebie klasy.
Obiekt usługi robi jedną rzecz
Obiekt usługi (inaczej obiekt metody) wykonuje jedną akcję. Utrzymuje logikę biznesową, aby wykonać tę akcję. Oto przykład:
# app/services/accept_invite.rb
class AcceptInvite
def self.call(invite, user)
invite.accept!(user)
UserMailer.invite_accepted(invite).deliver
end
end
Trzy konwencje, które stosuję, to:
Usługi znajdują się w katalogu app/services directory
. Zachęcam do korzystania z podkatalogów w domenach obciążonych logiką biznesową. Na przykład:
- Plik
app/services/invite/accept.rb
zdefiniujeInvite::Accept
aapp/services/invite/create.rb
zdefiniujeInvite::Create
- Usługi zaczynają się od czasownika (i nie kończą się na usłudze):
ApproveTransaction
,SendTestNewsletter
,ImportUsersFromCsv
- Usługi odpowiadają na metodę
call
. Odkryłem, że użycie innego czasownika sprawia, że jest to trochę zbędne:ApproveTransaction.approve()
nie czyta dobrze. Ponadto metodacall
jest de facto metodą dla obiektówlambda
,procs
i metod.
Korzyści
Obiekty usług pokazują, co robi moja aplikacja
Mogę tylko zerknąć na katalog usług, aby zobaczyć, co robi moja aplikacja: ApproveTransaction
, CancelTransaction
, BlockAccount
, SendTransactionApprovalReminder
…
Szybkie spojrzenie na obiekt usługi i wiem, na czym polega logika biznesowa. Nie muszę przechodzić przez kontrolery, wywołania zwrotne modelu ActiveRecord
i obserwatorów, aby zrozumieć, na czym polega „zatwierdzanie transakcji”.
Oczyszczanie modeli i sterowników
Kontrolery przekształcają żądanie (parametry, sesję, pliki cookie) w argumenty, przekazują je do usługi i przekierowują lub renderują zgodnie z odpowiedzią usługi.
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
Modele dotyczą tylko skojarzeń, zakresów, walidacji i trwałości.
class Invite < ActiveRecord::Base
def accept!(user, time=Time.now)
update_attributes!(
accepted_by_user_id: user.id,
accepted_at: time
)
end
end
To sprawia, że modele i sterowniki są znacznie łatwiejsze do testowania i konserwacji!
Kiedy stosować klasę usług
Sięgnij po obiekty usług, gdy akcja spełnia jedno lub więcej z następujących kryteriów:
- Działanie jest złożone (np. Zamknięcie ksiąg na koniec okresu obrachunkowego)
- Działanie dociera do wielu modeli (np. Zakupu e-commerce przy użyciu Zamówienia, Karty Kredytowej i obiektów Klienta)
- Działanie współdziała z usługą zewnętrzną (np. Publikowanie w sieciach społecznościowych)
- Działanie to nie jest głównym zmartwieniem modelu bazowego (np. Zamiatanie nieaktualnych danych po pewnym okresie czasu).
- Istnieje wiele sposobów wykonania akcji (np. Uwierzytelnianie za pomocą tokena dostępu lub hasła).
Źródła