Recherche…


Remarques

Cela semble être une chose simple à faire, mais lorsque vous commencez à grossir, vous serez reconnaissant d'avoir pris le temps de les organiser.

Classe de modèle

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

Les modèles sont généralement responsables de:

  • établir des relations
  • validation des données
  • fournir un accès aux données via des portées et des méthodes
  • Effectuer des actions sur la persistance des données.

Au plus haut niveau, les modèles décrivent les concepts de domaine et gèrent leur persistance.

Classe de service

Le contrôleur est un point d'entrée dans notre application. Cependant, ce n'est pas le seul point d'entrée possible. Je voudrais avoir ma logique accessible depuis:

  • Tâches de ratissage
  • emplois de fond
  • console
  • des tests

Si je lance ma logique dans un contrôleur, il ne sera pas accessible de tous ces endroits. Essayons donc l'approche «maigre contrôleur, gros modèle» et déplaçons la logique vers un modèle. Mais lequel? Si une logique donnée implique des modèles d' User , de Cart et de Product , où devrait-elle vivre?

Une classe qui hérite d' ActiveRecord::Base déjà beaucoup de responsabilités. Il gère l'interface de requête, les associations et les validations. Si vous ajoutez encore plus de code à votre modèle, il deviendra rapidement un gâchis intraitable avec des centaines de méthodes publiques.

Un service est simplement un objet Ruby normal. Sa classe n'a pas à hériter d'une classe spécifique. Son nom est une expression verbale, par exemple CreateUserAccount plutôt que UserCreation ou UserCreationService . Il vit dans le répertoire app / services. Vous devez créer ce répertoire par vous-même, mais Rails va automatiquement charger les classes à l'intérieur.

Un objet de service fait une chose

Un objet de service (objet de méthode aka) effectue une action. Il détient la logique métier pour effectuer cette action. Voici un exemple:

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

Les trois conventions que je suis sont:

Les services vont dans le app/services directory . Je vous encourage à utiliser des sous-répertoires pour les domaines à forte logique applicative. Par exemple:

  • Le fichier app/services/invite/accept.rb définira Invite::Accept lorsque app/services/invite/create.rb définira Invite::Create
  • Les services commencent par un verbe (et ne se terminent pas par Service): ApproveTransaction , SendTestNewsletter , ImportUsersFromCsv
  • Les services répondent à la méthode d' call . J'ai trouvé que l'utilisation d'un autre verbe le rend un peu redondant: ApproveTransaction.approve() ne lit pas bien. De plus, la méthode call est la méthode de facto pour lambda objets lambda , procs et method.

Avantages

Les objets de service montrent ce que fait mon application

Je peux simplement parcourir le répertoire des services pour voir ce que mon application fait: ApproveTransaction , CancelTransaction , BlockAccount , SendTransactionApprovalReminder

Un regard rapide sur un objet de service et je sais quelle logique métier est impliquée. Je n'ai pas besoin de passer par les contrôleurs, les rappels de modèles ActiveRecord et les observateurs pour comprendre ce que signifie «approuver une transaction».

Modèles et contrôleurs de nettoyage

Les contrôleurs transforment la requête (params, session, cookies) en arguments, les transmettent au service et les redirigent ou les rendent en fonction de la réponse du service.

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

Les modèles ne traitent que des associations, des étendues, des validations et de la persistance.

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

Cela rend les modèles et les contrôleurs beaucoup plus faciles à tester et à entretenir!

Quand utiliser la classe de service

Atteindre des objets de service lorsqu'une action répond à un ou plusieurs de ces critères:

  • L'action est complexe (par exemple, la fermeture des livres à la fin d'une période comptable)
  • L'action s'étend sur plusieurs modèles (par exemple, un achat en ligne via des objets Order, CreditCard et Customer).
  • L'action interagit avec un service externe (par exemple, publication sur les réseaux sociaux)
  • L'action n'est pas une préoccupation centrale du modèle sous-jacent (par exemple, balayer des données obsolètes après une certaine période).
  • Il y a plusieurs façons d'effectuer l'action (par exemple, authentifier avec un jeton d'accès ou un mot de passe).

Sources

Adam Niedzielski Blog

Blog de la brasserie

Code Climate Blog



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow