2016-03-19 70 views
2

如何在子类中创建自定义挂钩方法?自定义挂钩/回调/宏方法

不需要复制Rails,当然 - 越简单越好。

我的目标是转换:

class SubClass 

    def do_this_method 
    first_validate_something 
    end 
    def do_that_method 
    first_validate_something 
    end 

    private 

    def first_validate_something; end 
end 

要:

class ActiveClass; end 

class SubClass < ActiveClass 
    before_operations :first_validate_something, :do_this_method, :do_that_method 

    def do_this_method; end 
    def do_that_method; end 

    private 

    def first_validate_something; end 
end 

实例模块:https://github.com/PragTob/after_do/blob/master/lib/after_do.rb

Rails的#before_action:http://apidock.com/rails/v4.0.2/AbstractController/Callbacks/ClassMethods/before_action

+0

您是否希望'first_validate_something'在任何*方法被调用之前调用(甚至是'to_s')?如果不是,它应该如何决定哪些方法“挂钩”? –

+0

first_validate只列出作为参数的方法之前调用,所以在这种情况下#do_this_method和#do_that方法 这是该方法的定义是什么样子,从SteveTurczyn偷:before_operations(before_method,*方法) 不那回答你的问题? – Trajanson

+0

是的。我的道歉,我在移动,并没有足够的滚动来看第二个和第三个参数! –

回答

2

这是一个使用prepend的解决方案。当您第一次致电before_operations时,它会创建一个新的(空的)模块并将其预先分配给您的班级。这意味着当你在你的类上调用方法foo时,它将首先在模块中查找该方法。

before_operations方法然后在此模块中定义简单的方法,首先调用您的'before'方法,然后使用super来调用您的类中的实际实现。

class ActiveClass 
    def self.before_operations(before_method,*methods) 
    prepend(@active_wrapper=Module.new) unless @active_wrapper 
    methods.each do |method_name| 
     @active_wrapper.send(:define_method,method_name) do |*args,&block| 
     send before_method 
     super(*args,&block) 
     end 
    end 
    end 
end 

class SubClass < ActiveClass 
    before_operations :first_validate_something, :do_this_method, :do_that_method 

    def do_this_method(*args,&block) 
    p doing:'this', with:args, and:block 
    end 
    def do_that_method; end 

    private 

    def first_validate_something 
    p :validating 
    end 
end 

SubClass.new.do_this_method(3,4){ |x| p x } 
#=> :validating 
#=> {:doing=>"this", :with=>[3, 4], :and=>#<Proc:[email protected]/tmp.rb:31>} 

如果您想通过@SteveTurczyn使思想工作,你必须:

  1. 收到的define_method块的ARGS参数,可以不作为参数传递给它。
  2. 致电before_operations在您的方法已被定义后,如果你想能够将它们别名。

 

class ActiveClass 
    def self.before_operations(before_method, *methods) 
    methods.each do |meth| 
     raise "No method `#{meth}` defined in #{self}" unless method_defined?(meth) 
     orig_method = "_original_#{meth}" 
     alias_method orig_method, meth 
     define_method(meth) do |*args,&block| 
     send before_method 
     send orig_method, *args, &block 
     end 
    end 
    end 
end 

class SubClass < ActiveClass 
    def do_this_method(*args,&block) 
    p doing:'this', with:args, and:block 
    end 
    def do_that_method; end 

    before_operations :first_validate_something, :do_this_method, :do_that_method 

    private  
    def first_validate_something 
     p :validating 
    end 
end 

SubClass.new.do_this_method(3,4){ |x| p x } 
#=> :validating 
#=> {:doing=>"this", :with=>[3, 4], :and=>#<Proc:[email protected]/tmp.rb:31>} 
+0

关于如何在列出的方法之上移动before_operations的任何想法? – Trajanson

+1

让'before_operations'维护一个方法列表,重写'method_added'来检查当前定义的方法是否在列表中。例如,这就是Rake的“desc”方法。你可以在我的这两个答案中看到一个例子:http://stackoverflow.com/a/2948977/2988 http://stackoverflow.com/a/3161693/2988(注意自我:更新使用'Module#prepend '!) –

+0

我已经编辑了我的答案,为此问题添加了一个新的基于前缀的解决方案。这允许在定义方法之前调用'before_operations',并且不使用任何别名废话。 – Phrogz

3

您可以别名原始方法以不同的名字(所以:do_this_something是谈到:original_do_this_something),然后定义一个新的:do_this_something方法调用:first_validate_something然后像这样的方法事情的原始版本...

class ActiveClass 
    def self.before_operations(before_method, *methods) 
    methods.each do |method| 
     alias_method "original_#{method.to_s}".to_sym, method 
     define_method(method, *args, &block) do 
     send before_method 
     send "original_#{method.to_s}", *args, &block 
     end 
    end 
    end 
end 
+1

这看起来确实是在正确的轨道上,但是当我将它添加到简化的示例代码中时,我得到一个错误,首先是:“block arg和给定的实际块” 然后我从define方法行删除&得到这个错误“未定义的方法'do_this_method'类为'Context :: SubClass'” 任何想法,出了什么问题? – Trajanson

+0

我已经添加了一个部分给我的答案,这个想法的启发,但修复它实际工作。 – Phrogz

1

这是编写的代码,不使用别名的方式。它包含一个类方法validate,它指定验证方法和要调用验证方法的方法。可以多次调用此方法validate以更改验证器并动态验证。

class ActiveClass 
end 

将所有的方法不是在命名(比如说)MidClassActiveClass一个子类的验证等。

class MidClass < ActiveClass 
    def do_this_method(v,a,b) 
    puts "this: v=#{v}, a=#{a}, b=#{b}" 
    end 

    def do_that_method(v,a,b) 
    puts "that: v=#{v}, a=#{a}, b=#{b}" 
    end 

    def yet_another_method(v,a,b) 
    puts "yet_another: v=#{v}, a=#{a}, b=#{b}" 
    end 
end 

MidClass.instance_methods(false) 
    #=> [:do_this_method, :do_that_method, :yet_another_method] 

广场的验证,有一类方法validate在一起,在一个名为(说)​​的MidClass一个子类。

class SubClass < MidClass 
    def self.validate(validator, *validatees) 
    superclass.instance_methods(false).each do |m| 
     if validatees.include?(m) 
     define_method(m) do |v, *args| 
      send(validator, v) 
      super(v, *args) 
     end 
     else 
     define_method(m) do |v, *args| 
      super(v, *args) 
     end 
     end 
    end 
    end 

    private 

    def validator1(v) 
    puts "valid1, v=#{v}" 
    end 

    def validator2(v) 
    puts "valid2, v=#{v}" 
    end 
end 

SubClass.methods(false) 
    #=> [:validate] 
SubClass.private_instance_methods(false) 
    #=> [:validator1, :validator2] 

类方法validate传递要使用的验证方法的符号以及要验证的方法。我们来试试吧。

sc = SubClass.new 

SubClass.validate(:validator1, :do_this_method, :do_that_method) 

sc.do_this_method(1,2,3) 
    # valid1, v=1 
    # this: v=1, a=2, b=3 
sc.do_that_method(1,2,3) 
    # valid1, v=1 
    # that: v=1, a=2, b=3 
sc.yet_another_method(1,2,3) 
    # yet_another: v=1, a=2, b=3 

现在更改验证。

SubClass.validate(:validator2, :do_that_method, :yet_another_method) 

sc.do_this_method(1,2,3) 
    # this: v=1, a=2, b=3 
sc.do_that_method(1,2,3) 
    # valid2, v=1 
    # that: v=1, a=2, b=3 
sc.yet_another_method(1,2,3) 
    # valid2, v=1 
    # yet_another: v=1, a=2, b=3 

super不加参数从通常的方法调用时,所有参数和一个块中,如果有一个,被传递到超级。但是,如果该方法是使用define_method创建的,则不会将参数(并且不包含任何块)传递给super。在后一种情况下,论据必须明确。

我想通过一个块或过程super如果有一个,但一直在使用错误的秘密酱油。我希望为此提供建议。

+0

这很酷,但确实需要你在超类中定义validees? – SteveTurczyn

+0

@SteveTurczyn,如果validees在'SubClass'中,我没有看到不使用别名来修改它们的方法。通过让它们在'MidClass'中,但是在'SubClass'的实例上调用它们,'SubClass'中的同名实例方法只需要包含一行调用验证器和'super'的行,这很容易用' define_method'。 –

+0

我看到你从哪里来,它似乎是满足要求的最简单方法。 – SteveTurczyn