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 zdefiniuje Invite::Accept a app/services/invite/create.rb zdefiniuje Invite::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 metoda call jest de facto metodą dla obiektów lambda , 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

Blog Adama Niedzielskiego

Blog Brew House

Blog Code Climate



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow