Ruby-on-rails – What’s this &block in Ruby? And how does it get passed in a method here

rubyruby-on-rails

Saw this piece of code in a Ruby on Rails book. This first one is from a view and the second one is a helper module. I don't understand how that &block and the attributes={} thing work. Can anyone guide me to a tutorial of some kind explaining this?

<% hidden_div_if(@cart.items.empty?, :id => "cart") do %>
 <%= render(:partial => "cart", :object => @cart) %>
<% end %>

module StoreHelper
 def hidden_div_if(condition, attributes = {}, &block)
  if condition
   attributes["style"] = "display: none"
  end
   content_tag("div", attributes, &block)
  end
end

Best Answer

Blocks are a fairly basic part of ruby. They're delimited by either do |arg0,arg1| ... end or { |arg0,arg1,arg2| ... }.

They allow you to specify a callback to pass to a method. This callback can be invoked two ways - either by capturing it by specifying a final argument prefixed with &, or by using the yield keyword:

irb> def meth_captures(arg, &block)
       block.call( arg, 0 ) + block.call( arg.reverse , 1 )
     end
#=> nil
irb> meth_captures('pony') do |word, num|
       puts "in callback! word = #{word.inspect}, num = #{num.inspect}"
       word + num.to_s
     end
in callback! word = "pony" num = 0
in callback! word = "ynop" num = 1
#=> "pony0ynop1" 
irb> def meth_yields(arg)
       yield(arg, 0) + yield(arg.upcase, 1)
     end
#=> nil
irb> meth_yields('frog') do |word, num|
       puts "in callback! word = #{word.inspect}, num = #{num.inspect}"
       word + num.to_s
     end
in callback! word = "frog", num = 0
in callback! word = "FROG", num = 1
#=> "frog0FROG1"

Note that our callback was the same in each case - we can remove repetition by saving our callback in an object, and then passing it to each method. This can be done using lambda to capture the callback in an object, and then passed to a method by prefixing it with &.

irb> callback = lambda do |word, num|
       puts "in callback! word = #{word.inspect}, num = #{num.inspect}"
       word + num.to_s
     end
#=> #<Proc:0x0052e3d8@(irb):22>
irb> meth_captures('unicorn', &callback)
in callback! word = "unicorn", num = 0
in callback! word = "nrocinu", num = 1
#=> "unicorn0nrocinu1"
irb> meth_yields('plate', &callback)
in callback! word = "plate", num = 0
in callback! word = "PLATE", num = 1
#=> "plate0PLATE1"

It's important to understand the different uses of & here as a prefix to the last argument of a function

  • in a function definition, it captures any passed block into that object
  • in a function call, it expands the given callback object into a block

If you look around blocks are used all over the place, especially in iterators, like Array#each.