I have this code:
class B
def self.definer(name, *args, &block)
define_method(name) { self.instance_exec(*args, &block) }
end
end
and when I try to use it, I get this error:
B.definer(:tst) { super }
# => :tst
B.new.tst
# => TypeError: self has wrong type to call super in this context: B (expected #<Class:#<Object:0x007fd3008123f8>>)
I understand that super has a special meaning, and works little different from calling a method. Can someone explain why and what is happening? It would also be great if someone suggests a solution for this.
I don't get the same error message as you did, but get an error anyway. super must be used within a method definition. But you are not using it in a method definition. That raises an error.
Regarding the solution, I cannot give you one since it is not clear at all what you are trying to do.
You definitely don't want instance_exec there.
If you didn't have the *args involved, I'd say you just wanted this:
def self.definer(name, &block)
define_method(name, &block)
end
But then your new definer method would do the exact same thing that define_method does in the first place, so there's be no reason to create it, instead of just using define_method in the first place.
What are you actually trying to do? Explain what you want to do, and maybe someone can help you.
But I think the instance_exec in your existing implementation isn't what you want -- it is immediately executing the block upon definer call, when calling define_method -- I think you want the block executed when the method you are defining is being called instead? But I'm not really sure, it depends on what you're trying to do, which is unclear. super doesn't really make any sense within an instance_exec -- super to what method did you think you'd be calling?
Related
So, I'm currently learning about metaprogramming in Ruby and I want to fully understand what is happening behind the scenes.
I followed a tutorial where I included some of the methods in my own small project, an importer for CSV files and I have difficulties to wrap my hand around one of the methods used.
I know that the define_method method in Ruby exists to create methods "on the fly", which is great. Now, in the tutorial the method initialize to instantiate an object from a class is defined with this method, so basically it looks like this:
class Foo
def self.define_initialize(attributes)
define_method(:initialize) do |*args|
attributes.zip(args) do |attribute, value|
instance_variable_set("##{attribute}", value)
end
end
end
end
Next, in an initializer of the other class first this method is called with Foo.define_initialize(attributes), where attributes are the header row from the CSV file like ["attr_1", "attr_2", ...], so the *args are not provided yet.
Then in the next step a loop loops over the the data:
#foos = data[1..-1].map do |d|
Foo.new(*d)
end
So here the *d get passed as the *args to the initialize method respectively to the block.
So, is it right that when Foo.define_initialize gets called, the method is just "built" for later calls to the class?
So I theoretically get a class which now has this method like:
def initialize(*args)
... do stuff
end
Because otherwise, it had to throw an exception like "missing arguments" or something - so, in other words, it just defines the method like the name implies.
I hope that I made my question clear enough, cause as a Rails developer coming from the "Rails magic" I would really like to understand what is happening behind the scenes in some cases :).
Thanks for any helpful reply!
Short answer, yes, long answer:
First, let's start explaining in a really (REALLY) simple way, how metaprogramming works on Ruby. In Ruby, the definition of anything is never close, that means that you can add, update, or delete the behavior of anything (really, almost anything) at any moment. So, if you want to add a method to Object class, you are allowed, same for delete or update.
In your example, you are doing nothing more than update or create the initialize method of a given class. Note that initialize is not mandatory, because ruby builds a default "blank" one for you if you didn't create one. You may think, "what happens if the initialize method already exist?" and the answer is "nothing". I mean, ruby is going to rewrite the initialize method again, and new Foo.new calls are going to call the new initialize.
This is much more a curiosity than something I am living in a real situation.
Let's assume I need to define a method at a certain point of my program, using
self.class.send(:define_method, method_name)
without a block.
It happens that at this point I still don't know exactly what this method must do. In other words, I don't have a block of code to associate with this method.
My question is: Is there some way to create this association afterwards? A way to say 'Hey, from now on this method shall execute this block here?
Yes, just redefine it with
self.class.send(:define_method, method_name, &block)
Methods can be redefined as many times as you want.
I have been thinking about blocks in Ruby.
Please consider this code:
div {
h2 'Hello world!'
drag
}
This calls the method div(), and passes a block to it.
With yield I can evaluate the block.
h2() is also a method, and so is drag().
Now the thing is - h2() is defined in a module, which
is included. drag() on the other hand resides on an
object and also needs some additional information.
I can provide this at run-time, but not at call-time.
In other words, I need to be able to "intercept"
drag(), change it, and then call that method
on another object.
Is there a way to evaluate yield() line by line
or some other way? I don't have to call yield
yet, it would also be possible to get this
code as string, modify drag(), and then
eval() on it (although this sounds ugly, I
just need to have this available anyway
no mater how).
If I'm understanding you correctly, it seems that you're looking for the .tap method. Tap allows you to access intermediate results within a method chain. Of course, this would require you to restructure how this is set up.
You can kind of do this with instance_eval and a proxy object.
The general idea would be something like this:
class DSLProxyObject
def initialize(proxied_object)
#target = proxied_object
end
def drag
# Do some stuff
#target.drag
end
def method_missing(method, *args, &block)
#target.send(method, *args, &block)
end
end
DSLProxyObject.new(target_object).instance_eval(&block)
You could implement each of your DSL's methods, perform whatever modifications you need to when a method is called, and then call what you need to on the underlying object to make the DSL resolve.
It's difficult to answer your question completely without a less general example, but the general idea is that you would create an object context that has the information you need and which wraps the underlying DSL, then evaluate the DSL block in that context, which would let you intercept and modify individual calls on a per-usage basis.
I was trying to write my first method_missing override when I kept running into (edited) stack level too deep errors. The main culprit seemed to be trying to utilize an instance attribute. For instance if 'self' was a instance of the User class then checking for something like:
def method_missing(name)
if self.name
# do stuff
end
end
Would seg fault. I spent a long time on this but ended up giving up. There must be something I'm not understanding about accessing it.
Edit
My apologies, Andrew is correct, I am getting Stack Level too deep errors. With this in mind, what is the appropriate (if any) way to access the instances attribute values?
You can potentially rectify this problem by ensuring that self.name actually exists:
def method_missing(name)
if self.respond_to?(:name) && self.name
# do stuff
end
end
Note this may not work if your class inherits from anything Railsy (e.g. ActiveRecord::Base), since it overrides respond_to?.
If you are in a Railsy class, your method missing should call super, lest you lose a lot of the "magic" ActiveRecord methods (including, probably, self.name itself):
def method_missing(name, *args, &block)
if name_is_something_i_should_handle_here
# do your stuff
else
super(name, *args, block) # call parent's method_missing
end
end
Obviously you should replace name_is_something_i_should_handle_here with the appropriate logic.
You may also wish to consider using dynamic method creation instead of method_missing.
I'm trying to make an API for dynamic reloading processes; right now I'm at the point where I want to provide in all contexts a method called reload!, however, I'm implementing this method on an object that has some state (so it can't be on Kernel).
Suppose we have something like
WorkerForker.run_in_worker do
# some code over here...
reload! if some_condition
end
Inside the run_in_worker method there is a code like the following:
begin
worker = Worker.new(pid, stream)
block.call
rescue NoMethodError => e
if (e.message =~ /reload!/)
puts "reload! was called"
worker.reload!
else
raise e
end
end
So I'm doing it this way because I want to make the reload! method available in any nested context, and I don't wanna mess the block I'm receiving with an instance_eval on the worker instance.
So my question is, is there any complications regarding this approach? I don't know if anybody has done this already (haven't read that much code yet), and if it has been done already? Is there a better way to achieve the objective of this code?
Assuming i understand you now, how about this:
my_object = Blah.new
Object.send(:define_method, :reload!) {
my_object.reload!
...
}
Using this method every object that invokes the reload! method is modifying the same shared state since my_object is captured by the block passed to define_method
what's wrong with doing this?
def run_in_worker(&block)
...
worker = Worker.new(pid, stream)
block.call(worker)
end
WorkerForker.run_in_worker do |worker|
worker.reload! if some_condition
end
It sounds like you just want every method to know about an object without the method or the method's owner having been told about it. The way to accomplish this is a global variable. It's not generally considered a good idea (because it leads to concurrency issues, ownership issues, makes unit testing harder, etc.), but if that's what you want, there it is.