2016-03-19 67 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