始终使用关联的模型子类 [英] Consistently using associated model subclasses
问题描述
在某些情况下,我有一些要向其中添加业务逻辑的基本模型.例如,我可能有这样的东西.
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
模型中使用destroy
和destroy_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
模型上编写.您可以照常使用destroy
和destroy_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屋!