I have a set of contexts with given before clauses that I'd like to wrap around various examples. Here's my attempt:
# The "multi-context" wrapper
def with_foo_and_bar(&block)
before { p 'hello world' }
context 'foo' do
before { p 'baz' }
yield
end
context 'bar' do
before { p 'qux' }
yield
end
end
# The example
describe do
with_foo_and_bar do
it 'prints some stuff' do
# Example runs twice, but only 'hello world' is printed
end
end
end
In this spec, I would expect all before clauses to run and print out "baz" and "qux" once each and "hello world" twice, but only "hello world" is printed (twice, as expected). I feel like there's some problem with the yield ignoring the before blocks, but I'm not sure how to tweak the code to get what I want. I'd appreciate any suggestions!
I found the answer hinted at here:
https://www.jorgemanrubia.com/2010/01/16/using-macros-to-create-custom-example-groups-in-rspec/
The solution is to do the following:
def with_foo_and_bar(&block)
foo = context 'foo' do
before { p 'baz' }
end
foo.class_eval &block
bar = context 'bar' do
before { p 'qux' }
end
bar.class_eval &block
end
describe do
with_foo_and_bar do
it 'prints some stuff' do
# Prints 'baz' once and 'qux' once
end
end
end
Related
I was wondering how to pass a block to a method which will make the method return on yield.
The naive aproach doesn't work:
def run(&block)
block.call
end
run { return :foo } # => LocalJumpError
Wrapping in another proc has the same effect:
def run(&block)
proc { block.call }.call
end
run { return :bar } # => LocalJumpError
So I thought that the return statement is bound to the receiver of the current binding. However, trying it out with instance_eval proved me wrong:
class ProcTest
def run(&block)
puts "run: #{[binding.local_variables, binding.receiver]}"
instance_eval(&block)
end
end
pt = ProcTest.new
binding_inspector = proc { puts "proc: #{[binding.local_variables, binding.receiver]}" }
puts "main: #{[binding.local_variables, binding.receiver]}"
# => main: [[:pt, :binding_inspector], main]
binding_inspector.call
# => proc: [[:pt, :binding_inspector], main]
pt.run(&binding_inspector)
# => run: [[:block], #<ProcTest:0x007f4987b06508>]
# => proc: [[:pt, :binding_inspector], #<ProcTest:0x007f4987b06508>]
pt.run { return :baz }
# => run: [[:block], #<ProcTest:0x007f4987b06508>]
# => LocalJumpError
So the questions are:
How can this be done?
How is the return context tied to the return statement. Is this connection accessible via the language's API?
Was this implemented in such manner intentionally? If yes - why? If no - what are the obstacles to fix it?
I thought that the return statement is bound to the receiver of the current binding.
Only methods have an receiver. return is not a method:
defined? return #=> "expression"
Trying to invoke it as a method doesn't work:
def foo
send(:return, 123)
end
foo #=> undefined method `return'
trying it out with instance_eval proved me wrong
Though instance_eval evaluates the block in the context of the receiver (so you have access to the receivers instance methods and instance variables):
class MyClass
def foo(&block)
#var = 123
instance_eval(&block)
end
end
MyClass.new.foo { instance_variables }
#=> [:#var]
... it does not evaluate the block in the current binding (so you don't have access to any local variables):
class MyClass
def foo(&block)
var = 123
instance_eval(&block)
end
end
MyClass.new.foo { local_variables }
#=> []
How can this be done?
You could use eval, but that requires a string:
def foo
var = 123
eval yield
nil
end
foo { "return var * 2" }
#=> 246
Or by passing the binding to the block (again using eval):
def foo
var = 123
yield binding
nil
end
foo { |b| b.eval "return var * 2" }
#=> 246
return in a block returns from the enclosing method when the block is defined (ie, the closure in which the block is created). In your example, there is no enclosing block to return from, hence your exception.
This is easily demonstrated:
def foo(&block)
puts yield
puts "we won't get here"
end
def bar
foo { return "hi from the block"; puts "we never get here" }
puts "we never get here either"
end
puts bar # => "hi from the block" (only printed once; the puts in `foo` is not executed)
Return in a proc will immediately return out of the proc, not out of the method on the stack under the proc:
def foo(&block)
puts yield
puts "we will get here"
end
def bar
foo &->{ return "hi from the proc"; puts "we never get here" }
puts "we will get here too"
end
puts bar
# hi from the proc # puts from foo
# we will get here # puts from foo
# we will get here too # puts from bar
Because of these behaviors, there is no way to achieve your desired behavior, in which a return in the given block will execute a return in the method from which the block is invoked, unless the block was defined within that scope, since doing so would require one of the existing behaviors not work.
You could achieve something like this with throw...catch, which is kinda-sorta useful as a way to zip up the stack from an arbitrary depth, but you can't return arbitrary values with it:
def foo(&block)
yield
puts "we won't get here"
end
catch(:escape) do
foo &->{ throw :escape }
end
I am not sure if this is possible in Ruby, but in case someone knows a good solution.
I'd like to change the structure of a block, replacing particular nodes in it with other code structures. Much like macros.
For example, say I have an unevaluated code block
some_method do
foo
bar
end
Then I define some_method like
def some_method(&block)
...
end
In some_method, I really would like to replace "bar" in block with something else, e.g. with baz.
I want to do the replacement w/o evaluating the block, because ultimately I am passing the block around to other places.
Doable? or no?
I can think of fairly complicated answers: e.g. I can pass the block around with an additional closure that defines replacement for bar, and uses method_missing and continuation to replace bar with baz when bar is evaluated. But is there a simpler way?
Thanks.
Ruby doesn't have dynamic scoping and doesn't have macros, so unless you wrap the block in a function taking bar as a parameter, and pass that function around, I don't think you can substitute code like that. You can use eval of course, but I wouldn't recommend it=)
This is the simplest way i can think of:
class Base
def some_method(&block)
self.instance_eval(&block)
end
def foo; puts 'foo'; end
def bar; puts 'bar'; end
end
class Replacement < Base
def foo; puts 'baz'; end
end
Base.new.some_method do
foo
bar
end
Replacement.new.some_method do
foo
bar
end
output:
foo
bar
baz
bar
Do aliases and Procs help?
def foo; p 'foo'; end
def bar; p 'bar'; end
def sm(&block)
##Block = Proc.new {
alias :oldbar :bar
def bar; p 'baz'; end #redefine
block.call
alias :bar :oldbar #restore
}
yield #prints foo,bar
end
sm do
foo
bar
end
def later(&block)
yield
end
def delayedEx
later { ##Block.call}
end
delayedEx #prints foo,baz
bar #prints bar (unchanged)
This prints "foo bar foo baz bar", i.e: bar does something different in the block, but retains its original behavior outside.
def some_method(a_proc=Proc.new{puts "Bar"}, &block)
a_proc.call
yield
end
p1 = Proc.new{puts "Baz"}
some_method{puts "a block"}
some_method(p1){puts "a block"}
How do I pass the parameter name in the following case..the name is being is evaluated before being passed to class_eval
class Foo
end
Foo.class_eval %Q{
def hello(name)
p "hello #{name}"
end
}
Sorry about not giving the entire scenario...
I just wanted to add a instance method dynamically to a class and that method should be able to take arguments...
the above code would not compile complaining that the name is not defined as local variable when executing in irb..
Thanks
The other answers are the "right" answer, but you could also just skip interpolating inside the p call:
Foo.class_eval %Q{
def hello(name)
p "hello \#{name}"
end
}
I thought you wanted to change the actual parameter name (possibly useful for completion or when using Pry on dynamic methods), here assuming it's in a global, but could also be passed into a method doing the class_eval:
Foo.class_eval %Q{
def hello(#{$argname})
p "hello \#{$argname}"
end
}
Really simple:
Foo.class_eval do
def hello(name)
p "hello #{name}"
end
end
Try passing a block to class_eval instead of an array (from this link):
class Foo
end
Foo.class_eval {
def hello(name)
p "hello #{name}"
end
}
You then can call the instance method hello in the usual fashion:
boo = Foo.new
boo.hello("you")
which produces:
>> boo.hello("you")
"hello you"
=> nil
class Foo
end
Foo.class_eval do
define_method :hello do |name|
p "hello #{name}"
end
end
Foo.new.hello("coool") # => "hello coool"
Is there any way to make methods and functions only available inside blocks? What I'm trying to do:
some_block do
available_only_in_block
is_this_here?
okay_cool
end
But the is_this_here?, okay_cool, etc. only being accessible inside that block, not outside it. Got any ideas?
Pass an object with the methods that you want to be available into the block as an argument. This is a pattern that's widely used in Ruby, such as in IO.open or XML builder.
some_block do |thing|
thing.available_only_in_block
thing.is_this_here?
thing.okay_cool
end
Note that you can get closer to what you asked for using instance_eval or instance_exec, but that's generally a bad idea as it can have fairly surprising consequences.
class Foo
def bar
"Hello"
end
end
def with_foo &block
Foo.new.instance_exec &block
end
with_foo { bar } #=> "Hello"
bar = 10
with_foo { bar } #=> 10
with_foo { self.bar } #=> "Hello
While if you pass an argument in, you always know what you'll be referring to:
def with_foo
yield Foo.new
end
with_foo { |x| x.bar } #=> "Hello"
bar = 10
x = 20
with_foo { |x| x.bar } #=> "Hello"
I'm just learning ruby and trying to understand the scope of code executed in blocks. For example, I want to be able to create a block that affects the method that it is attached to, like so:
def test(&block)
block.call() if block_given?
puts "in test, foo is #{foo}"
puts "in test, bar is #{bar}"
end
test() {
foo="this is foo"
bar="this is bar"
}
In this case I don't want to have to modify the block at all -- I want to be able to write it using simple variable references and no parameters. Only by making changes to the 'test' method in the above example, is it possible to access the variables defined in the block?
Again, the goal is to leave the block unmodified, but be able to access the created variables from within 'test' after the block executes.
First of all, block.call() is done with yield, and you don't need the &block parameter that way.
You can't normally do what you want, blocks are bound when they are created, and inside the block you can see the local variables defined at that moment; the easiest way to do what you want, which is not how you will use blocks normally, is this:
def test()
foo = yield if block_given?
puts "in test, foo is #{foo}"
end
test() {
foo="this is foo"
}
But that's only a side effect because foo is "returned" by the block. If you instead do this:
def test()
foo = yield if block_given?
puts "in test, foo is #{foo}"
end
test() {
foo="this is foo"
"ha ha, no foo for you"
}
You'll notice that it does something different.
Here's more magic:
def test(&block)
foo = eval "foo", block.binding
puts foo
block.call
foo = eval "foo", block.binding
puts foo
end
foo = "before test"
test() {
foo = "after test"
"ha ha, no foo for you"
}
That would kind of work, but it breaks if you remove foo = "before test" because foo becomes a local variable in the block and does not exist in the binding.
Summary: you can't access local variables from a block, just the locals where the block was defined and the return value of the block.
Even this won't work:
def test(&block)
eval "foo = 'go fish'", block.binding
block.call
bar = eval "foo", block.binding
puts bar
end
because the foo in the binding is different from the local in the block (I didn't know this, thanks).
No, a block can't affect local variables in the place where it's called.
Blocks in Ruby are closures, which means they capture the scope around them when they are created. The variables that are visible when you create the block are the ones it sees. If there were a foo and bar at the top of your code, outside any method, that block would change those when it was called.
You can do what you want by being a little more verbose:
class Test
def foo(t)
#foo = t
end
def bar(t)
#bar = t
end
def test(&block)
self.instance_eval &block if block_given?
puts "in test, foo is #{#foo}"
puts "in test, bar is #{#bar}"
end
end
Test.new.test() {
foo "this is foo"
bar "this is bar"
}
You can create methods like attr_accessor that will generate apropriate setter (the foo and bar methods).
def test(&block)
foo = yield
puts "in test, foo is #{foo}"
end
test { "this is foo" }
prints in test, foo is this is foo
The value of yield is the value of the block.
You can also pass parameters to yield, which then can be accessed by the block using |param,another| at the block's beginning.
Also, check out procs.
foo = "this is foo"
p = Proc.new { "foo is #{foo}" }
p.call
Prints "foo is this is foo"
def test(p)
p.call
end
test p
Prints "foo is this is foo"
def test2(p)
foo = "monkey"
p.call
end
test2 p
Prints "foo is this is foo"