I have the following code:
class A
def self.scope
yield
end
def self.method_added method
self.instance_eval %{
# do something involving the added method
}
end
end
class B < A
scope do
def foo
end
end
end
When the method_added hook is fired, will the code inside instance_eval run within the same scope as the method that was added? Or, will it run outside of it?
What are the caveats and gotchas involved within this?
Your scope method is basically a no-op. When you pass a block to a method that yields, the block is evaluated in the current scope. Observe:
class A
def self.scope
yield
end
end
A.scope { p self }
# main
Since nothing is yielded to the block, and nothing is done with the return value of yield, any code run in the block will have the same effect run outside the scope block.
This isn't the case with instance_eval, however. When instance_eval runs a block, self in the block is set to the receiver (rather than whatever self is in the block's scope). Like this:
class A
end
A.instance_eval { p self }
# A
But note that this means that self.instance_eval { ... } is also a fancy no-op, because you're changing the block's self to the same self outside the block.
So your code is equivalent to this:
class A
def self.method_added method
# do something involving the added method
end
end
class B < A
def foo
end
end
Let's find out!
class A
def self.scope
yield
end
def self.method_added method
puts "In method_added, method = #{method}, self = #{self}"
instance_eval 'puts "In instance_eval, method = #{method}, self = #{self}"'
end
end
class B < A
scope do
puts "In scope's block, self = #{self}"
def foo
end
end
end
# In scope's block, self = B
# In method_added, method = foo, self = B
# In instance_eval, method = foo, self = B
Notice that you don't need self. in self.instance_eval.
Related
When inheriting from Module you can pass a block to super() and it is executed:
class Foo < Module
def initialize
super() do
# do some other stuff...
end
end
end
Why would you use this over just calling super(), without the block, followed by the rest of the code, such as:
class Foo < Module
def initialize
super()
# do some other stuff...
end
end
An less contrived example would be:
class QuestionAttr < Module
def initialize(method_name)
super() do
define_method "#{method_name}?" do
!!public_send(method_name)
end
end
end
end
Fruit = Struct.new(:colour) do
include QuestionAttr.new(:colour)
end
fruit = Fruit.new('red')
fruit.colour? # => true
Why would I define_method inside the block passed to super(), as per the above example, instead of omitting the block passed to super() and define_method afterwards.
I found this neat delegator based 'tee' implementation on SO:
https://stackoverflow.com/a/6410202/2379703
And I'm curious what is means for #targets (instance variable) means in the context of a class method:
require 'logger'
class MultiDelegator
def initialize(*targets)
#targets = targets
end
def self.delegate(*methods)
methods.each do |m|
define_method(m) do |*args|
#targets.map { |t| t.send(m, *args) }
end
end
self
end
class <<self
alias to new
end
end
log_file = File.open("debug.log", "a")
log = Logger.new MultiDelegator.delegate(:write, :close).to(STDOUT, log_file)
I get that it defining the methods write/close but #targets isn't even defined at this point since .to (aliased to new) has yet to be called so I'd assume #targets is nil.
Can anyone give an explanation as to the logistics of how this code works? Does ruby not even attempt to access/resolve #targets until the method in question is attempted to be called, which would be by the logger after it was instantiated?
The define_method method is called on a class to create an instance method. Inside that method, the self (and the instance variable) are instances of the class.
For example:
class Foo
#bar = "CLASS"
def initialize
#bar = "INSTANCE"
end
def self.make_method
define_method :whee do
p #bar
end
end
end
begin
Foo.new.whee
rescue NoMethodError=>e
puts e
end
#=> undefined method `whee' for #<Foo:0x007fc0719794b8 #bar="INSTANCE">
Foo.make_method
Foo.new.whee
#=> "INSTANCE"
It is correct that you can ask about instance variables that have never been created, at any time:
class Bar
def who_dat
puts "#dat is #{#dat.inspect}"
end
end
Bar.new.who_dat
#=> dat is nil
The same is true of other aspects of the language. As long as the code in the method is syntactically valid, it may be defined, even if invoking it causes a runtime error:
class Jim
def say_stuff
stuff!
end
end
puts "Good so far!"
#=> Good so far!
j = Jim.new
begin
j.say_stuff
rescue Exception=>e
puts e
end
#=> undefined method `stuff!' for #<Jim:0x007f9c498852d8>
# Let's add the method now, by re-opening the class
class Jim # this is not a new class
def stuff!
puts "Hello, World!"
end
end
j.say_stuff
#=> "Hello, World!"
In the above I define a say_stuff method that is syntactically valid, but that calls a method that does not exist. This is find. The method is created, but not invoked.
Then I try to invoke the method, and it causes an error (which we catch and handle cleanly).
Then I add the stuff! method to the class. Now I can run the say_stuff method (on the same instance as before!) and it works just fine.
This last example shows how defining a method does not run it, or require that it would even work when it is run. It is dynamically evaluated each time it is invoked (and only at that time).
instance_eval method change self in its block, eg:
class D; end
d = D.new
d.instance_eval do
puts self # print something like #<D:0x8a6d9f4>, not 'main'!
end
If we define a method ourself(or any other methods(other than instance_eval) which takes a block), when print self, we will get 'main', which is different from instance_eval method.eg:
[1].each do |e|
puts self # print 'main'
end
How can i define a method(which takes a block) like instance_eval?
Thanks in advance.
You can write a method that accepts a proc argument, and then pass that as a proc argument to instance_eval.
class Foo
def bar(&b)
# Do something here first.
instance_eval &b
# Do something else here afterward, call it again, etc.
end
end
Foo.new.bar { puts self }
Yields
#<Foo:0x100329f00>
It's obvious:
class Object
def your_method(*args, &block)
instance_eval &block
end
end
receiver = Object.new
receiver.your_method do
puts self #=> it will print the self of receiver
end
If a block is a closure, why does this code does not work, and how to make it work?
def R(arg)
Class.new do
def foo
puts arg
end
end
end
class A < R("Hello!")
end
A.new.foo #throws undefined local variable or method `arg' for #<A:0x2840538>
Blocks are closures and arg is indeed available inside the Class.new block. It's just not available inside the foo method because def starts a new scope. If you replace def with define_method, which takes a block, you'll see the result you want:
def R(arg)
Class.new do
define_method(:foo) do
puts arg
end
end
end
class A < R("Hello!")
end
A.new.foo # Prints: Hello!
If you define the class dynamically, you can alter it as you like:
def R(arg)
c = Class.new
# Send the block through as a closure, not as an inline method
# definition which interprets variables always as local to the block.
c.send(:define_method, :foo) do
arg
end
c
end
class A < R("Hello!")
end
puts A.new.foo.inspect
# => "Hello!"
Is there any difference if you define Foo with instance_eval: . . .
class Foo
def initialize(&block)
instance_eval(&block) if block_given?
end
end
. . . or with 'yield self':
class Foo
def initialize
yield self if block_given?
end
end
In either case you can do this:
x = Foo.new { def foo; 'foo'; end }
x.foo
So 'yield self' means that the block after Foo.new is always evaluated in the context of the Foo class.
Is this correct?
Your two pieces of code do very different things. By using instance_eval you're evaluating the block in the context of your object. This means that using def will define methods on that object. It also means that calling a method without a receiver inside the block will call it on your object.
When yielding self you're passing self as an argument to the block, but since your block doesn't take any arguments, it is simply ignored. So in this case yielding self does the same thing as yielding nothing. The def here behaves exactly like a def outside the block would, yielding self does not actually change what you define the method on. What you could do is:
class Foo
def initialize
yield self if block_given?
end
end
x = Foo.new {|obj| def obj.foo() 'foo' end}
x.foo
The difference to instance_eval being that you have to specify the receiver explicitly.
Edit to clarify:
In the version with yield, obj in the block will be the object that is yielded, which in this case is is the newly created Foo instance. While self will have the same value it had outside the block. With the instance_eval version self inside the block will be the newly created Foo instance.
They are different. yield(self) does not change the value of self inside the block, while instance_eval(&block) does.
class Foo
def with_yield
yield(self)
end
def with_instance_eval(&block)
instance_eval(&block)
end
end
f = Foo.new
f.with_yield do |arg|
p self
# => main
p arg
# => #<Foo:0x100124b10>
end
f.with_instance_eval do |arg|
p self
# => #<Foo:0x100124b10>
p arg
# => #<Foo:0x100124b10>
end
You just can drop the self keyword
class Foo
def initialize
yield if block_given?
end
end
Update from comments
Using yield there is a bit new to my taste, specially when used outside irb.
However there is a big and significant difference between instance_eval approach and yield approach, check this snippet:
class Foo
def initialize(&block)
instance_eval(&block) if block_given?
end
end
x = Foo.new { def foo; 'foo'; end }
#=> #<Foo:0xb800f6a0>
x.foo #=> "foo"
z = Foo.new #=> #<Foo:0xb800806c>
z.foo #=>NoMethodError: undefined method `foo' for #<Foo:0xb800806c>
Check this one as well:
class Foo2
def initialize
yield if block_given?
end
end
x = Foo2.new { def foo; 'foo'; end } #=> #<Foo:0xb7ff1bb4>
x.foo #=> private method `foo' called for #<Foo2:0xb8004930> (NoMethodError)
x.send :foo => "foo"
z = Foo.new #=> #<Foo:0xb800806c>
z.send :foo => "foo"
As you can see the difference is that the former one is adding a singleton method foo to the object being initialized, while the later is adding a private method to all instances of Object class.