views:

1293

answers:

6

Saw this piece of code in a RoR 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
+4  A: 

The &block is a way of sending a piece of Ruby code in to a method and then evaluate 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 Sosinskis blog.

ba
Yep, blocks in Ruby are just closures. For a good explanation of this, see http://reprog.wordpress.com/2010/02/27/closures-finally-explained/ .
Xiong Chiamiov
+3  A: 

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".

kch
+1  A: 

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.

Miquel
+6  A: 

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.

rampion
+2  A: 

Ruby implements Blocks, Procs and lambdas which are referred to as closures in the computer science community. If you beginning to learn Ruby you will quickly run into code that looks like this.

a = ["dog", "cat", "bird"]
a.alter_each! do |n, i|
  "#{i}_#{n}"
end

So whats going on here?

We start off with an array of animal names and call the alter_each! method passing a block. In this block of code we can specify how we want to alter each item. Our example will prefix each animal name with it's position in the array. As the alter_each! method iterates through each item it will execute our block passing the value and index. Our block captures these params, prefixes the index to the name and returns the result. Now lets look at the alter_each! method.

Notice the method doesn't specify any params, that's because a block is automatically assigned to yield keyword. yield is called like a function passing in the value and index of each item in the array and overriding the original value.

class Array
  def alter_each!
    self.each_with_index do |n, i|
      self[i] = yield(n,i)
    end
  end
end

What if you need to pass a param to this method?

You can modify the method signature to accept params and finally catch the block with a param starting with an ampersand. In the example below our block will be captured with the &block param which we will invoke the call method. This is in place of using yield

class Array
  def modify_each!(add_one = true, &block)
    self.each_with_index do |n, i|
      j = (add_one) ? (i + 1) : i
      self[i] = block.call(n,j)
    end
  end
end

Full article on ruby blocks

jspooner
+2  A: 

Blocks, Procs and lambdas (referred to as closures in Computer Science) are one of the most powerful aspects of Ruby, and also one of the most misunderstood. This is probably because Ruby handles closures in a rather unique way. Making things more complicated is that Ruby has four different ways of using closures, each of which is a tad bit different, and sometimes nonsensical. There are quite a few sites with some very good information about how closures work within Ruby. But I have yet to find a good, definitive guide out there.

class Array
  def iterate!(&code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end

array = [1, 2, 3, 4]

array.iterate! do |n|
  n ** 2
end

Procedures, AKA, Procs

Blocks are very handy and syntactically simple, however we may want to have many different blocks at our disposal and use them multiple times. As such, passing the same block again and again would require us to repeat ourself. However, as Ruby is fully object-oriented, this can be handled quite cleanly by saving reusable code as an object itself. This reusable code is called a Proc (short for procedure). The only difference between blocks and Procs is that a block is a Proc that cannot be saved, and as such, is a one time use solution. By working with Procs, we can start doing the following:

class Array
  def iterate!(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end

array_1 = [1, 2, 3, 4]
array_2 = [2, 3, 4, 5]

square = Proc.new do |n|
  n ** 2
end

Lambdas

So far, you have used Procs in two ways, passing them directly as an attribute and saving them as a variable. These Procs act very similar to what other languages call anonymous functions, or lambdas. To make things more interesting, lambdas are available within Ruby too. Take a look:

class Array
  def iterate!(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end

array = [1, 2, 3, 4]

array.iterate!(lambda { |n| n ** 2 })

puts array.inspect

Blocks

The most common, easiest and arguably most “Ruby like” way to use closures in Ruby is with blocks. They have the following familiar syntax:

array = [1, 2, 3, 4]

array.collect! do |n|
  n ** 2
end

puts array.inspect

# => [1, 4, 9, 16]
Dinesh Atoliya