【发布时间】:2014-07-25 18:32:00
【问题描述】:
我有一个模型依赖于一个单独的连接模型。
class Magazine < ActiveRecord::Base
has_one :cover_image, dependent: :destroy, as: :imageable
end
class Image < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
end
图像是多态的,可以附加到许多对象(页面和文章)上,而不仅仅是杂志。
杂志需要在其相关图像发生任何变化时自行更新
杂志还保存了自己的截图,可以用来宣传:
class Magazine < ActiveRecord::Base
has_one :cover_image, dependent: :destroy, as: :imageable
has_one :screenshot
def generate_screenshot
# go and create a screenshot of the magazine
end
end
现在如果图片发生变化,杂志也需要更新截图。所以杂志真的需要知道图像什么时候发生了什么事。
所以我们可以直接从封面图片天真地触发屏幕截图更新
class Image < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
after_save { update_any_associated_magazine }
def update_any_associated_magazine
# figure out if this belongs to a magazine and trigger
# screenshot to regenerate
end
end
...但是图片不应该代表杂志做事
但是,该图像可以用于许多不同的对象,并且确实不应该执行特定于杂志的操作,因为这不是图像的责任担心。图片也可以附加到页面或文章中,不需要为它们做各种事情。
“正常”的 rails 方法是使用观察者
如果我们采用 Rails(y) 方法,那么我们可以创建一个第三方观察者,然后触发相关杂志上的事件:
class ImageObserver < ActiveRecord::Observer
observe :image
def after_save image
Magazine.update_magazine_if_includes_image image
end
end
但是,这对我来说感觉有点糟糕。
我们通过更新杂志避免了图像的负担,这很棒,但我们实际上只是将问题推到了下游。这个观察者的存在并不明显,在 Magazine 对象内部并不清楚对 Image 的更新实际上会触发对 Magazine 的更新,而且我们有一个奇怪的浮动对象,它的逻辑实际上只属于 Magazine。
我不想要观察者——我只想要一个对象能够订阅另一个对象上的事件。
有没有办法直接从另一个模型订阅一个模型的更改?
我更愿意让杂志直接订阅图片上的事件。所以代码看起来像:
class Magazine < ActiveRecord::Base
...
Image.add_after_save_listener Magazine, :handle_image_after_save
def self.handle_image_after_save image
# determine if image belongs to magazine and if so update it
end
end
class Image < ActiveRecord::Base
...
def self.add_after_save_listener class_name, method
@@after_save_listeners << [class_name, method]
end
after_save :notify_after_save_listeners
def notify_after_save_listeners
@@after_save_listeners.map{ |listener|
class_name = listener[0]
listener_method = listener[1]
class_name.send listener_method
}
end
这是一种有效的方法吗?如果不是,为什么不呢?
这种模式对我来说似乎是明智的。它使用类变量和方法,因此不对特定实例可用做任何假设。
但是,我现在已经足够大,也足够聪明了,我知道如果在 Rails 中还没有完成一些看似显而易见的事情,那可能是有充分理由的。
这对我来说似乎很酷。但它有什么问题?为什么我看到的所有其他解决方案都在第三方反对处理事情?这行得通吗?
【问题讨论】:
-
我同意观察者和回调...有一个有趣的 gem,叫做 wisper,它做类似的事情。在这里查看:github.com/krisleech/wisper
-
我之前在 Laravel 中实现了你所说的模式,它非常容易创建并且总体上运行良好。我能想到的唯一缺点是 1)需要有一个绕过机制 2)调试,很难追溯什么时候被调用,而且很容易导致意想不到的后果,但可以通过良好的工具来弥补我想。
标签: ruby-on-rails ruby publish-subscribe