4

I have a Model, which has method_1 to method_10. I also have ModelObserver. I would like to notifiy ModelObserver before invoking method1 to method_9, but not method_10.

Is there a DRY way to write this, instead of repeating notify_observers(:after_something) in all 9 methods?

Arslan Ali
  • 17,418
  • 8
  • 58
  • 76
AdamNYC
  • 19,887
  • 29
  • 98
  • 154

2 Answers2

8

Add a file called monkey_patches.rb in config/initializers dirctory.

class Object
  def self.method_hook(*args)
    options = args.extract_options!
    return unless (options[:before].present? or options[:after].present?)
    args.each do |method_name|      
      old_method = instance_method(method_name) rescue next

      define_method(method_name) do |*args|
        # invoke before callback
        if options[:before].present?
          options[:before].is_a?(Proc) ? options[:before].call(method_name, self):
            send(options[:before], method_name) 
        end

        # you can modify the code to call after callback 
        # only when the old method returns true etc..
        old_method.bind(self).call(*args)

        # invoke after callback
        if options[:after].present?
          options[:after].is_a?(Proc) ? options[:after].call(method_name, self): 
            send(options[:after], method_name) 
        end
      end
    end
  end
end  

The patch enables you to add before and after callbacks on an instance method of a class. A hook can be:

  • The name of an instance method which accepts one parameter
  • A lambda accepting two parameters

Multiple hooks can be registered on a same method. The method being hooked should come before the hook.

E.g:

class Model < ActiveRecord::Base

  def method1
  end

  def method2
  end

  def method3
  end

  def method4
  end

  def update_cache
  end

  # instance method name as `after` callback parameter
  method_hook :method1, :method2, :after => :update_cache

  # lambda as `before` callback parameter
  method_hook :method1, :method2, 
    :before => lambda{|name, record| p name;p record}

  # lambda as `after` callback parameter
  method_hook :method3, :method4, 
    :after => lambda{|name, record| 
       Model2.increment_counter(:post_count, record.model2_id)}

end
Harish Shetty
  • 64,083
  • 21
  • 152
  • 198
  • Fantastic answer. Was able to easily modify the hook definition to add additional functionality needed in my application. For instance, capturing the response of the `old_method` call and returning it from the new `define_method`. – Michael Lynch May 29 '19 at 14:42
1

How about something like this?

def notify; puts "Was notified."; end
def method1; end
def method2; end
def method3; end

def original
  notify
  method1
  notify
  method2
  method3
end

def dry
  [:method1, :method2].each do |m|
    notify
    send(m)
  end
  method3
 end

original
dry
bdon
  • 2,068
  • 17
  • 15
  • Thanks bdon. It's a good idea, but won't work. notify_observers require a method name for the observer that is specific to method1 (such as: notify_observers(:after_method_1), notify_observers(:after_method_2). Furthermore, notify statement must be put within method_1, method_2 etc. – AdamNYC Feb 20 '12 at 06:22