我有一种情况,我有基本模型,我想添加业务逻辑。例如,我可能会有这样的事情。
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方法。 Here's another example这个系统导致很难找到bug。
我正在考虑消除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
问题是我发现我必须复制所有关联以保证托管对象与其他托管对象相关联。
有更好,更少冗余的方式吗?
我真的不明白为什么你需要在子类中再次定义关联。但是,我有一个提示,你可以直接在你的Subscription
模型中使用。
如果您希望保持模型简单,并且不使用回调逻辑重载它,则可以创建一个callback class来包装模型将使用的所有逻辑。
为此,您需要创建一个类,例如:
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
订阅并应用所有自定义逻辑而无需使用服务。
UPDATE
具体来说,我不明白为什么你在三个模型上制作Single Table Inheritance只是为了给其中一个模型添加回调。您编写问题的方式,对于三个子类,您重写关联以使用创建的子类。这真的有必要吗?我认为不,因为你想要实现的只是重构你的服务作为回调,以便在destroy
模型中直接使用destroy_all
和Subscription
,我从这里开始:
但我发现它越来越尴尬了。例如,我无法使用自然的subscriber.subscriptions.destroy_all,但必须小心通过SubscriptionManager方法。
也许与conditional callbacks足够,甚至只是你的Subscription
模型上的正常回调。
我不知道真正的代码是如何编写的,但我发现使用单表继承只是为了添加回调很棘手。这并不能使您的模型“简单而灵活”。
更新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