0

Say I have a block like this:

<% @help_sections.each do |section| %>
    <li><%= section.name %></li>
<% end %>

But on the last record returned, I want to do something else, e.g. applying a class to the li that's there:

<li class="last"><%= section.name %></li>

How do I do that in the most DRY way?

Thanks.

Edit1:

I imagine I would simply use an if statement and the last ruby method, but not sure how to do that within the block? I know that if I just wanted the last element in that array, I could just do @help_sections.last, but that doesn't make sense within the confines of a Ruby block.

marcamillion
  • 32,933
  • 55
  • 189
  • 380

7 Answers7

7

The most DRY way is to use CSS instead. Instead of e.g. this:

li.last { color: red; }

..and then cluttering up your markup with an extra CSS class, just use the :last-child pseudoselector, i.e.:

li:last-child { color: red; }

Then you don't have to change anything in your view. This is supported in all modern browsers including IE9.

Jordan Running
  • 102,619
  • 17
  • 182
  • 182
  • you may be right...except the only question is, are the `:last` and `:last-child` pseudoclasses supported by all major browsers? I like this suggestion though. – marcamillion Nov 08 '11 at 21:00
  • @Jordan @marcamillion This would have been the perfect answer if the problem were about styling the last `li`. However, the question is about doing something special on the last record of an array that is parsed using an `each` iterator. At least, this is what I understand from the title of the question and from the fact that `li` and `class` on last `li` were given as an example (see 'e.g. ....') – p.matsinopoulos Nov 08 '11 at 21:05
  • look at this references: http://reference.sitepoint.com/css/pseudoclass-lastchild and http://www.quirksmode.org/css/contents.html. `:last-child` are supported by all major browsers except IE (up to 9 version) – WarHog Nov 08 '11 at 21:05
  • @PanayotisMatsinopoulos you are actually right, when it comes to the letter of the question. However, in this particular case I actually do want to do that...i.e. apply a css style to the last element returned from that array. So this answer works perfectly...but the others also work pretty nicely too. I have learned new approaches to this problem for sure. – marcamillion Nov 08 '11 at 21:07
2

Try each_with_index:

<% @help_sections.each do |section, index| %>
    <li <%= "class='last'" if index == (@help_sections.length-1) %>><%= section.name %></li>
<% end %>
p.matsinopoulos
  • 7,655
  • 6
  • 44
  • 92
  • The CSS solution is most elegant for what I want in this one...but...do you think you might have a solution for another problem (of a similar nature) I am having? - http://stackoverflow.com/questions/8044038/how-do-i-detect-if-a-link-has-been-clicked-and-apply-a-different-css-class-if-it – marcamillion Nov 08 '11 at 21:18
2

DRY is a good idea in general, but don't kill yourself to keep from repeating a li.

<% @help_sections[0...-1].each do |section| %>
    <li><%= section.name %></li>
<% end %>
<li class="last"><%= @help_sections.last.name %></li>
pguardiario
  • 53,827
  • 19
  • 119
  • 159
1

If you use each_with_index instead of plain each, the block will also be passed the index of the current element within the collection. You can then compare that to @help_sections.length.

E.g.

<% @help_sections.each_with_index do |section, i| %>
    <li<% concat " class='last'" if i == @help_sections.length - 1 %>><%= section.name %></li>
<% end %>
J Cooper
  • 16,891
  • 12
  • 65
  • 110
  • Is `each_with_index` available in rails? 'Cause it isn't in ruby core – Sergio Campamá Nov 08 '11 at 20:58
  • The CSS solution is most elegant for what I want in this one...but...do you think you might have a solution for another problem (of a similar nature) I am having? - http://stackoverflow.com/questions/8044038/how-do-i-detect-if-a-link-has-been-clicked-and-apply-a-different-css-class-if-it – marcamillion Nov 08 '11 at 21:17
1

You could either do something like

<% @help_sections.count.times do |i| %>
    <%= @help_sections[i].name %>
    <%= do_something if @help_sections.count == i - 1 %>
<% end %>
Sergio Campamá
  • 746
  • 1
  • 7
  • 14
  • The CSS solution is most elegant for what I want in this one...but...do you think you might have a solution for another problem (of a similar nature) I am having? - http://stackoverflow.com/questions/8044038/how-do-i-detect-if-a-link-has-been-clicked-and-apply-a-different-css-class-if-it – marcamillion Nov 08 '11 at 21:19
1

This old answer might help (Tell the end of a .each loop in ruby). Basically, you can use:

<% @help_sections.each_with_index do |section, index| %>
  <% if index == @help_sections.size - 1 %>
    <li class="last">
  <% else %>
    <li>
  <% end %>

  <%= section.name %></li>
<% end %>
Community
  • 1
  • 1
William
  • 3,511
  • 27
  • 35
  • I thought about this, but this sounds like it is raising the complexity of the loop - i.e. if on every cycle through I am doing another check on the array itself, sounds like it could take a performance hit if the array size is large, no? – marcamillion Nov 08 '11 at 21:02
  • It doesn't seem to be that much slower. I ran quick benchmark in 1.9.1 on an array containing the array `['Random', 'titles', 'stuffs']` repeated 1000 times, and with the check for `.size - 1` it took .0001 seconds slower than without it. – William Nov 08 '11 at 21:13
  • The CSS solution is most elegant for what I want in this one...but...do you think you might have a solution for another problem (of a similar nature) I am having? - http://stackoverflow.com/questions/8044038/how-do-i-detect-if-a-link-has-been-clicked-and-apply-a-different-css-class-if-it – marcamillion Nov 08 '11 at 21:18
1

In general cases not covered by smart CSS selectors, you can define a convenient helper method, which would provide each iterated element with its context within the collection. Like this one:

def each_with_context enum
  length = enum.count
  enum.each_with_index do |elem, i|
    context = {
      :first => i == 0,
      :last => i == length - 1,
      :even => i.even?,
      :odd => i.odd?,
      :middle => i == length / 2
    }
    yield elem, context
  end
end

And then use it within HAML view like this:

-each_with_context(@help_sections) do |section, context|
  %li{:class => context[:last] ? 'last' : nil}
    =section.name
Mladen Jablanović
  • 43,461
  • 10
  • 90
  • 113
  • Thanks for this...it seems a BIT overkill for this particular problem, but I think something like this might be appropriate for another problem I am having: http://stackoverflow.com/questions/8044038/how-do-i-detect-if-a-link-has-been-clicked-and-apply-a-different-css-class-if-it Care to take a stab at that one? – marcamillion Nov 08 '11 at 21:16
  • It would be an overkill if you would use it just on one place, I agree. But it's pretty generic and I guess it could be useful here and there in an average web application, just put it in your application helper. – Mladen Jablanović Nov 08 '11 at 21:20