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?
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
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
Related
Lets say I have a class with hundreds of instance methods in it. Now I have the requirement to run each method only if a certain thing is detected. Also I want to run my detection algorithm once for whole class instance no matter how many methods got called. If not detected first time no methods get called. I cannot afford if else around that many methods so I have to get a workaround. I have the following said class:
class CrawlerModule
extend Callbacks
before_run [:method_names, :of, :my, :class], :check_if_detected
#detected = nil
def check_if_detected
if #detected.nil?
detect
end
#detected
end
#hundreds of methods
private
def detect
detected_now = #my_detection_algorithm
#detected = detected_now
end
end
What I have done so far is to include following Callbacks module to call my check_if_detected method before every method but it doesn't work because method_added called at the very start of program and my detect function need some things to get initialized before detection. So the result array is always nil. Here's that complete module:
module Callbacks
def self.extended(base)
base.send(:include, InstanceMethods)
end
def overridden_methods
#overridden_methods ||= []
end
def callbacks
#callbacks ||= Hash.new { |hash, key| hash[key] = [] }
end
def method_added(method_name)
return if should_override?(method_name)
overridden_methods << method_name
original_method_name = "original_#{method_name}"
alias_method(original_method_name, method_name)
define_method(method_name) do |*args|
result = run_callbacks_for(method_name)
if result[0] || (self.class.callbacks.values.flatten.include? method_name)
send(original_method_name, *args)
end
end
end
def should_override?(method_name)
overridden_methods.include?(method_name) || method_name =~ /original_/
end
def before_run(method_names, callback)
method_names.each do |method_name|
callbacks[method_name] << callback unless method_name.eql? callback
end
end
module InstanceMethods
def run_callbacks_for(method_name)
result = []
self.class.callbacks[method_name].to_a.each do |callback|
result << send(callback)
end
result
end
end
end
This solution came to me while trying to get to sleep, so pardon the brevity and untested code.
Forget all of the callback stuff. Instead...
You could rename every method to include a prefix like prefix_method_name (or suffix if you prefer).
Then implement a method_missing method which implements your check, and then calls the appropriate method afterward.
Something like this:
def method_missing(method_name, *args, &block)
if detected_now
send("prefix_#{method_name}")
end
end
And then to run the detection once for the whole class instance do it in the constructor:
def initialize
detected_now
super
end
Cache the detected_now results if you wish in an instance variable as normal and work with it that way if that is something you want to do.
app/models/product.rb
class Product < ApplicationRecord
def methode1.1
# Do something
end
def method1
# Do something
methode1.1
end
def self.method2
# Do something
method1
end
end
in controller
def Method_4
# Do something
Product.method2
# Do something
end
I call method2 from controller. When I run the program. I got an error:
undefined local variable or method methode1 '' for class
You call a class method Product.method2 and it tries to call an instance method method1. In order to do that, you need to find or initialize an instance of a model, e.g.:
# initialize
def self.method2
# Do something
new.method1
end
or
# find
def self.method2
# Do something
find_by(attr1: val1, attr2: val2).method1
end
I am trying to create around initialize callback for benchmarks.
class BaseProcessor
extend ActiveModel::Callbacks
define_callbacks :initialize
set_callback :initialize, :around, :run_benchmark
protected
def run_benchmark
#benchmark = Benchmark.realtime do
yield
end
end
end
Then other classes are inherited from this BaseProcessor
class Child < BaseProcessor
def initialize
run_callbacks :initialize do
# some stuff
end
end
end
So in every child I have to invoke run_callbacks. So my question is: can I avoid it?
If I understand you correctly. Something like this should work even without around callbacks.
class BaseProcessor
def self.new(*args)
Benchmark.realtime do
super
end
end
end
I'm creating a module that extends the functionality of an ActiveRecord model.
Here's my initial setup.
My class:
class MyClass < ActiveRecord::Base
is_my_modiable
end
And Module:
module MyMod
def self.is_my_modiable
class_eval do
def new_method
self.mod = true
self.save!
end
end
end
end
ActiveRecord::Base(extend,MyMod)
What I would like to do now is extend the functionality of the new_method by passing in a block. Something like this:
class MyClass < ActiveRecord::Base
is_my_modiable do
self.something_special
end
end
module MyMod
def self.is_my_modiable
class_eval do
def new_method
yield if block_given?
self.mod = true
self.save!
end
end
end
end
This doesn't work though, and it makes sense. In the class_eval, the new_method isn't being executed, just defined, and thus the yield statement wouldn't get executed until the method actually gets called.
I've tried to assign the block to a class variable within the class_eval, and then call that class variable within the method, but the block was being called on all is_my_modiable models, even if they didn't pass a block into the method.
I might just override the method to get the same effect, but I'm hoping there is a more elegant way.
If I understood you correctly, you can solve this by saving passed block to an instance variable on class object and then evaling that in instance methods.
bl.call won't do here, because it will execute in the original context (that of a class) and you need to execute it in scope of this current instance.
module MyMod
def is_my_modiable(&block)
class_eval do
#stored_block = block # back up block
def new_method
bl = self.class.instance_variable_get(:#stored_block) # get from class and execute
instance_eval(&bl) if bl
self.mod = true
self.save!
end
end
end
end
class MyClass
extend MyMod
is_my_modiable do
puts "in my modiable block"
self.something_special
end
def something_special
puts "in something special"
end
attr_accessor :mod
def save!; end
end
MyClass.new.new_method
# >> in my modiable block
# >> in something special
You can do this by assigning the block as a method parameter:
module MyMod
def self.is_my_modiable
class_eval do
def new_method(&block)
block.call if block
self.mod = true
self.save!
end
end
end
end
I want to find a gem or to write a code that implements hooks for methods.
class A
include SomeModule
before_hook :meth, lambda { puts 'bla' }
def meth
puts 'meth'
end
end
# A.new.meth => "bla\nmeth\n"
I am using Rails and I know about callbacks and filters but
meth isn't an action
I don't wand to change how I method call
Help me please...
UPDATE
I find a gem for automating this code:
include ActiveSupport::Callbacks
define_callbacks :meth_callback
set_callback :meth_callback, :before do |object|
# my code
end
def meth_with_callback
run_callbacks(:meth_callback) { meth }
end
alias_method_chain :meth, :callback
You can use ActiveModel::Callbacks
define_model_callbacks :create
def create
run_callbacks :create do
# do your thing here
end
end
You can even write a little helper method to hide this run_callbacks line. It may look like this:
hooked_method :create do
# do your thing here
end