Reputation: 13720
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
Upvotes: 64
Views: 55259
Reputation: 3797
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.
Upvotes: 14
Reputation: 11315
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
Upvotes: 4
Reputation: 89043
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
If you look around blocks are used all over the place, especially in iterators, like Array#each
.
Upvotes: 110
Reputation: 4829
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.
Upvotes: 1
Reputation: 79512
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".
Upvotes: 4