Rails の Array と Emumerable 拡張
よく使うので、自分用にまとめ。
前提
以下、すべてのサンプルで Category 及び Entry というモデルを操作します。
持っている値は、以下のとおり。
- Category (has many entries)
- name => :string
- Entry (belongs to category)
- category_id => :integer
- title => :string
- body => :text
- view_count => :integer
- created_at => :datetime
- updated_at => :datetime
Array
active_support\core_ext\array\groupng.rb を参照。バージョンは 2.0.2 準拠です。
in_groups_of
恐らくこれが一番よく使うメソッド。配列を指定された数値ごとのグループに分けてくれます。以下、サンプルで説明します。
[1, 2, 3, 4, 5, 6, 7].in_groups_of(3) => [[1, 2, 3], [4, 5, 6], [7, nil, nil]]
このように配列が分割され、不足分は nil が代入されます。
ブロックを使って操作する事も可能。
[1, 2, 3, 4, 5, 6, 7].in_groups_of(3) do |group| p group.join end "123" "456" "7" => nil
よくあるのが、ビューで使うケース。
@entries = Entry.find(:all)
このように find してきたエントリを、横に2つずつ並べる事になってしまった時。
<% unless @entries.blank? %> <table> <% @entries.in_groups_of(2) do |group| %> <tr> <% group.each do |e| %> <% if e %> <td> <h1><%= e.title %></h1> <p><%= e.body %></p> </td> <% else %> <td> </td> <% end %> <% end %> </tr> <% end %> </table> <% end %>
html 的な是非はともかく、同じ処理を自分で行うとミスしやすいので、便利だと思います。
ちなみに blank? メソッドも Rails の拡張で、これはこれで便利なメソッドです。
split
こちらはあまり使った事がありません。条件に従って、配列を分割します。言葉では説明しづらいので、サンプルで。
[1, 2, 3, 4, 5].split(3) =>[[1, 2], [4, 5]]
このように、指定された値は削除され、残った部分がグループ化されます。
これもブロックを使った条件指定が可能。
(1..10).to_a.split do |i| i % 3 == 0 end =>[[1, 2], [4, 5], [7, 8], [10]]
find と組み合わせて使う場合はあまりない気がします(:conditions オプションで大体片付くはず)。何か特別な処理が必要な時に思い出すといいかもしれません。
Enumerable
active_support\core_ext\enumerable.rb 参照。こちらも 2.0.2 準拠。
group_by
これもグルーピングですが、個数ではなく条件によるグループ化を行います。
entries = Entry.find(:all) # カテゴリ別にグループ化 @catregorized_entries = entries.group_by {|e| e.category_id} # 年/月別でグループ化 @monthly_entries = entries.group_by {|e| e.created_at.strftime("%Y%m")}
サンプルで分かるように、かなり柔軟に条件指定ができるようです。グループ化した時の条件部分も表示可能。
<% @monthly_entries.each |term, entries| do %> <h1><%= term %></h1> <% entries.each do |e| %> <p><%= e.title %></p> <% end %> <% end %>
sum
合計を求めます。
一番簡単なサンプル。
[1, 2, 3].sum => 6
ブロックを使う事もできます。実際には、ほとんどの場合こちらを使うと思います。
entries = Entry.find(:all) catregorized_entries = entries.group_by {|e| e.category_id}
カテゴリ別でエントリを取得。これらのエントリの参照数が view_count で取得できるとして、合計値を表示します。
<% categorized_entries.each do |category, entries| %> <h1><%= category.name %>の全エントリの参照数</h1> <%= entries.sum {|e| e.view_count } %> <% end %>
以上、コードとしては非常に無駄が多いですが、使い方は理解できると思います。
なお、デフォルトの場合空の配列への操作は 0 が返りますが、オーバーライド可能です。
[].sum {|i| i.amount} => 0 [].sum(Payment.new(0)) {|i| i.amount} => Payment.new(0)
ソースに書いてあったサンプルそのままで申し訳ないのですが、返り値を任意のオブジェクトにできるという事です。うまく使うと、コードの簡略化に役立つかもしれません。
index_by
group_by に似ていますが、こちらはハッシュを作ります。あるキーに複数の値が属する事になる場合、ひとつだけ(ソースを読む限り、一番最後に一致した値)が選ばれます。
使い方は group_by に似ています。
categories = Category.find(:all) categories.index_by(&:name) => {"diary" => <Category ...>, "rails" => <Category ...>} entries = Entry.find(:all) entries.index_by {|e| e.created_at.strftime("%Y/%m/%d ") + e.title} => {"20080216 本日の日記" => <Entry ...>, "20080215 本日の日記" => <Entry ...>