我对ActiveModel的序列化有很多乐趣,特别是as_json
和serializable_hash
的纠结网络。
我的应用有大量模型,通过包含模块来共享行为,我们称之为SharedBehavior
。
我的团队已经决定我们有一个默认格式,我们希望所有这些类在转换为JSON时都要遵循(用于在Rails应用程序中渲染),但是其中一些应该表现得有些不同。由于ActiveModel库中这两种方法的奇怪行为,在模型中添加列入白名单或列入黑名单的属性会被此模块中的方法定义覆盖,然后传递给ActiveModel中的超级声明。
出于这个原因,我希望这个模块只将这些方法的定义应用于模型,如果它们没有在那些模型中明确地重写(实质上,将模块从祖先链中取出用于一些方法)调用),但我仍然需要这个模块的共享行为。
我尝试通过有条件地解决这个问题,在IRB中动态应用模块包含的方法:
class A
def foo
puts 'in A'
end
end
module D
def self.included(base)
unless base.instance_methods(false).include?(:foo)
define_method(:foo) do
puts 'in D'
super()
end
end
end
end
class B < A
include D
end
class C < A
include D
def foo
puts 'in C'
super
end
end
通过此声明,我预计C.new.foo
的输出为
in C
in A
但它改为
in C
in D
in A
我唯一的另一个想法是将这个逻辑移到另一个模块中并在每个类中包含该模块(其中大约有54个)没有显式覆盖此方法,但是有一些缺点:
其他人是否可以想到另一个解决方案,或者在上面的代码示例中发现我的疏忽,这样我就可以在SharedBehavior
挂钩中进行调用? (我也尝试切换included
类定义C
方法并包含foo
模块的顺序,但看到了完全相同的行为。)
答案 0 :(得分:3)
这里有两个棘手的错误。
include D
中定义foo
之前C
,所以当调用included
挂钩时,foo
将不会被定义base
。您需要在班级的结束 include D
。foo
中定义了D
。因此,在D
中包含B
之后,D#foo
已定义,即使您修复了上一个错误,也意味着仍然包含在C
中。您需要base
成为define_method
。 但有一个有趣的转折:修复第二个错误使第一个错误无关紧要。通过直接在foo
中定义base
,它将被任何后续定义覆盖。这就像在做
class C < A
def foo
puts 'in D'
super()
end
# overwrites previous definition!
def foo
puts 'in C'
super
end
end
总而言之,你只需要
# in D.included
base.class_eval do
define_method(:foo) do
puts 'in D'
super()
end
end