Последовательно используя связанные подклассы модели - PullRequest
0 голосов
/ 21 сентября 2018

У меня есть ситуация, когда у меня есть базовые модели, к которым я хочу добавить бизнес-логику.Например, у меня может быть что-то вроде этого.

class List < ApplicationRecord
  has_many :subscriptions
  has_many :subscribers, though: :subscriptions
end

class Subscriber < ApplicationRecord
  has_many :subscriptions
  has_many :lists, through: :subscriptions
end

class Subscription < ApplicationRecord
  belongs_to :list
  belongs_to :subscriber
end

Подписаться и отписаться легко с помощью обычных методов ассоциации.

# Subscribe
list.subscriptions.create(
    subscriber: subscriber
)

# Unsubscribe
list.subscriptions.destroy(subscription)

# Unsub from all lists
subscriber.subscriptions.destroy_all

Но есть регистрация и отслеживание, а также метрики, хуки и другоебизнес логика.Я мог бы сделать это с помощью обратных вызовов.Однако я бы хотел сохранить основные модели простыми и гибкими.Я хочу отделить основную функциональность от дополнительной бизнес-логики.Прямо сейчас это должно упростить тестирование.В конце концов мне нужно будет добавить два различных наборов бизнес-логики поверх одного и того же ядра.

В настоящее время я использую сервисный объект, чтобы объединить общие действия со всей текущей бизнес-логикой.,Вот простой пример, есть намного больше.

class SubscriptionManager
  def subscribe(list, subscriber)
    list.subscriptions.create( subscriber: subscriber )
    log_sub(subscription)
  end

  def unsubscribe(subscription)
    subscription.list.subscriptions.destroy(subscription)
    log_unsub_reason(subscription)
  end

  def unsubscribe_all(subscriber)
    subscriber.subscriptions.each do |subscription|
      unsubscribe(subscription)
    end
    subscriber.lists.reset
    subscriber.subscriptions.reset
  end
end

Но я нахожу это все более неловким.Например, я не могу использовать натуральный subscriber.subscriptions.destroy_all, но должен быть осторожен, чтобы вместо этого использовать методы SubscriptionManager. Вот еще один пример , когда эта система вызвала ошибку при поиске.

Я подумываю об устранении SubscriptionManager и вместо этого писать подклассы моделей, которые имеют дополнительную логику в хуках.

class ManagedList < List
  has_many :subscriptions, class_name: "ManagedSubscription"
  has_many :subscribers, though: :subscriptions, class_name: "ManagedSubscriber"
end

class ManagedSubscriber < Subscriber
  has_many :subscriptions, class_name: "ManagedSubscription"
  has_many :lists, through: :subscriptions, class_Name: "ManagedList"
end

class ManagedSubscription < Subscription
  belongs_to :list, class_name: "ManagedList"
  belongs_to :subscriber, class_name: "ManagedSubscriber"

  after_create: :log_sub
  after_destroy: :log_unsub
end

Проблема в том, что я обнаружил, что мне нужно продублировать все ассоциации, чтобы гарантировать, что управляемые объекты связаны с другими управляемыми объектами.

Есть ли лучший и менее избыточный способ?

1 Ответ

0 голосов
/ 21 сентября 2018

Я не очень понимаю, зачем вам снова определять ассоциации в подклассах.Тем не менее, у меня есть совет, который вы можете использовать непосредственно в вашей Subscription модели.

Если вы хотите сохранить свою модель простой и не перегружать ее логикой обратных вызовов, вы можете создать Класс обратного вызова , чтобы обернуть всю логику, которая будет использоваться моделью.

Для этого вам нужно создать класс, например:

class SubscriptionCallbacks

  def self.after_create(subscription)
    log_sub(subscription)
  end

  def self.after_destroy(subscription)
    log_unsub_reason(subscription)
  end

end

Затемв Subscription модель:

class Subscription < ApplicationRecord
  belongs_to :list
  belongs_to :subscriber

  after_destroy SubscriptionCallbacks
  after_create SubscriptionCallbacks
end

Таким образом, ваша модель станет чистой, и вы сможете destroy подписаться и применять всю пользовательскую логику без использования службы.

ОБНОВЛЕНИЕ

В частности, я не понимаю, почему вы делаете наследование одной таблицы на трех моделях, чтобы добавить обратные вызовы для одной из них.То, как вы написали свой вопрос, для трех подклассов вы переопределяете ассоциации, чтобы использовать созданные подклассы.Это действительно необходимо?Я думаю, что нет, потому что то, что вы хотите достичь, - это просто рефакторинг вашего сервиса в качестве обратных вызовов, чтобы использовать destroy и destroy_all непосредственно в модели Subscription, я беру это отсюда:

Но я нахожу это все более неловким.Я не могу использовать естественный subscriber.subscription.destroy_all, например, но должен быть осторожен, чтобы пройти методы SubscriptionManager вместо этого.

Может быть, с условных обратных вызовов достаточно,или даже обычные обратные вызовы в вашей Subscription модели.

Я не знаю, как пишется реальный код, но я обнаружил, что сложно использовать Single Table Inheritance просто для добавления обратных вызовов.Это не делает ваши модели «простыми и гибкими».

ОБНОВЛЕНИЕ 2

В классе обратного вызова вы определяете методы с именем обратного вызова, который вы хотитереализовать и передать subscription в качестве параметра.Внутри этих методов вы можете создать всю логику, какую захотите.Например (при условии, что вы будете использовать другую логику с атрибутом type):

 class SubscriptionCallbacks

   def after_create(subscription)
     if subscription.type == 'foo'
       log_foo_sub(subscription)
     elsif subscription.type == 'bar'
       log_bar_sub(subscription)
     end
   end

   private

   def log_foo_sub(subscription)
     # Here will live all the logic of the callback for subscription of foo type
   end

   def log_bar_sub(subscription)
     # Here will live all the logic of the callback for subscription of bar type
   end

 end 

Это может быть много логики, которая не будет записана в модели Subscription.Вы можете использовать destroy и destroy_all как обычно, и если тип подписки не определен в if else, тогда ничего не произойдет.

Вся логика обратных вызовов будет заключена в callback class, и единственный код, который вы добавите к модели subscription, будет:

 class Subscription < ApplicationRecord
   belongs_to :list
   belongs_to :subscriber

   after_create SubscriptionCallbacks.new
 end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...