What's this &block in Ruby? And how does it get passed in a method here?

Viewed 49719

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
5 Answers

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.

The &block is a way of sending a piece of Ruby code in to a method and then evaluating that code in the scope of that method. In your example code above it means a partial named cart will be rendered in a div. I think the term closure is used for this in computer science.

So in your example the &block is:

<%= render(:partial => "cart", :object => @cart) %>

Some good reading and an explanation of blocks, procs and lamdas can be found at Robert Sosinski's blog.

Re attributes = {}, that's just a method argument with a default value. So if you call hidden_div_if(whatever), that is, passing only the first argument, attributes would default to an empty hash.

That's useful because it simplifies setting attributes["style"] later, as attributes doesn't have to be initialized to a hash first. (Which might nonetheless be done simply as (attributes ||= {})["style"] = ….)


&block is only slightly more complicated.

Ruby methods can take a last argument that is a block, using the special syntax method(args) { |block_args| block_code }. &block basically captures that block into the block variable as a Proc object. So block is just a variable pointing to an anonymous procedure here.

When later content_tag is called, and &block is passed as its last argument, it's expanded into a block, like if the call was indeed content_tag(…) { block originally passed to hidden_if_div }


So maybe I was really confusing here. What you should google for is "ruby default arguments" and "ruby blocks".

It works like this:

@cart.items.empty? is the codition

:id => "cart" Becomes attributes as per convention you can remove {} on a param hash if it is last one.

the block is

render(:partial => "cart", :object => @cart)

so within the function if the cart is empty it will add the attribute style with value "display: none"

Then it will create a div tag filled with the content of the result of executing the block which will be the result of rendering the partial view cart with the contents of @cart.

Related