LoginSignup
2
0

More than 3 years have passed since last update.

Ruby の Enumerator に末尾のアイテムを判断する with_last を追加してみた

Last updated at Posted at 2020-02-28

したいこと

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0