I have Memoize module, that provides methods for caching of class and instance methods.
module Memoize
def instance_memoize(*methods)
memoizer = Module.new do
methods.each do |method|
define_method method do
#_memoized_results ||= {}
if #_memoized_results.include? method
#_memoized_results[method]
else
#_memoized_results[method] = super()
end
end
end
end
prepend memoizer
end
def class_memoize(*methods)
methods.each do |method|
define_singleton_method method do
#_memoized_results ||= {}
if #_memoized_results.include? method
#_memoized_results[method]
else
#_memoized_results[method] = super()
end
end
end
end
end
This is an example of how I use it:
class Foo
extend Memoize
instance_memoize :instance_method1, :instance_method2
class_memoize :class_method1, :class_method2
...
end
Please advice how to avoid code duplication in this module.
One might define a lambda:
λ = lambda do
#_memoized_results ||= {}
if #_memoized_results.include? method
#_memoized_results[method]
else
#_memoized_results[method] = super()
end
end
And. then:
define_method method, &λ
Please be aware of an ampersand in front of λ, it is used to notice define_method about it’s receiving a block, rather than a regular argument.
I did not get what failed with this approach on your side, but here is a bullet-proof version:
method_declaration = %Q{
def %{method}
#_memoized_results ||= {}
if #_memoized_results.include? :%{method}
#_memoized_results[:%{method}]
else
#_memoized_results[:%{method}] = super()
end
end
}
methods.each do |method|
class_eval method_declaration % {method: method}
end
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.
Method super calls parent method based on __method__.
How to call parent method based on __callee__?
class SomeLogic
DICTIONARY = {
new_method_1: 'dictionary value 1',
new_method_2: 'dictionary value 2'
}
def initialize(method_name)
#method_name = method_name
end
def call
DICTIONARY[#method_name]
end
end
module M
extend ActiveSupport::Concern
def base_method
logic = SomeLogic.new(__callee__).call
if logic
logic
elsif defined?(__callee__)
super # here analogue of super needed
else
{}
end
end
alias_method :new_method_1, :base_method
alias_method :new_method_2, :base_method
alias_method :new_method_3, :base_method
end
class A
prepend M
def new_method_3
'foo'
end
end
Expected results:
A.new.new_method_1 # dictionary value 1
A.new.new_method_2 # dictionary value 2
A.new.new_method_3 # foo
Current results:
A.new.new_method_1 # dictionary value 1
A.new.new_method_2 # dictionary value 2
A.new.new_method_3 # no superclass method `base_method' for A
If you change
class A
prepend M
to
class A
include M
you will get the desired output
Okay so this is extremely convoluted but it will work as requested
class SomeLogic
DICTIONARY = {
new_method_1: 'dictionary value 1',
new_method_2: 'dictionary value 2'
}
def initialize(method_name)
#method_name = method_name
end
def call
DICTIONARY[#method_name]
end
end
module M
def self.prepended(base)
base.extend(ClassMethods)
end
def base_method(&block)
SomeLogic.new(__callee__).call ||
block&.call || # ruby < 2.3 block && block.call
{}
end
module ClassMethods
def method_added(method_name)
if M.instance_methods.include?(method_name)
orig = M.instance_method(method_name)
M.remove_method(method_name)
new = self.instance_method(method_name)
self.prepend(Module.new do
define_method(method_name) do
result = orig.bind(self).call(&new.bind(self))
end
end)
M.define_method(method_name, &orig.bind(M))
end
end
end
alias_method :new_method_1, :base_method
alias_method :new_method_2, :base_method
alias_method :new_method_3, :base_method
alias_method :new_method_4, :base_method
end
class A
prepend M
def new_method_3
'foo'
end
end
class B
prepend M
end
So when you define a new method we check to see if that method is already defined by M. If so then we capture that UnboundMethod remove the method from M, then we capture the new definition as an UnboundMethod then define the new method in an anonymous Module as Ms implementation passing the new method implementation as a proc to Ms implementation and prepend this module to the defining Class, then we place Ms implementation back into M so other classes still work.
Result:
p A.new.new_method_1
#=> 'dictionary value 1'
p A.new.new_method_2
#=> 'dictionary value 2'
p A.new.new_method_3
#=> 'foo'
p A.new.new_method_4
#=> {}
p B.new.new_method_3
#=> {}
If you would prefer include over prepend then M could look like
module M
def self.included(base)
super
base.extend(ClassMethods)
end
def base_method
if logic = SomeLogic.new(__callee__).call
logic
elsif self.class.instance_methods(false).include?(__callee__)
send(__callee__,true)
else
{}
end
end
module ClassMethods
def method_added(method_name)
return if #_adding_method # avoid stack level issues
if M.instance_methods.include?(method_name)
new = instance_method(method_name)
#_adding_method = true # avoid stack level issues
define_method(method_name) do |bypass_parent=false|
return super() unless bypass_parent
new.bind(self).call
end
#_adding_method = false
end
end
end
end
I've entirely reworded this question as I feel this more accurately reflects what I wanted to ask the first time in a less roundabout way.
After instantiating a FormObject, calls to dynamically defined methods do not evaluate their block parameter in the context I'm trying for. For example:
#registration = RegistrationForm.new
#registration.user
# undefined local variable or method `user_params' for RegistrationForm:Class
RegistrationForm calls a class method exposing(:user) { User.new(user_params) } which I would like to have define a new method that looks like this:
def user
#user ||= User.new(user_params)
end
My implementation doesn't use #ivar ||= to cache the value (since falsey values will cause the method to be re-evaluated). I borrowed the idea from rspec's memoized_helpers and I 'think' I understand how it works. What I don't understand is what I should replace class_eval with in lib/form_object/memoized_helpers.rb.
Thank you
lib/form_object/base.rb
class FormObject::Base
include ActiveModel::Model
include FormObject::MemoizedHelpers
attr_reader :params, :errors
def initialize(params = {})
#params = ActionController::Parameters.new(params)
#errors = ActiveModel::Errors.new(self)
end
def save
valid? && persist
end
end
lib/form_object/memoized_helpers.rb
module FormObject
module MemoizedHelpers
private
def __memoized
#__memoized ||= {}
end
def self.included(mod)
mod.extend(ClassMethods)
end
module ClassMethods
def exposing(name, &block)
raise "#exposing called without a block" unless block_given?
class_eval do
define_method(name) { __memoized.fetch(name) { |k| __memoized[k] = block.call } }
end
end
end
end
end
app/forms/registration_form.rb
class RegistrationForm < FormObject::Base
exposing(:user) { User.new(user_params) { |u| u.is_admin = true } }
exposing(:tenant) { user.build_tenant(tenant_params) }
validate do
tenant.errors.each do |key, value|
errors.add("#{tenant.class.name.underscore}_#{key}", value)
end unless tenant.valid?
end
validate do
user.errors.each do |key, value|
errors.add("#{user.class.name.underscore}_#{key}", value)
end unless user.valid?
end
private
def persist
user.save
end
def user_params
params.fetch(:user, {}).permit(:first_name, :last_name, :email, :password, :password_confirmation)
end
def tenant_params
params.fetch(:tenant, {}).permit(:name)
end
end
So, I might have simplified this example too much, but I think this is what you want:
module Exposing
def exposing(name, &block)
instance_eval do
define_method(name, block)
end
end
end
class Form
extend Exposing
exposing(:user) { user_params }
def user_params
{:hi => 'ho'}
end
end
Form.new.user
You can fiddle around here: http://repl.it/OCa
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
Issue:
I am using a global constant STORE_DATA_ENABLED to enable a module I wrote (StoreData):
Example:
STORE_DATA_ENABLED = false
...
module StoreData
def a
return unless STORE_DATA_ENABLED
...
end
def b
return unless STORE_DATA_ENABLED
...
end
def c
return unless STORE_DATA_ENABLED
...
end
...
end
I think there is a way to disable the module without checking all the methods in the module.
Any Ideas to DRY this code ?
Use a before_filter. This is called before every method call in your module:
module StoreData
before_filter :store_data_enabled
def a
...
end
private
def store_data_enabled
STORE_DATA_ENABLED # return implicit true/false
end
end
EDIT: a ruby-only approach. This uses the module initializer to re-create all public methods in the module and make them return nil. I did not try how this behaves when you have arguments in your methods.
module M
# this is called while module init
def self.included(base)
unless STORE_DATA_ENABLED
# iterate over public methods of this instance
public_instance_methods.each do |m|
# overwrite all methods, does not care about arguments
define_method(m) { return nil }
end
end
end
def a
...
end
end