08 Jun, 2007

Published at 04:21AM

Tagged with programming, rails, and views

This post has 5 comments

Help writing better block helpers

I basically use block helpers to avoid writing out conditions in my views. Sometimes I’d also like to avoid looping statements, but I don’t know how. For instance, in the case of displaying a table and its multiple rows. Currently, I might do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# helper
def table_rows_for(contents, &block)
  unless contents.blank?
    yield
  else
    text = "Currently no contents for this view."
    tr   = content_tag(:tr, content_tag(:td, text, :colspan => '3'))
    concat(tr, block.binding)
  end
end

# view
<table>
  <tr>
    <th>Title</th><th>Changed By</th><th>Changed When</th>
  </tr>
  <% table_rows_for somethings do %>
    <% somethings.each do |something| %>
      <!-- more ERB stuff -->
    <% end -%>
  <% end -%>
</table>

I would like to include the loop inside of the helper as well, since the contents are being passed, anyway. However, everything I’ve tried results in nil object errors. In a standard loop, the methods are qualified with an object variable (“something” in the example above), and that’s a lot of what I don’t know how to handle. I’d like to just pass the contents to a table_for helper, instead of a table_rows_for helper. I could do that by avoiding the block helper altogether, and construct the html manually in a regular helper, but I still want the html block visible in my view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def table_for(contents, &block)
  unless contents.blank?
    start = "<table><tr><th>Title</th><th>Changed by</th><th>Changed when</th></tr>"
    concat(start, block.binding)
    contents.each do
      # don't know what to do here
      concat(capture(&block), block.binding)
    end
    concat('</table>', block.binding)
  else
    text = "Currently no contents for this view."
    tr   = content_tag(:tr, content_tag(:td, text, :colspan => '3'))
    concat(tr, block.binding)
  end
end

That’s probably not even close, but inside of the contents loop is what I’m having trouble with. Concatenating the captured block seems logical to me. But even if it was right, I don’t know what to do in the ERB (the block I’d be passing). I can no longer do something.title because something now belongs to the helper. If anyone can decipher my issue, please feel free to enlighten me.

Comments

fallenrogue Saturday, 09 Jun, 2007 Posted at 06:33AM

I’m not entirely sure exactly what you’re trying to accomplish here but you can certainly pass a block to another block and have it execute there. It’s one of the magic points of Ruby. I usually provide instance variables when I’m going to forward them around in Rails (so @something instead of something) and by calling “yield” in the

1
2
3
contents.each do
  yield
end

block, the yield should execute the next block… so if you wanted to do this…

1
2
3
4
5
6
7
<% outer_pass do %>
  <%inner_pass @boxes do %>
    <% @boxes.each do |box| %>
      <%= box.name %><br />
    <% end%>
  <% end %>
<% end%>

the helper might look like this…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module BoxesHelper
  def outer_pass( &block)
    concat "outer top <br />", block.binding
    yield
    concat "<br />outer bottom <br />", block.binding
  end

  def inner_pass(collection,&block)
    concat "inner top <br />", block.binding
    collection.each do |box| 
      yield
    end
    concat "<br />inner bottom <br />", block.binding
  end
end

Am I way off on what you were looking to do? Maybe you’ve already gone in another direction. Either way, good luck solving the problem!

Ryan Saturday, 09 Jun, 2007 Posted at 10:31PM

It’s close, but not quite. What you’ve explained is how I’m currently doing it. I’ll use your example to help explain…

OK… my problem lies within the inner_pass looping in the view code you’ve posted above. Ignore the outer_pass part, for now (altogether, actually). You’re passing @boxes to the inner_pass method, along with a block, which is what I’m doing. Now, instead of doing @boxes.each in the view, I wanted to do that in the helper, since @boxes was already being passed in, anyway. Essentially leaving me with:

1
2
3
<% inner_pass @boxes do %>
  <%= box.name %><br />
<% end -%>

I was trying to extract the looping to within the helper, basically. I’ve never tried passing a parameter back to the block via yield, but I think it can be done… something like…

1
2
3
4
5
def inner_pass(collection, &block)
  collection.each do |box|
    yield box
  end
end

I’ve actually never tried this—think it would work? Anyway, I won’t be working on this again until Monday, so we’ll see how it goes then. Thanks for the help…

fallenrogue Monday, 11 Jun, 2007 Posted at 08:08AM

OH!!!! Dude, you’re so close then… just establish that you want to accept a param back! Try this…

1
2
3
4
5
6
7
8
9
10
11
#helper
def inner_pass(collection, &block)
  collection.each do |box|
    yield box.name
  end  
end

#view
<% inner_pass @boxes do |pass_to_me|%>
  <%= pass_to_me %><br />
<% end %>

I believe that’s what you mean, right? Sorry for my confusion! :)

Ryan Monday, 11 Jun, 2007 Posted at 10:23AM

Works perfectly. I think the part that I was missing was the |pass_to_me| param going back into the block. It’s hard to imagine me not trying that, though. I guess I had the wrong combination of attempts. Anyway, I have a bunch of fields I need, so I’m just returning the object as a param and using it as I was originally. Thanks again for the help.

fallenrogue Monday, 11 Jun, 2007 Posted at 12:43PM

no problem, man. Good luck with the rest of the app!

Do you have something to say about this post?
Retype the image to the right Spam Hint: Are You Human? Textile Formatting Tips

or

Ryan Heath | Site Management A Ruby on Rails production.

This site is a Formed Function. Formed Function LLC | @formedfunction | Get in Touch