Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Hook/Callback/Macro Methods

How do I create a Custom Hook Method in a Subclass?

No need to duplicate Rails, of course -- the simpler, the better.

My goal is to convert:

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

To:

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

Example in Module: 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

like image 990
Trajanson Avatar asked Sep 15 '26 12:09

Trajanson


2 Answers

Here's a solution that uses prepend. When you call before_operations for the first time it creates a new (empty) module and prepends it to your class. This means that when you call method foo on your class, it will look first for that method in the module.

The before_operations method then defines simple methods in this module that first invoke your 'before' method, and then use super to invoke the real implementation in your 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>}

If you want to make the idea by @SteveTurczyn work you must:

  1. receive the args params in the block of define_method, not as arguments to it.
  2. call before_operations AFTER your methods have been defined if you want to be able to alias them.

 

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>}
like image 140
Phrogz Avatar answered Sep 17 '26 05:09

Phrogz


You can alias the original method to a different name (so :do_this_something becomes :original_do_this_something) and then define a new :do_this_something method that calls :first_validate_something and then the original version of the method Something like this...

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
like image 42
SteveTurczyn Avatar answered Sep 17 '26 07:09

SteveTurczyn



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!