2016-03-19 15 views
2

Wie erstelle ich eine benutzerdefinierte Hook-Methode in einer Subklasse?Benutzerdefinierte Hook/Callback/Makro-Methoden

Keine Notwendigkeit, Rails zu duplizieren, natürlich - je einfacher, desto besser.

Mein Ziel ist es zu konvertieren:

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 

An:

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 

Beispiel in Modul: 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

Möchten Sie 'first_validate_something' aufgerufen haben, bevor * any * Methode aufgerufen wird (auch, sagen wir, to_s')? Wenn nicht, wie soll es entscheiden, welche Methoden "haken"? –

+0

first_validate ist nur vor den Methoden als Argumente aufgeführt, so genannt, in diesem Fall #do_this_method und #do_that Methode Dies ist, was die Methodendefinition wie von SteveTurczyn zu stehlen aussehen: before_operations (before_method, * Methoden) Does Das beantwortet deine Frage? – Trajanson

+0

Ja, tut es. Ich entschuldige mich, ich bin mobil und scrollte nicht weit genug, um das zweite und dritte Argument zu sehen! –

Antwort

2

Hier ist eine Lösung, die prepend verwendet. Wenn Sie zum ersten Mal before_operations aufrufen, erstellt es ein neues (leeres) Modul und fügt es Ihrer Klasse hinzu. Dies bedeutet, dass beim Aufruf der Methode foo für Ihre Klasse zuerst nach dieser Methode im Modul gesucht wird. Die Methode before_operations definiert dann einfache Methoden in diesem Modul, die zuerst Ihre 'before'-Methode aufrufen und dann super verwenden, um die echte Implementierung in Ihrer Klasse aufzurufen.

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>} 

Wenn Sie die Idee von @SteveTurczyn machen wollen arbeiten müssen Sie:

  1. erhalten die args params im Block der define_method, nicht als Argumente zu.
  2. Anruf before_operations NACH Ihre Methoden wurden definiert, wenn Sie in der Lage sein möchten, sie zu aliasieren.

 

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

irgendwelche Gedanken darüber, wie man before_operations über die aufgelisteten Methoden bewegt? – Trajanson

+1

Haben 'before_operations' eine Liste von Methoden, überschreiben Sie' method_added', um zu prüfen, ob die aktuell definierte Methode in dieser Liste enthalten ist. So funktioniert zum Beispiel Rake's 'desc' Methode. Sie können ein Beispiel in diesen zwei Antworten von mir sehen: http://stackoverflow.com/a/2948977/2988 http://stackoverflow.com/a/3161693/2988 (Hinweis zu Selbst: Aktualisierung zu verwenden 'Modul # prepend '!) –

+0

Ich habe meine Antwort bearbeitet, um dem Problem eine neue, vorläufige Lösung hinzuzufügen. Dies ermöglicht das Aufrufen von 'before_operations' vor dem Definieren der Methoden und auch keinen Aliasing-Nonsense. – Phrogz

3

Sie können die ursprüngliche Methode alias zu einem anderen Namen (so :do_this_something sein kommt :original_do_this_something) und dann eine neue :do_this_something Methode definieren, :first_validate_something und dann die ursprüngliche Version des Verfahrens So etwas nennt ...

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

Sieht aus wie es ist definitiv auf der richtigen Spur, aber wenn ich es zu vereinfachten Beispielcode hinzufügen, erhalte ich einen Fehler, zuerst dies: "Block Arg und tatsächlichen Block gegeben" dann lösche ich & Blockieren von der Definition der Linie und bekomme diesen Fehler "undefined Methode' do_this_method 'für die Klasse' Context :: SubClass '" irgendwelche Gedanken, was schief gelaufen ist? – Trajanson

+0

Ich habe meiner Antwort einen Teil hinzugefügt, der von dieser Idee inspiriert ist, aber es repariert, um tatsächlich zu funktionieren. – Phrogz

1

Dies ist ein Weg, um den Code zu schreiben, die nicht Gebrauch von Alias-Namen machen. Es enthält eine Klassenmethode validate, die die Validator-Methode und die Methoden angibt, die die Validator-Methode aufrufen sollen. Diese Methode validate kann mehrmals aufgerufen werden, um den Validator zu ändern und dynamisch zu validieren.

class ActiveClass 
end 

Legen Sie alle andere Methoden als die Prüfungen in einer Unterklasse von ActiveClass genannt (sagen wir) 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] 

Legen Sie die Validierer, zusammen mit einer Klassenmethode validate, in einer Unterklasse von MidClass genannt (sagen wir) 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] 

die Klassenmethode validate Symbole für die Validierungsmethode gelangt zu verwenden und das Verfahren zu validieren. Lass es uns versuchen.

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 

Jetzt die Validierung ändern.

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 

Wenn super ohne Argumente aus einer normalen Methode aufgerufen wird, alle Argumente und ein Block, wenn es einen gibt, sind super geführt. Wenn die Methode mit define_method erstellt wurde, werden jedoch keine Argumente (und kein Block) an super übergeben. Im letzteren Fall müssen die Argumente explizit sein.

Ich wollte einen Block oder Proc auf super übergeben, wenn es einen gibt, aber die falsche geheime Soße verwendet haben. Ich würde einen Rat begrüßen, um das zu tun.

+0

das ist cool, aber erfordert, dass Sie die validees in der Super-Klasse definieren? – SteveTurczyn

+0

@SteveTurczyn, Wenn die Valides in 'SubClass' sind, sehe ich keine Möglichkeit, sie zu ändern, ohne Aliase zu verwenden. Wenn Sie sie in 'MidClass' haben, aber sie auf einer Instanz von' SubClass' aufrufen, muss die Instanzmethode mit dem gleichen Namen in 'SubClass' nur eine Zeile enthalten, die einen Validator und' super' aufruft, was einfach mit 'erstellt werden kann definiere_Methode'. –

+0

Ich sehe, woher du kommst, und es scheint der einfachste Weg, um die Anforderungen zu erfüllen. – SteveTurczyn

Verwandte Themen