始终使用关联的模型子类 [英] Consistently using associated model subclasses

查看:66
本文介绍了始终使用关联的模型子类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在某些情况下,我有一些要向其中添加业务逻辑的基本模型.例如,我可能有这样的东西.

I have a situation where I have basic models that I want to add business logic to. For example, I might have something like this.

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

通过常规关联方法,订阅和取消订阅都很容易.

Subscribing and unsubscribing is easy via the normal association methods.

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

# Unsubscribe
list.subscriptions.destroy(subscription)

# Unsub from all lists
subscriber.subscriptions.destroy_all

但是有日志记录,跟踪,指标,挂钩和其他业务逻辑.我可以通过回调来做到这一点.但是,我想保持基本模型的简单性和灵活性.我的愿望是将核心功能与额外的业务逻辑分开.现在,这是为了简化测试.最终,我需要在同一核心之上添加两组不同业务逻辑.

But there's logging and tracking and metrics and hooks and other business logic. I could do this with callbacks. However I'd like to keep the basic models simple and flexible. My desire is to separate the core functionality from the extra business logic. Right now this is to simplify testing. Eventually I'll need to add two different sets of business logic on top of the same core.

当前,我正在使用服务对象来包装具有所有当前业务逻辑的常见操作.这是一个简单的例子,还有很多.

Currently I'm using a service object to wrap common actions with all the current business logic. Here's a simple example, there's a lot more.

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方法. 这是另一个示例,该系统导致难以发现错误.

But I'm finding it increasingly awkward. I can't use the natural subscriber.subscriptions.destroy_all, for example, but must be careful to go through the SubscriptionManager methods instead. Here's another example where this system caused a hard to find bug.

我正在考虑消除SubscriptionManager,而是编写模型中具有附加逻辑的子类.

I'm thinking about eliminating the SubscriptionManager and instead writing subclasses of the models which have the extra logic in hooks.

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

问题是我发现我必须重复所有关联,以确保托管对象与其他托管对象相关联.

The problem is I'm finding I have to duplicate all the associations to guarantee that Managed objects are associated to other Managed objects.

有没有更好,更少冗余的方法?

Is there a better and less redundant way?

推荐答案

我不太了解为什么需要在子类中再次定义关联.但是,我有一个提示,您可以直接在您的Subscription模型中使用.

I don't really understand why do you need to define the associations again in the subclasses. However, I have a tip that you could use directly in your Subscription model.

如果您想保持模型简单,并且不使用回调逻辑重载模型,则可以创建回调类,用于包装模型将使用的所有逻辑.

If you want to keep your model simple, and don't overload it with callbacks logic, you can create a callback class to wrap all the logic that will be used by the model.

为此,您需要创建一个类,例如:

In order to do that, you need to create a class, for example:

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进行订阅并应用所有自定义逻辑,而无需使用服务.

That way, your model stand clean and you can destroy a subscription and apply all custom logic without using a service.

更新

具体来说,我不明白的是为什么要进行单表继承在三个模型上只是为了向其中之一添加回调.您编写问题的方式,对于这三个子类,您将覆盖关联以使用所创建的子类.那真的有必要吗?我认为不可以,因为您想要实现的只是将服务重构为回调,以便直接在Subscription模型中使用destroydestroy_all,因此请从这里开始:

Specifically, what I don't understand is why are you making Single Table Inheritance on three models just to add callbacks to one of them. The way you wrote your question, for the three subclasses you override the associations to use the subclasses created. Is that really necessary? I think that no, because what you want to achieve is just refactor your service as callbacks in order to use destroy and destroy_all directly in the Subscription model, I take that from here:

但是我发现它越来越尴尬.例如,我不能使用自然的subscriber.subscriptions.destroy_all,但必须小心使用SubscriptionManager方法.

But I'm finding it increasingly awkward. I can't use the natural subscriber.subscriptions.destroy_all, for example, but must be careful to go through the SubscriptionManager methods instead.

也许使用条件回调就足够了,甚至只是普通的回调您的Subscription模型.

Maybe with conditional callbacks is enough, or even just normal callbacks on your Subscription model.

我不知道真正的代码是如何编写的,但是我发现使用单表继承"来添加回调很棘手.那不会使您的模型简单而灵活".

I don't know how the real code is wrote, but I found tricky to use Single Table Inheritance just to add callbacks. That doesn't make your models "simple and flexible".

更新2

在回调类中,使用要实现的回调的名称定义方法,并将subscription作为参数传递.在这些方法中,您可以创建所需的所有逻辑.例如(假设给定type属性,您将使用不同的逻辑):

In a callback class, you define methods with the name of the callback that you want to implement, and pass the subscription as a parameter. Inside that methods, you can create all the logic that you want. For example (assuming that you will use different logic given a type attribute):

 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模型上编写.您可以照常使用destroydestroy_all,如果if else中未定义订阅类型,则不会发生任何事情.

This could be a lot of logic that will not be wrote on Subscription model. You can use destroy and destroy_all as usual, and if a type of subscription is not defined in the if else, then nothing will happen.

所有回调逻辑都将包裹在callback class中,并且您将添加到subscription模型中的唯一代码是:

All the logic of callbacks will be wrapped in a callback class, and the only peace of code that you will add to the subscription model will be:

 class Subscription < ApplicationRecord
   belongs_to :list
   belongs_to :subscriber

   after_create SubscriptionCallbacks.new
 end

这篇关于始终使用关联的模型子类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆