自定义 Hook/Callback/Macro 方法

Custom Hook/Callback/Macro Methods

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

无需重复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

您可以将原始方法别名为不同的名称(因此 :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

这是一个使用 prepend 的解决方案。当您第一次调用 before_operations 时,它会创建一个新的(空)模块并将其添加到您的 class 之前。这意味着当您在 class 上调用方法 foo 时,它将首先在模块中查找该方法。

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

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 参数,而不是它的参数。
  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:0x007fdb1301fa18@/tmp.rb:31>}

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

class ActiveClass
end

将验证器以外的所有方法放在 ActiveClass 的子 class 中,命名为(比如)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]

将验证器与 class 方法 validate 一起放在 MidClass 的子 class 中,命名为(比如)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]

class 方法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。但是,如果该方法是使用 define_method 创建的,则没有参数(也没有块)传递给 super。在后一种情况下,参数必须是明确的。

我想将一个方块或过程传递给 super(如果有的话),但一直使用错误的秘方。我会欢迎这样做的建议。