call before methods in model on ruby

2020-07-09 07:38发布

问题:

This my implementation to developing way to run code before all method in your model

The call "before_hook :months_used" method need to be on bottom of class to the ExecutionHooks can get the instance_method loaded in the module. I would like to load the instance methods on top

class BalanceChart < BalanceFind
 include ExecutionHooks

 attr_reader :options

 def initialize(options = {})
  @options = options
  @begin_at = @options[:begin_at]
 end

 def months_used
  range.map{|date| I18n.l date, format: :month_year}.uniq!
 end

 before_hook :months_used
end

module ExecutionHooks

def self.included(base)
 base.send :extend, ClassMethods
end

module ClassMethods
  def before
   @hooks.each do |name|
    m = instance_method(name)
    define_method(name) do |*args, &block|  

      return if @begin_at.blank? ## the code you can execute before methods

      m.bind(self).(*args, &block) ## your old code in the method of the class
    end
   end
  end

  def before_hook(*method_name)
   @hooks = method_name
   before
  end

  def hooks
   @hooks ||= []
  end
 end
end

回答1:

You can do this with prepend. prepend is like include in that it adds a module to the ancestors of the class, however instead of adding it after the class it adds it before.

This means that if a method exists both in the prepended module and the class then the module implementation is called first (and it can optionally call super if it wants to call the base class).

This allows you to write a hooks module like so:

module Hooks
  def before(*method_names)
    to_prepend = Module.new do
      method_names.each do |name| 
        define_method(name) do |*args, &block|
          puts "before #{name}"
          super(*args,&block)
        end
      end
    end
    prepend to_prepend
  end
end


class Example
  extend Hooks
  before :foo, :bar

  def foo
    puts "in foo"
  end
  def bar
    puts "in bar"
  end
end

In real use you would probably want to stash that module somewhere so that each call to before doesn't create a new module but that is just an inplementation detail



回答2:

Instead of redefining the method when calling before_hook, you could override the method_added hook to prepend your before hooks to a method right after it was defined. This way your before_hook calls can be (actually, must be) placed at the top of the class definition.



回答3:

@rathrio This is my implementation using method_added that you talked. Thanks

module ExecutionHooks

def validation
  p "works1"
end

def self.included(base)
  base.send :extend, ClassMethods
end

module ClassMethods

  attr_writer :hooked

  def hooked
    @hooked ||= []
  end

  def method_added(method)
    return if @hooks.nil? 
    return unless @hooks.include?(method)
    m = self.instance_method(method)
    unless hooked.include?(method)
      hooked << method
      define_method(method) do |*args, &block|  
        validation      
        m.bind(self).(*args, &block) ## your old code in the method of the class
      end
    end
  end

  def before_hook(*method_name)
    @hooks = method_name
  end

  def hooks
    @hooks ||= []
  end
 end
end

class BalanceChart < BalanceFind
 include ExecutionHooks
 before_hook :months_data, :months_used, :debits_amount, :test

  def test
    "test"
  end
end