Accueil Blogue Conférences

Gérer les callbacks de modèle avec des abonnés

2017-03-27

Pour éviter de rendre le fichier de modèle trop long, déplacez les callbacks dans un abonné.

Scénario et problème

Solution

Exemple simple

Lors de la mise à jour du prix d’un produit, il est nécessaire de mettre à jour le prix des commandes associées.

class Product < ApplicationRecord
  has_many :orders

  after_update do
    if price_changed?
      orders.each do |order|
        order.update! price: price
      end
    end
  end
end

Après avoir utilisé un abonné

# ./app/subscribers/order_subscriber.rb
subscribe_model :product, :after_update do
  if price_changed?
    orders.each do |order|
      order.update! price: price
    end
  end
end

Utilisation des conventions

Avantages

Code principal

# ./config/initializers/subscribe_model.rb
def subscribe_model(model_name, event_name, &block)
  ActiveSupport::Notifications.subscribe("active_record.#{model_name}.#{event_name}") do |_name, _started, _finished, _unique_id, data|
    data[:model].instance_eval(&block)
  end
end

class ActiveRecord::Base
  class_attribute :skip_model_subscribers
  self.skip_model_subscribers = false
end

%i(after_create after_update after_destroy after_save after_commit after_create_commit after_update_commit).each do |name|
  ActiveRecord::Base.public_send(name) do
    unless skip_model_subscribers
      readonly! unless readonly?
      ActiveSupport::Notifications.instrument "active_record.#{self.class.model_name.singular}.#{name}", model: self
      ActiveSupport::Notifications.instrument "active_record.#{self.class.base_class.model_name.singular}.#{name}", model: self if self.class.base_class != self.class
      public_send(:instance_variable_set, :@readonly, false)
    end
  end
end

Rails.application.config.after_initialize do
  Dir[Rails.root.join('app/subscribers/*.rb')].each { |f| require f }
end

FAQ

Q: Pourquoi ne pas utiliser une préoccupation ?

R: Dans l’équipe, il est convenu que seuls les codes communs à deux modèles ou plus doivent être déplacés vers une préoccupation.

Q: Pourquoi ne pas créer une couche de service ?

R: La couche de service n’est pas assez intuitive, et pour les petites équipes, le coût de maintenance est plus élevé que celui d’un abonné.

Retour à tous les articles