例えばUserモデルがあり、nameやらemailやらageやらを各オブジェクトが持っていたとする。
ある特定のemailの人たちだけに施したい処理があって、どのemailが対象となるかは既に配列のグループがあったとする。(若干例え悪いけどそんな感じだった)
※emailは一意性が担保されています
最初、以下の様な処理を書いていた。
# 対象としたいemailアドレス群。
targets = ["xxxx@gmail.com","xxx3@gmail.com"..."xxx25@gmail.com"]
targets.each do |target|
User.all.each do |user|
# 一致するものがあれば次の処理へ
next if target != user.email
user.nextaction
end
end
これだとeachで二重に回すことになるので、もっと簡単にできないものかと考えた時に、emailをキーとするハッシュつくればいいのでは??となった。
そして最初、injectでハッシュを生成したのがこちら。
# 対象としたいemail群。
targets = ["xxxx@gmail.com","xxx3@gmail.com"..."xxx25@gmail.com"]
# emailをキーとしてUser.allをハッシュ化
users_sort_email = User.all.inject({}) {|hash,user| hash[user.email] = user; hash }
targets.each do |target|
# targetをキーとした(=targetと同じ値のemailが存在した)場合次の処理に飛ばす
next if users_sort_email[target].blank?
user.nextaction
end
これでもできるんだけど、index_byを使うともっとみやすくなりました。
# 対象としたいemail群。
targets = ["xxxx@gmail.com","xxx3@gmail.com"..."xxx25@gmail.com"]
# emailをキーとしてUser.allをハッシュ化をなんとこれだけで表している。
users_sort_email = User.all.index_by(&:email)
targets.each do |target|
next if users_sort_email[target].blank?
user.nextaction
end
非常にみやすくなった。
ただしindex_byはActiveRecordでサポートされているものなので、デフォルトのRubyではinjectionを使いましょうという話。
追記
当初上記の記事はemailではなくageをキーとして記載していたのですが、コメントにて「一意性が担保されていないデータをキーに指定すると、ハッシュのキーが重複してしまい、そのうち一つにしかnextactionを呼ぶことができないとのご指摘をいただきました。
これはおっしゃって頂いた通りで、実際に実務で使用した際は一意性の担保されているキーで処理を行っていたものの、「だからこそ上記のソートが使える」という認識を十分に理解しておらず記事を記載したため生じたミスでした。
踏まえましてemailをキーとし、emalには一意性が担保されている旨を明記する形で記事を修正させていただきました。ありがとうございます!
また、例えばキーがageの様に一意性が担保されていない物で同様の処理を行いたい場合についても丁寧にコメントをいただくことができました。
詳細はコメント欄をご参照いただければと思うのですが、ActiveRecordがある前提であれば、以下の通りの処理で実現が可能とのことです。
targets = [1,4,5,6,10,23]
User.where(age: targets).each do |u|
u.nextaction
end
この場合に発行されるSQLは以下の通りです。
-- User.where(age: targets)はこんなSQLを発行している
SELECT * FROM users WHERE (users.age IN (1,4,5,6,10,23));
**ターゲットとなるageをもつUserを全て取得(この時点で抽出されたuserデータは全てnextactionの対象となる)→eachで回すことで「対象となるユーザーのみ全ての人にnextactionを行う」**という形で実現できています。
これについても非常に勉強になり、お時間割いてコメント頂いてありがとうございますという気持ちです・・・勉強になりました!