自定义挂钩/回调/宏方法

时间:2016-03-19 00:02:41

标签: ruby metaprogramming

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

当然不需要复制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

3 个答案:

答案 0 :(得分: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 :(得分:3)

以下是使用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:0x007fdb1301fa18@/tmp.rb:31>}

如果你想通过@SteveTurczyn工作来实现这个想法,你必须:

  1. define_method的块中接收args params,而不是作为参数。
  2. 如果您希望能够为其设置别名,请在定义方法后调用before_operations
  3. 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:0x007fdb1301fa18@/tmp.rb:31>}
    

答案 2 :(得分:1)

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

class ActiveClass
end

将除验证者以外的所有方法放在ActiveClass的子类中(名为)MidClass

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的{​​{1}}的子类中。

SubClass

类方法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。但是,如果使用super创建方法,则不会将任何参数(并且没有块)传递给super。在后一种情况下,参数必须是明确的。

我想将一个块或proc传递给define_method,如果有的话,但是一直使用错误的秘密酱。我会欢迎这样做的建议。