我对 ActiveModel 的序列化很感兴趣,特别是 as_json
和 serializable_hash
的纠结网络。
我的应用程序有大量模型,通过包含一个模块来共享行为,我们将其称为 SharedBehavior
。
我的团队决定我们有一个默认格式,我们希望所有这些类在转换为 JSON(用于在 Rails 应用程序中渲染)时遵循,但其中一些类的行为应该略有不同。由于 ActiveModel 库中这两个方法的奇怪行为,在模型本身中添加白名单或黑名单属性会被此模块中的方法定义覆盖,然后传递到 ActiveModel 中的 super 声明。
因此,我希望此模块仅将这些方法的定义应用于模型(如果这些方法没有在这些模型中显式重写)(实质上,将模块从祖先链中取出以进行一些方法调用) ,但我仍然需要此模块的共享行为。我尝试通过有条件地、动态地应用 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
Hook 中进行调用? (我还尝试切换 C
类定义 foo
方法并包含 D
模块的顺序,但看到了完全相同的行为)。
最佳答案
这里有两个棘手的错误。
- Ruby 评估类,因此表达式的顺序很重要。您
在
,因此当C
中定义foo
之前包含Dincluded
钩子(Hook)是调用时,foo
不会在base
中定义。您需要在类的末尾包含D
。 - 您正在
D
中定义foo
。因此,在B
中包含D
后,定义了D#foo
,这意味着它仍然包含在C 中
即使你修复了之前的错误。您需要base
作为define_method
的接收者。
但是有一个有趣的转折:修复第二个错误会使第一个错误变得无关紧要。通过直接在 base
中定义 foo
,它将被任何后续定义覆盖。这就像做
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
关于ruby - 仅当不包含类/模块时才允许模块定义方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26938732/