40

In your typical each loop in Rails, how do I determine the last object, because I want to do something different to it than the rest of the objects.

<% @stuff.each do |thing| %>

<% end %>
alamodey
  • 14,320
  • 24
  • 86
  • 112

6 Answers6

66
@stuff.each do |s|
  ...normal stuff...
  if s == @stuff.last
    ...special stuff...
  end
end
abject_error
  • 2,858
  • 1
  • 19
  • 23
  • 2
    this is the best answer... could be a touch better if you'd like to 1-line it. `s == @stuff.last ? 'thing for last' : 'things for the rest'` – Dudo Oct 01 '13 at 01:34
  • 16
    warning: this won't work if @stuff elements aren't unique. ie "a = [1,1];a.map{|v| v == a.last}" returns [true,true]. in case of integers there's no way to determine if it's really last element. if using other objects, you can use equal? (http://stackoverflow.com/questions/7156955/whats-the-difference-between-equal-eql-and) – Pavel K. Dec 03 '13 at 15:11
  • this will also not work if @stuff is an `io` or some other `Enumerator` and not an array (e.g. `io`). One can use `Enumerator#next` instead like https://stackoverflow.com/a/59556778/520567 – akostadinov Jan 01 '23 at 22:56
33

Interesting question. Use an each_with_index.

len = @stuff.length

@stuff.each_with_index do |x, index|
 # should be index + 1       
 if index+1 == len
 # do something
  end
end
Jakub Kuchar
  • 1,665
  • 2
  • 23
  • 39
A.Ali
  • 749
  • 6
  • 14
8
<% @stuff[0...-1].each do |thing| %>
  <%= thing %>
<% end %>
<%= @stuff.last %>
jackpipe
  • 941
  • 1
  • 8
  • 13
  • Much nicer than checking every iteration if we're at the end! Although I think you have one too many `.`'s? – Matt Aug 31 '16 at 14:06
  • Thanks! Actually it's a little confusing, since `-1` in a range stands for "the last element", so actually `stuff[0..-1] == stuff`. We need to use the `...` form of range which omits the last element. – jackpipe Jan 12 '17 at 06:48
  • I see! You should add that to the answer to make it a bit clearer and save stupid questions like mine ;) – Matt Jan 12 '17 at 14:48
2

A somewhat naive way to handle it, but:

<% @stuff.each_with_index do |thing, i| %>
  <% if (i + 1) == @stuff.length %>
    ...
  <% else %>
    ...
  <% end %>
<% end %>
apostlion
  • 720
  • 5
  • 10
2

A more lispy alternative is to be to use

@stuff[1..-1].each do |thing|

end
@stuff[-1].do_something_else
Sam
  • 1,014
  • 6
  • 8
0

Surprised this is not listed as an answer, but to my this is the cleanest way to do it. By starting the index at 1, you can remove the math of making the index and size match up.

@stuff = [:first, :second, :last]

@stuff.each.with_index(1) do |item, index|
  if index == @stuff.size
    # last item
  else
    # other items
  end
end

also the added benefit here is that you can use this on the .map as well.

Arian Faurtosh
  • 17,987
  • 21
  • 77
  • 115