Don't pass block when calling super - ruby

How can I set a block to nil for the super call?
class A
def foo
if block_given?
result = yield
# do stuff with the yield result
end
# some more code
end
end
class B < A
def foo
block_result = yield
# process block results and store it
# ...
super
end
end
B.new.foo { puts "block called" }
# => block called
# => block called
I don't want to yield the block twice. Is it somehow possible that block_given? in class A returns false?
Background is that I don't own the A class and I can't change it's foo method but I want to avoid calling my block twice. I also don't want to pass a dummy / empty block to super, because the behaviour of A's foo method changes when a block is given.

It's actually not that obvious. From the documentation we know that:
calling super passes all arguments
calling super() passes no arguments
What the documentation doesn't say is that this only applies to positional and keyword arguments. super() still passes a given block!
You have to explicitly unset the block by calling:
super(&nil)
How does this work?
You probably know that you can define a method with an explicit block argument:
def foo(&block)
# ...
end
and that you can pass it as a block to another method: (that other method might be super)
def foo(&block)
super(&block)
end
Now, if you pass a block when calling the method, the corresponding block variable will be an instance of Proc:
def foo(&block)
p block_given: block_given?, block: block
end
foo {}
#=> {:block_given=>true, :block=>#<Proc:0x00007ff4990d0030>}
If you call it without passing block, the block variable will just be nil:
foo
#=> {:block_given=>false, :block=>nil}
So if no block is given, block is nil and
super(&block)
becomes:
super(&nil)

Related

Evaluate instance variable in block passed to class_eval through wrapper

I want to extend the functionality of a method asdf at runtime.
A method append_asdf should allow modifying the method more easily.
I understand how to call the previous method but the private variable #b evaluates to nil inside the block used to specify the additional behaviour - if passed through the wrapper.
It works as expected when passed to class_eval directly (which is not what I want).
Why?
class A
def initialize
#b = "144"
end
def asdf
puts "12"
end
def self.append_asdf(&n)
m = instance_method(:asdf)
define_method(:asdf) {
m.bind(self).call
n.call
}
end
end
a = A.new
p = proc {
puts #b
}
The proc p is used here to drive home the point that it doesn't depend on the block. It behaves the same as a literal.
This doesn't work:
A.append_asdf(&p)
a.asdf
=>
12
Note the empty line. The same proc used here evaluates as expected:
A.class_eval {
define_method(:asdf, p)
}
a.asdf
=>
144
You need to evaluate that block in the proper context. In other words:
instance_eval(&n)
Instead of a regular call.

How to use define_method in an included module to create methods?

I want a class method, step, which dynamically creates instance methods.
step(:start) do |payload|
puts payload
end
This would create the equivalent of:
def start(payload)
payload
end
The trouble I am having is getting a block passed to the class method evaluate in the context of the instance.
The problem seems to be that the block, because it is a closure, is evaluated in the context of where is was defined, the class.
module Steps
def step(name, &block)
define_method(name) do |payload|
self # => Foo instance
block.call(payload)
end
end
end
class Foo
extend Steps
step(:start) do |payload|
self # => Foo class
payload # => 1
self.data # => undefined method `data' for Foo:Class (NoMethodError)
end
def data
'DATA'
end
end
puts Foo.new.start(1)
Is it possible to have the block evaluated in the context of the instance?
The closest I have come is using instance_eval however I can't figure out how to pass the argument payload to the block:
define_method(name) do |payload|
instance_eval(&block)
end
When I do the above self in the passed block is an instance of Foo so I can call data, but how to access payload?
instance_exec
Executes the given block within the context of the receiver (obj). In
order to set the context, the variable self is set to obj while the
code is executing, giving the code access to obj’s instance variables.
Arguments are passed as block parameters.
define_method(name) do |payload|
instance_exec(payload, &block)
end
ref: Change Block Binding Without Eval?

Why can a method definition without a block parameter accept a block?

Why can a method definition without a block parameter accept a block? This is the demo code:
def fun
yield
end
fun {puts 'hello ruby'}
Because that's how ruby works. Any method can be passed a block. It is responsibility of that method to check if block_given? and yield to it if needed.
This is implicit block passing. When you declare a block parameter, then something different happens: the block is converted to a Proc object, so that it can be called like a function and passed around as a parameter. You can't do that with implicit blocks (AFAIK).
def foo &block
block.call 3
bar block
end
# this method expects proc as a regular parameter (not a block), so you can pass
# a block in addition to it (if you so desire)
def bar block
block.call 4
end
foo do |x|
puts "this is #{x}"
end
# >> this is 3
# >> this is 4

Can I pass a block which itself expect a block to instance_exec in ruby?

I expect the code
foo=proc{puts "foo"}
instance_exec(1,2,3,&foo) do |*args , &block|
puts *args
block.call
puts "bar"
end
to output
1
2
3
foo
bar
But got the error
both block arg and actual block given
Can I pass a block which itself expect a block to instance_exec in ruby?
&foo tries to pass foo as a block to instance_exec, and you are already passing an explicit block. Omitting ampersand sends foo just like any other argument (except that it is a Proc instance). So, try this instead:
instance_exec(1,2,3,foo) do |*args, block|
puts *args
block.call
puts "bar"
end
This also means that you can do something like:
bar = proc{ |*args, block|
puts *args
block.call
puts "bar"
}
instance_exec(1,2,3,foo,&bar)
And get the same result.
More info at Difference between block and &block in Ruby
I'm about 3 years late to this party, but I thought I'd share an approach that let's you treat the inner block more like a real block, rather than just a plain old argument.
The best way I know of to go about this is to create an object to act as a binding context and define the outer block as a method. So if I rewrite the original example as follows without the instance_exec call...
inner_proc = proc { puts "inner" }
outer_proc = proc { |*args, &inner_block|
puts *args
inner_block.call
puts "bar"
}
We can define outer_proc as a method on an object
scope_object = Object.new
scope_object.define_singleton_method :bound_proc, &outer_proc
Now you can call scope_object.bound_proc instead of the instance_exec call above.
scope_object.bound_proc 1, 2, 3, &inner_proc
You'll get:
1
2
3
inner
bar
Unfortunately, you'll get a LocalJumpError if you try to yield inside of outer_proc, rather than the inner_block.call, I'm not entirely sure why. If someone has that answer then I'd be interested.

Difference between block and &block in Ruby

Why sometimes I should use block and other times &block inside functions that accept blocks?
block is just a local variable, &block is a reference to the block passed to the method.
def foo(block = nil)
p block
end
foo # => nil
foo("test") # => test
foo { puts "this block will not be called" } # => nil
def foo(&block)
p block
end
foo # => nil
foo("test") # => ArgumentError: wrong number of arguments (1 for 0)
foo { puts "This block won't get called, but you'll se it referenced as a proc." }
# => #<Proc:0x0000000100124ea8#untitled:20>
You can also use &block when calling methods to pass a proc as a block to a method, so that you can use procs just as you use blocks.
my_proc = proc {|i| i.upcase }
p ["foo", "bar", "baz"].map(&my_proc)
# => ["FOO", "BAR", "BAZ"]
p ["foo", "bar", "baz"].map(my_proc)
# => ArgumentError: wrong number of arguments (1 for 0)
The variable name block doesn't mean anything special. You can use &strawberries if you like, the ampersand is the key here.
You might find this article helpful.
In an argument list, &whatever takes the block that was passed to the method and wraps it in a Proc object. The Proc is stored in a variable called whatever (where that can be whatever name you typed after the ampersand, of course — usually it's "block"). After a method call, the &whatever syntax turns a Proc into a block. So if you define a method like so:
def thing(&block)
thing2 &block
end
You're defining a method that takes a block and then calls another method with that block.
If you don't set the & before block, Ruby won't recognize it's relationship to the "block" you pass to the function. Here some examples.
def f(x, block); end
f(3) { 2+2 } # gives an error, because "block" is a
# regular second argument (which is missing)
def g(x, &block); end
g(3) { 2+2 } # legal
def h(x); end
h(3) { 2+2 } # legal
For later use in a function:
def x(&block) # x is a 0 param function
y(block) # y is a 1 param function (taking one "Proc")
z(&block) # z is a 0 param function (like x) with the block x received
end
So, if you call z(&block) it's (nearly!!) the same as calling z { yield }: You just pass the block to the next function.

Resources