Run custom callback without run_callback - ruby-on-rails

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

Related

Checking a condition before running multiple methods. Ruby on Rails

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.

How to call `super` in a block

I have a block of code. It was:
class User < ActiveRecord::Base
def configuration_with_cache
Rails.cache.fetch("user_#{id}_configuration") do
configuration_without_cache
end
end
alias_method_chain :configuration, :cache
end
I want to remove the notorious alias_method_chain, so I decided to refactor it. Here is my version:
class User < ActiveRecord::Base
def configuration
Rails.cache.fetch("#{id}_agency_configuration") do
super
end
end
end
But it doesn't work. The super enters a new scope. How can I make it work? I got TypeError: can't cast Class, and I misunderstood it.
To start off, calling super in blocks does behave the way you want. Must be your console is in a corrupted state (or something).
class User
def professional?
Rails.cache.fetch("user_professional") do
puts 'running super'
super
end
end
end
User.new.professional?
# >> running super
# => false
User.new.professional?
# => false
Next, this looks like something Module#prepend was made to help with.
module Cacher
def with_rails_cache(method)
mod = Module.new do
define_method method do
cache_key = "my_cache_for_#{method}"
Rails.cache.fetch(cache_key) do
puts "filling the cache"
super()
end
end
end
prepend mod
end
end
class User
extend Cacher
with_rails_cache :professional?
end
User.new.professional?
# >> filling the cache
# => false
User.new.professional?
# => false
you can user Super in block.
please see this, any issues let me know.
Calling it as just 'super' will pass the block.
super(*args, &block)' will as well.

Access variables outside a class method

How do I access things outside of a class method in rails? I get an error like undefined method do_something_else
module Thing
def self.do_something
do_something_else
end
def do_something_else
end
end
Here's a good reference that shows the difference between class_methods/singleton_methods and instance_methods.
In your case, you cannot access the instance method(do_something_else) without an instance.
To solve this, you have to include the module in a class and use an instance of that class.
module Thing
def self.do_something
Logic.new.do_something_else
end
def do_something_else
#perform the logic and actions here
end
end
class Logic
include Thing
end
If you would like to think of it differently though, here's what I'd propose:
module Thing
def self.do_something_else
# perform your logic and actions here
end
def do_something
# this is possible because do_something_else is defined on the module Thing
Thing.do_something_else
end
end
Try this
def self.do_something
Thing.new.do_something_else
end

Passing a block to a dynamically created method

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

How does one include a module with cache expirations in sweepers?

We have the following sweeper in a rails application:
class AgencyEquipmentTypeSweeper < ActionController::Caching::Sweeper
observe AgencyEquipmentType
#include ExpireOptions
def after_update(agency_equipment_type)
expire_options(agency_equipment_type)
end
def after_delete(agency_equipment_type)
expire_options(agency_equipment_type)
end
def after_create(agency_equipment_type)
expire_options(agency_equipment_type)
end
def expire_options(agency_equipment_type)
Rails.cache.delete("agency_equipment_type_options/#{agency_equipment_type.agency_id}")
end
end
We'd like to extract the after_update, after_delete, and after_create callbacks to a module called "ExpireOptions"
The module should look like this (with the 'expire_options' method staying behind in the
original sweeper):
module ExpireOptions
def after_update(record)
expire_options(record)
end
def after_delete(record)
expire_options(record)
end
def after_create(record)
expire_options(record)
end
end
class AgencyEquipmentTypeSweeper < ActionController::Caching::Sweeper
observe AgencyEquipmentType
include ExpireOptions
def expire_options(agency_equipment_type)
Rails.cache.delete("agency_equipment_type_options/#{agency_equipment_type.agency_id}")
end
end
BUT the cache expirations only work if we define the methods explicitly inside the sweeper. Is there an easy way to extract those callback methods to a module, and still have them work?
Try with:
module ExpireOptions
def self.included(base)
base.class_eval do
after_update :custom_after_update
after_delete :custom_after_delete
after_create :custom_after_create
end
end
def custom_after_update(record)
expire_options(record)
end
def custom_after_delete(record)
expire_options(record)
end
def custom_after_create(record)
expire_options(record)
end
end
I would try something like:
module ExpireOptions
def after_update(record)
self.send(:expire_options, record)
end
def after_delete(record)
self.send(:expire_options, record)
end
def after_create(record)
self.send(:expire_options, record)
end
end
This should make sure it does not try to call those methods on the module, but on self which would hopefully be the calling object.
Does that help?

Resources