I'm learning ruby metaprogramming at the moment and while I was playing around and testing stuff, I stumbled upon something I can't seem to find the answer to. Let's say we have the following code:
class Foo
end
Foo.instance_eval do
define_method("bar") do
1
end
end
I would expect this to add a class method called bar to Foo but instead when I call the method it says it's undefined. What baffles me even more is that the same code works when I use def instead of define_method. Both ways seem to work when I try to define an instance method with class_eval as well. So what's really going on here?
Thanks in advance.
Let's make it simple.
define_method is a method. Or I should say a private class method of Object class. You invoke it by giving it an argument as instance method name you are going to define, and a block which contains the code of the method. apidock has very clear definition. You may want to read documentation.
def is a keyword. You use this to define methods just as you do all the time. Not really related to meta-programming.
If you are trying define class method, use class_eval, and give it a string. As its name indicates, instance_eval defines stuffs on instance level. In your code, if you do Foo.instance_methods, you will find the bar method. So if you do Foo.new.bar it returns 1, as TK-421 answered you. But since define_method defines instance_method, as indicated by documentation, regardless if you use class_eval or instance_eval, you will get instance method.
Here's the documentations you can read and they will answer all you question.
class_eval: http://apidock.com/ruby/v1_9_3_392/Module/class_eval
define_method: http://apidock.com/ruby/Module/define_method
instance_eval: http://apidock.com/ruby/Object/instance_eval
And don't forget this all mighty: http://www.google.com :D
Differences between def and define_method.
1:- define_method can use variables from the scope where it was defined.
local_var = "Hello World"
def greet1
local_var #undefined local_var
end
define_method(:greet2) do
local_var # returns local_var
end
2:- If you want to define method whose name is stored inside a variable, then you will have to use define_method. You can't declare methods dynamically using def.
So based on the requirement you will have to use def or define_method.
Explanation for your code.
Here you will have to use #define_singleton_method instead of #define_method to define class methods for Foo class. define_method will define instance methods for class Foo
So the expected code should be
class Foo
end
Foo.instance_eval do
define_singleton_method("bar") do
1
end
define_method("baz") do
2
end
end
Foo.bar #=> 1
Foo.new.baz #=> 2
It's an instance method. So:
f = Foo.new
f.bar # 1
Related
You can define classes as constants on Object, using the const_set method. Are there any interesting or instructive use cases where someone would pass arguments into the Class.new block?
Object.const_set(:Klass, Class.new do |can_i_use_this|
def ping
"pong"
end
end)
Klass.new.ping
Can you do anything with that?
It turns out that the block argument is the class you are creating. Run this snippet in irb
Class.new do |what|
p what
end
and you will see something like
#<Class:0x000000022b2698>
=> #<Class:0x000000022b2698>
The first line of output is given by p what, and the second line shows the return value of Class.new, which we know is the class. You can see that the what is the same object as the return value of Class.new.
Conclusion: the block argument is not very useful because you can get the class instance just using self in that class. The only usage I can imagine of is using the trick called flat scope to create methods.
Foo = Class.new do |klass|
define_method :class_name do
klass.name
end
end
Foo.new.class_name #=> "Foo"
Yet this is not very useful either because an object can easily access its class with self.class.
I'm trying to write a method that prints class variable names and their values. As an example:
class A
def printvars
???
end
end
class <<A
def varlist(*args)
???
end
end
class B < A
varlist :c
def initialize(c)
#c = c
end
b = B.new(10)
b.printvars()
And I would like the output to be c => 10. But I don't know what goes in the ???. I've tried using a self.class_eval in the body of varlist, but that won't let me store args. I've also tried keeping a hash in the class A and just printing it out in printvars, but the singleton class is a superclass of A and so has no access to this hash. So far everything I've tried doesn't work.
I think something similar must be possible, since Rails does something related with its validates_* methods. Ideally I could make this work exactly as expected, but even a pointer to how to print just the variable names (so just c as output) would be most appreciated.
You might like this answer: What is attr_accessor in Ruby?
Basically, as you surmised, varlist needs to be a class method which takes a variable list of arguments (*args). Once you have those arguments you could try any number of things using send, respond_to?, or maybe even instance_variable_get. Note, none of those are really recommended, but I wanted to answer your question a bit.
The other half is that you should probably look into method_missing in order to understand how things like validates_* are working. The * part necessitates that you do something like method_missing because you can't actually do module_eval until you know what you're looking for. In the case of the magic rails methods, you don't necessarily ever know what you're looking for! So we rely on the built in method_missing to let us know what got called.
For funzies, try this in IRB:
class A
def method_missing(method, *args, &block)
puts method, args.inspect
end
end
A.new.banana(13, 'snakes')
A.new.validates_serenity_of('Scooters', :within => [:calm, :uncalm])
Does that help?
Just use Module#class_variables
As far as I can tell, you're vastly over-complicating this. All you need is the pre-defined Module#class_variables method. You can call this directly on the class, or invoke it through self if you want to bind it to an instance of the class. For example:
class Foo
##bar = "baz"
def show_class_variables
self.class.class_variables
end
end
Foo.class_variables
#=> [:##bar]
foo = Foo.new
foo.show_class_variables
#=> [:##bar]
Seems I am confused between the two methods though I have been using them for a while, I can't understand why the method passengers is not being added to the object in the following code:
class Bus
def number_of_seats
42
end
end
Bus.class_eval do
define_method :number_of_windows do
number_of_seats
end
def fuel_type
:diesel
end
end
Bus.instance_eval do
define_method :destination do
'Paris'
end
def passengers
12
end
end
bus = Bus.new
bus.number_of_windows # => 42
bus.fuel_type # => :diesel
bus.destination # => "Paris"
bus.passengers # => undefined method `passengers' (NoMethodError)
Notes:
Tried instance_eval first, just randomly used class_eval and then it too seemed to work!
My understanding of instance_eval's block: The code in the block is run with self set to the object calling instance_eval.
My understanding of class_eval's block: The code in the block is evaluated as if its placed by opening the class of the object calling it. Hence I am puzzled at the class_eval in the above case! I was expecting class_eval on Bus would mean evaluating the block in the class of Bus Class.
You can refer this awesome article on class and instance_eval as to why passengers is not being added to the object.
TL;DR:
Bus.class_eval will create instance methods and Bus.instance_eval will create class methods.
Now, regarding the behavior of destination(which could be called on instance).....define method when used inside either class_eval or instance_eval is immune to the usual behaviour. Why?.
Because the documentation says so. As per the documentation:
define method - Defines an instance method in the receiver.
Therefore, it does not matter if you use define_method inside class_eval or instance_eval it would always create an instance method.
Source for reference.
Hope this helped :-).
Basically you can't do an instance_eval on a class object when using def. Here's a general way you use instance_eval and class_eval. http://web.stanford.edu/~ouster/cgi-bin/cs142-winter15/classEval.php
Is there a way to bind an existing method to an existing instance of an object if both the method and the instance are passed as symbols into a method that does that if the instance is not a symbol?
For example:
def some_method
#do something
end
some_instance = Klass.new(something)
def method_that_binds(:some_method, to: :some_instance)
#how do I do that?
end
Your requirements are a little unusual, but it is possible to do this mostly as you say:
class Person; end
harry = Person.new
barry = Person.new
def test
puts 'It works!'
end
define_method :method_that_binds do |a_method, to|
eval(to[:to].to_s).singleton_class.send(:define_method, a_method, &Object.new.method(a_method))
end
method_that_binds :test, to: :harry
harry.test
# It works! will be sent to STDOUT
barry.test
# undefined method 'test'
This doesn't actually use a named parameter, but accepts a hash with a to key, but you can see you can call it in the way you want. It also assumes that the methods you are defining are defined globally on Object.
The API you want doesn't easily work, because you have to know from which scope you want to access the local variable. It's not quite clear to me why you want to pass the name of the local variable instead of passing the content of the local variable … after all, the local variable is present at the call site.
Anyway, if you pass in the scope in addition to the name, this can be accomplished rather easily:
def some_method(*args)
puts args
puts "I can access some_instance's ivar: ##private_instance_var"
end
class Foo; def initialize; #private_instance_var = :foo end end
some_instance = Foo.new
def method_that_binds(meth, to:, within:, with: [])
self.class.instance_method(meth).bind(within.local_variable_get(to)).(*with)
end
method_that_binds(:some_method, to: :some_instance, within: binding, with: ['arg1', 'arg2'])
# arg1
# arg2
# I can access some_instance's ivar: foo
As you can see, I also added a way to pass arguments to the method. Without that extension, it becomes even simpler:
def method_that_binds(meth, to:, within:)
self.class.instance_method(meth).bind(within.local_variable_get(to)).()
end
But you have to pass the scope (Binding) into the method.
If you'd like to add a method just to some_instance i.e. it's not available on other instances of Klass then this can be done using define_singleton_method (documentation here.)
some_instance.define_singleton_method(:some_method, method(:some_method))
Here the first use of the symbol :some_method is the name you'd like the method to have on some_instance and the second use as a parameter to method is creating a Method object from your existing method.
If you'd like to use the same name as the existing method you could wrap this in your own method like:
def add_method(obj, name)
obj.define_singleton_method(name, method(name))
end
Let's say we have a class A with a method a and a local variable c.
class A
def a; 10 end
end
c = '5'
And we want to add the method A#a to c.
This is how it can be done
c.singleton_class.send :define_method, :b, &A.new.method(:a)
p c.b # => 10
Explanations.
One way to add a method to an object instance and not to its class is to define it in its singleton class (which every ruby object has).
We can get the c's singleton class by calling the corresponding method c.signleton_class.
Next we need to dynamically define a method in its class and this can usually be accomplished by using the define_method which takes a method name as its first argument (in our case :b) and a block. Now, converting the method into a block might look a bit tricky but the idea is relatively simple: we first transform the method into a Method instance by calling the Object#method and then by putting the & before A.new.method(:a) we tell the interpreter to call the to_proc method on our object (as our returned object is an instance of the Method, the Method#to_proc will be called) and after that the returned proc will be translated into a block that the define_method expects as its second argument.
I'm getting an error so I guess I have to reference a class method from inside of an instance method with self.class_method_name, but why is that?
Shouldn't it resolve this by itself? Confused.
def self.blah(string)
..
end
def some_method()
some_thing = blah("hello")
end
If you have
# This won't work
class Foo
def self.blah(string)
puts "self.blah called with a string of #{string}"
end
def some_method
# This won't work
self.blah("hello")
end
end
foo = Foo.new
foo.some_method
It won't work, because it'll look for the instance method Foo#blah. Instead, you're looking for Foo.bar.
To make some_method call Foo.bar, you have to make some_method refer to the Foo class, and then call blah on it.
class Foo
def self.blah(string)
puts "self.blah called with a string of #{string}"
end
def some_method
# This will work
self.class.blah("hello")
end
end
foo = Foo.new
foo.some_method
The reason you have def self.blah to define the method, but self.class.blah to call the method, is that in the former, self refers to the Foo class, while in the latter, self refers to the foo object, so you need self.class to refer to the Foo class.
It may be easier to think of self as part of the method name, that way it's clear that you never defined a blah method, you defined only a self.blah method. (To clarify: the previous sentence shouldn't be thought of too much, so please don't read into it, as it's not how things are actually working, just a sort of "layman's terms" attempt at describing why it doesn't work.)
Also, what if you had defined a blah instance method in addition to the class method? If calling blah was enough to access the class method, how would you call the instance method?
Finally, there really isn't any such thing as a class method in Ruby, "class methods" are really methods of the singleton class.