Passing Block into a Method - Ruby

Pass block passed to method to another method in Ruby

You can reference the block explicitly

def discard(&block)
self - self.keep(&block)
end

or implicitly

def discard
self - self.keep(&Proc.new {})
end

In your case, I would suggest the first approach.

Passing block from one method to another

Short answer: Always use yield, unless you have a good reason to explicitly reference &block.

See: Why blocks make ruby methods 439% slower

With &block, you get a reified Proc on which you can do all kinds of stuff and which you can move around. However, with a yield and an implicit block, you are limited to only calling the block.

By using yield, the interpreter can bypass all the Proc reification as it knows the developer won't be able to use it; hence it can keep just a C-level structure instead of having to set up a Ruby-level object.

How to pass a block to another in Ruby?

You can't use yield in a. Rather, you have to pass a Proc object. This would be the new code:

def do_something(a,&b)
AnotherClass.instance_exec(b, &a)
end

a = Proc.new do |b|
puts "start"
b.call
puts "end"
end

do_something(a) do
puts "this block is b!"
end

yield is only for methods. In this new code, I used instance_exec (new in Ruby 1.9) which allows you to pass parameters to the block. Because of that, we can pass the Proc object b as a parameter to a, which can call it with Proc#call().

Passing block into a method - Ruby

The difference between do .. end and curly braces is that the curly braces bind to the rightmost expression, while do .. end bind to the leftmost one. Observe the following examples:

def first(x=nil)
puts " first(#{x.inspect}): #{block_given? ? "GOT BLOCK" : "no block"}"
"f"
end

def second(x=nil)
puts " second(#{x.inspect}): #{block_given? ? "GOT BLOCK" : "no block"}"
"s"
end

first second do |x| :ok end # second(nil): no block
# first("s"): GOT BLOCK

first second {|x| :ok } # second(nil): GOT BLOCK
# first("s"): no block

In the first case, the block made with do..end will be bound to the first function (leftmost). In the second case the block made with curly brackets will be bound to the second function (rightmost).

Usually it's good idea to use parentheses if you have two functions and a block - just for readability and to avoid mistakes.

It's very easy to accidentally pass a block to puts method, just as in your question.

Why can't we pass a {} block with other parameters to a method in Ruby

You can only pass other objects as arguments, and blocks are not objects in Ruby. Blocks are syntactic constructs.

But they can easily be wrapped in objects, objects of Proc class. There is even a special shorthand operator for that:

method4(true, 4, &->{puts 'Hello World'})

A short explanation of above code:

->{} creates a Proc object around given block. As method4 requires a block, and not an object, you need to "unwrap" it to block once more, and that's why ampersand is there. If the signature of method4 were instead:

def method4(condition, attribute, proc) # note no ampersand

you would be able to omit the ampersand in method call too.

Ruby - Passing Blocks To Methods

The code by David will work fine, but this is an easier and shorter solution:

foo = Proc.new { |prompt| prompt.echo = false }
new_pass = ask("Enter your new password: ", &foo)
verify_pass = ask("Enter again to verify: ", &foo)

You can also use an ampersand to assign a block to a variable when defining a method:

def ask(msg, &block)
puts block.inspect
end

Is there a way to pass a block into Class::new as an argument?

As both the documentation of Class::new and the error message clearly show, the block is passed one argument [bold emphasis mine]:

new(super_class=Object) { |mod| ... }a_class

[…]

If a block is given, it is passed the class object, and the block is evaluated in the context of this class like class_eval.

Also, in the error message you posted:

ArgumentError (wrong number of arguments (given 1, expected 0))

The error message is saying that your lambda was passed one argument, but it is expecting none.

The fix is easy: add a parameter to your lambda:

class_body = -> * do
def foo
'bar'
end
end

klass = Class.new(&class_body)

Or, if you don't absolutely require a lambda, you could use a non-lambda Proc:

class_body = proc do
def foo
'bar'
end
end

klass = Class.new(&class_body)

Passing a block to a dynamically created method

If I understood you correctly, you can solve this by saving passed block to an instance variable on class object and then evaling that in instance methods.

bl.call won't do here, because it will execute in the original context (that of a class) and you need to execute it in scope of this current instance.

module MyMod
def is_my_modiable(&block)
class_eval do
@stored_block = block # back up block
def new_method
bl = self.class.instance_variable_get(:@stored_block) # get from class and execute
instance_eval(&bl) if bl
self.mod = true
self.save!
end
end
end
end

class MyClass
extend MyMod

is_my_modiable do
puts "in my modiable block"
self.something_special
end

def something_special
puts "in something special"
end

attr_accessor :mod
def save!; end
end

MyClass.new.new_method
# >> in my modiable block
# >> in something special


Related Topics



Leave a reply



Submit