Ruby mixins和调用超级方法
好的,所以我一直在我的小Rails应用程序中重构代码,以消除重复,并总体上使我的生活更轻松(因为我喜欢简单的生活)。重构的一部分就是将我两个模型所共有的代码移动到一个模块,该模块可以包含在需要的地方。
到目前为止,一切都很好。看起来它将解决,但是我遇到了一个不确定如何解决的问题。该模块(我称为sendable)将成为处理传真,电子邮件或者打印文档PDF的代码。例如,我有一个采购订单,而我有一个内部销售订单(想象中缩写为ISO)。
我遇到的问题是,我希望在加载对象之后初始化一些变量(为那些拼写不正确的人初始化:P),所以我一直在使用after_initialize挂钩。没问题...直到我开始添加更多的mixin。
我的问题是,我的任何一个mixin中都可以有一个after_initialize
,因此我需要在开始时包括一个超级调用,以确保其他mixinafter_initialize
调用被调用。太好了,直到我最终调用super并收到错误消息为止,因为没有super可以调用。
这是一个小例子,以防万一我还不够困惑:
class Iso < ActiveRecord::Base include Shared::TracksSerialNumberExtension include Shared::OrderLines extend Shared::Filtered include Sendable::Model validates_presence_of :customer validates_associated :lines owned_by :customer order_lines :despatched # Mixin tracks_serial_numbers :items # Mixin sendable :customer # Mixin attr_accessor :address def initialize( params = nil ) super self.created_at ||= Time.now.to_date end end
因此,如果每个混入都具有一个after_initialize调用以及一个超级调用,那么如何停止最后一个超级调用引发错误呢?在调用超级方法之前,如何测试超级方法是否存在?
解决方案
回答
我们可以只定义它而不是检查super方法是否存在。
class ActiveRecord::Base def after_initialize end end
这在我的测试中有效,并且不应该破坏我们现有的任何代码,因为所有其他定义该代码的类都将以静默方式重写此方法。
回答
我们是否尝试过" alias_method_chain"?我们基本上可以将所有的after_initialize调用链接起来。它就像一个装饰器:每个新方法都添加了新的功能层,并将控件传递给"覆盖"方法以完成其余工作。
回答
包含类(从" ActiveRecord :: Base"继承的东西,在这种情况下为" Iso")可以定义自己的" after_initialize",因此除了" alias_method_chain"(或者其他保存原始别名的别名)之外的任何解决方案)可能会覆盖代码。 @Orion Edwards的解决方案是我能想到的最好的解决方案。还有其他人,但是他们要强硬得多。
Alias_method_chain`还具有创建after_initialize方法的命名版本的优点,这意味着我们可以在重要的罕见情况下自定义呼叫顺序。否则,我们将受到包含类包含混合对象的任何命令的支配。
之后:
我已经在ruby-on-rails-core邮件列表中发布了一个有关创建所有回调的默认空实现的问题。无论如何,保存过程都会检查它们,因此我不明白为什么它们不应该存在。唯一的缺点是创建额外的空堆栈框架,但是在每个已知的实现中这都是很便宜的。
回答
我们可以在其中快速添加条件:
super if respond_to?('super')
我们应该没有添加无用的方法就可以了;干净整洁。
回答
我们可以使用此:
super if defined?(super)
这是一个例子:
class A end class B < A def t super if defined?(super) puts "Hi from B" end end B.new.t