したいこと
Rails で HTML を生成するとき、配列をループして、末尾だけちょっと違うものをレンダリングしたい。
したいことの例.ユーザのリストをレンダリング
aさんとbさんとcさんです
これはこんな感じに書くとおもう。
例1.mapしてjoin
<%= users.map {|user| "#{user.name}さん"}.join('と') %>です
例2.with_indexで末尾を判断(ちょっと辛くなってきた)
<%= users.each.with_index do |user, index| %>
<%= user.name %>さん<%= 'と' if (index + 1) != users.size %>
<% end %>です
レンダリングするものが複雑になってくると each
にブロック渡してレンダリングしたくなる。
理想.こんなふうにやりたい
<% users.each.with_last do |user, last| %>
<複雑なマークアップ>
<%= user.name %>さん<%= last ? 'です' : 'と'%>
</複雑なマークアップ>
<% end %>
実装
with_index
と同じアイデアなので、 with_index
の定義されてる Enumeratorクラスのドキュメントを読んでいたら next
ってメソッドがあった。次のアイテムを取得して、無理だったら StopIteration
例外を発生させるという。これを使えばそのアイテムが終端かどうか判断できそう。
with_last追加
class Enumerator
def with_last
each do |*args|
# each とともにイテレータをすすめる
self.next
# 進めた状態で次があるか確認、なければ終端例外 StopIteration が発生する
peek
yield(*args, false)
rescue StopIteration
# 終端
yield(*args, true)
end
end
end
[1,2,3].each.with_last { |item, last|
pp [item, last]
}
# => [1, false] [2, false] [3, true]
[1,2].cycle.with_index.with_last { |item, index, last|
pp [item, index, last]
}
# => [1, 0, false] [2, 1, false]
# [1, 2, false] [2, 3, false]
# [1, 4, false] [2, 5, false] ....(無限ループ)
なんとなく行けそうなので、引数とかを整理して gem にしました。
変な挙動したらissue/PRください
https://github.com/oieioi/with_last.rb