2014-11-14 81 views
2

我有很多的乐趣加载ActiveModel用的序列化,具体的as_jsonserializable_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个)并没有明确地覆盖该方法,但是有一些缺点:

  1. 它引入了一些隐式耦合在项目的新模式包括该模块当且仅当它不希望重写此方法实现
  2. 当前实现的模块中,这些序列化方法与行为,并通过该模块建立的属性做的,所以我觉得像如果有第二个模块知道并取决于SharedBehavior的实现细节,虽然第二个模块与第一个模块几乎没有任何关系,但这将是不直观的。

其他人能否想到另一种解决方案,或者可能在上面的代码示例中发现我的疏忽,这将允许我在included挂钩中拨打电话? (我也尝试切换C类定义foo方法的顺序,并包含D模块,但看到完全相同的行为)。

回答

3

这里有两个棘手的错误。

  1. 红宝石评估类,所以表达式的顺序很重要。您include D之前定义fooC,所以当included挂钩被调用时,foo将不会被定义在base。您需要include D在该类的
  2. 您在D中定义foo。因此,在包含DB之后,即使您修复了上一个错误,也会定义D#foo,这意味着它仍然是包含在C中。您需要base作为define_method的接收方。

但是有一个有趣的转折:修复第二个bug使得第一个bug无关紧要。通过直接在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 
+0

这是伟大的。实际上,我在3分钟前得到了与此类似的东西,除了在我的解决方案中,我在'base'上调用了私有方法'define_method'。我试图避开'* _eval'方法,可能是由于我缺乏知识。我听说他们非常不安全,可能导致安全风险。你的例子在这方面有什么不同吗? – 2014-11-14 22:16:19

+1

如果你传递一个可能包含任意代码的字符串,''eval'方法只是不安全的。在这种情况下,它非常好,我只是用它将隐式接收器从'D'改为'base'。 – Max 2014-11-14 22:17:37

+0

甜。刚刚在IRB中验证过。这是一个非常有启发性的练习。谢谢! – 2014-11-14 22:20:49