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
Related
I'm trying to achieve something that is for sure possible but I'm not able to put it into to find it from the docuemntation.
In a nutshell, I would like to define methods dynamically:
Initial point:
class Foo < Bar
def baz
RecordLoader.for(Baz).load(object.baz_id)
end
def qux
RecordLoader.for(Quz).load(object.qux_id)
end
end
class Bar
end
I would like to be able to change it to
class Foo < Bar
record_loader_for :baz
record_loader_for :qux
end
class Bar
def self.record_loader_for(attribute)
define_method attribute.to_s do
# What is missing here?
end
end
end
I'm trying to figure out how I can use the value of attribute to write something like
RecordLoader.for(attribute.to_s.classify.constantize). # <- attribute is local to the class
.load(object.send("#{attribute.to_s}_id")) # <- object is local to the instance
You can go with class_eval and generate your method into string:
def self.record_loader_for(attribute)
class_eval <<~RUBY, __FILE__ , __LINE__ + 1
def #{attribute}
RecordLoader.for(#{attribute.to_s.classify}).load(#{attribute}_id)
end
RUBY
end
but in fact, define_method should work too, ruby will save closure from the method call:
require 'active_support'
require 'active_support/core_ext'
require 'ostruct'
class RecordLoader
def self.for(cls)
new(cls)
end
def initialize(cls)
#cls = cls
end
def load(id)
puts "loading #{#cls} id #{id}"
end
end
class Baz; end
class Bar
def object
OpenStruct.new(baz_id: 123, qux_id:321)
end
def self.record_loader_for(attribute)
define_method attribute.to_s do
RecordLoader.for(attribute.to_s.classify.constantize).
load(object.send("#{attribute.to_s}_id"))
end
end
end
class Foo < Bar
record_loader_for :baz
record_loader_for :qux
end
Foo.new.baz
class_eval is slower to define method, but resulting method executes faster and does not keep references to original closure context, define_method is the opposite - defines faster, but method runs slower.
I want to use an instance variable throughout a class's methods. Unfortunately, the methods are all class (not instance) methods, so the classic #my_data ||= [1,2,3] doesn't work, since every call to the class's class-methods will reuse the same data forever:
class MyThing
class << self
def do_something
do_something_else
end
private
def my_data
#my_data ||= Time.now
end
def do_something_else
puts my_data
end
end
end
MyThing.do_something # puts the current time
MyThing.do_something # puts the same time as above (not what I want)
One way around it is to declare the variable, then set it to nil at the end of the method like this:
class MyThing
class << self
def do_something
#my_data = my_data
do_something_else
#my_data = nil
end
private
def my_data
Time.now
end
def do_something_else
puts #my_data || my_data
end
end
end
MyThing.do_something # puts the current time
MyThing.do_something # puts the new current time (yay)
However, setting the data and destroying it at the start and end of the method seems messy.
Is there a cleaner way to say "set (#my_data = Time.now) until the end of this method"?
It's soluiton, if I understand you correctly.
class MyThing
class << self
def do_something
do_something_else
end
private
def my_data
#my_data = Time.now
end
def do_something_else
puts my_data
end
end
end
The name says it all "instance variable" is available as long as the instance lives. In your case that instance is the MyThing class, meaning it doesn't reset automatically. Classes stay loaded as long as your program runs, so it will never reset.
The simplest solution for the given scenario is to use method arguments.
class MyThing
class << self
def do_something
do_something_else(my_data)
end
private
def do_something_else(my_data)
puts my_data || Time.now
end
end
end
The other option you already discovered, that is, setting an instance variable and then resetting it.
Another alternative is moving the thing your doing into an class instance. The disadvantage here is that this solution produces more overhead code. However it does keep the MyThing class cleaner, since you can move helper methods into this new class.
class MyThing
class DataObject
attr_reader :data
def initialize(data)
#data = data || Time.now
end
def do_something_else
puts data
end
end
class << self
def do_something
DataObject.new(my_data).do_something_else
end
end
end
If possible you could also create a MyThing instance instead of an DataObject instance, but I assumed the MyThing class already has another purpose.
Why when I do self.method from a class, I get an undefined method `my_method' for MyModule::MyOtherModule::MyClass:Class
module MyModule
module OtherModule
class MyClass < Base
def my_method
end
def self.my_self_method
my_method
end
end
end
end
I call my_self_method with send from an herited [sic] class:
class Base
class << self
my_method(method_name)
send("my_self_#{method_name}")
end
end
end
I don't understand it.
In your code, you're defining one instance method (my_method), and one class method (my_self_method).
This means you can call:
MyClass.my_self_method
or
MyClass.new.my_method
If you want my_method to be callable from my_self_method, you could define it as:
def self.my_method
...
end
Then the following would be available:
def self.my_self_method
my_method
end
Here's another alternative. There's a comment that suggests it's bad practice to call new.my_method from within a class method, but I've seen a pattern that applies this that I find quite idiomatic, for example:
class MyClass
def self.run(the_variables)
new(the_variables).process
end
def initialize(the_variables)
# setup the_variables
end
def process
# do whatever's needed
end
end
This allows a simple entry point of MyClass.run(the_variables). If your use case seems suitable, a similar pattern for you would be:
module MyModule
module OtherModule
class MyClass < Base
def my_method
end
def self.my_self_method
new.my_method
end
end
end
end
I'm sure there's scope to disagree with this pattern, and would be interested to hear others' opinions in the comments.
Hope this helps clear a few things up #N.Safi.
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 am trying to do a custom active record macro. But it right now seems impossible set an instance variable from within it's block.. here is what i am trying to do.
module ActiveRecord
class Base
def self.included(base)
base.class.send(:define_method, :my_macro) do |args|
# instance_variable_set for the model instance that has called this
# macro using args
end
end
end
end
i have tried class_eval, instance_eval.. but nothing seems to work or i don't how to use them.
Thanks in advance.
Edit: Let me try to explain better. I have a class method. An instance of the class calls this method. Now, this class method should instruct the instance to set an instance variable for itself.
Edit- this is how i want o use the macro
class MyModel < ActiveRecord::Base
my_macro(*args)
def after_initialize
# use the value set in the macro as #instance variable
end
end
Is this what you are thinking of:
class DynamicAdd
def add_method
self.class_eval do
attr_accessor :some_method
end
end
end
You can then do the following:
k = DynamicAdd.new
k.some_method = "hi"
should result in an undefined method error.
But,
k = DynamicAdd.new
k.add_method
k.some_method = "hi"
should work.
You can use this same format to define other types of methods besides attr_accessors as well:
class DynamicAdd
def add_method
self.class_eval do
def some_method
return "hi"
end
end
end
end
Hm.. Isn't included() a Module method? I don't think you can use that in a class like you have written. If you want to create a class method you can do
class Base
def self.my_method
end
or
class Base
class << self
def my_method
end
end
If all you want to do is to add an instance variable to an existing object, then you can use #instance_variable_set
class Base
class << self
def my_method(instance_of_base, value)
instance_of_base.instance_variable_set "#x", value
end
end
end
a = Base.new
a.class.send(:my_method, *[a,4])