アクティブレコードには、find_or_create_byという便利な動的属性があります。
Model.find_or_create_by_<attribute>(:<attribute> => "")
しかし、複数の属性でfind_or_createが必要な場合はどうなりますか?
GroupMemberというグループとメンバー間のM:M関係を処理するモデルがあるとします。 member_id = 4のインスタンスを多数持つことができますが、member_id = 4およびgroup_id = 7のインスタンスを複数回使用することは望ましくありません。
GroupMember.find_or_create(:member_id => 4, :group_id => 7)
これを処理するためのより良い方法があるかもしれないことを理解していますが、find_or_createのアイデアの便利さが気に入っています。
複数の属性をand
で接続できます:
GroupMember.find_or_create_by_member_id_and_group_id(4, 7)
(すぐにレコードを保存したくない場合は、find_or_initialize_by
を使用してください)
Edit:上記のメソッドはRails 4.で非推奨です。新しい方法は次のとおりです。
GroupMember.where(:member_id => 4, :group_id => 7).first_or_create
そして
GroupMember.where(:member_id => 4, :group_id => 7).first_or_initialize
編集2:これらのすべてがRails属性固有のものだけから除外されたわけではありません。
https://github.com/Rails/rails/blob/4-2-stable/guides/source/active_record_querying.md
例
GroupMember.find_or_create_by_member_id_and_group_id(4, 7)
なりました
GroupMember.find_or_create_by(member_id: 4, group_id: 7)
このスレッドに出くわしたが、状況に応じて変化する可能性のある属性を持つオブジェクトを検索または作成する必要がある場合は、次のメソッドをモデルに追加します。
# Return the first object which matches the attributes hash
# - or -
# Create new object with the given attributes
#
def self.find_or_create(attributes)
Model.where(attributes).first || Model.create(attributes)
end
最適化のヒント:選択するソリューションに関係なく、最も頻繁にクエリを実行する属性にインデックスを追加することを検討してください。
Rails 4では次のことができます。
GroupMember.find_or_create_by(member_id: 4, group_id: 7)
where
の使用は異なります:
GroupMember.where(member_id: 4, group_id: 7).first_or_create
これにより、GroupMember.where(member_id: 4, group_id: 7)
でcreate
が呼び出されます。
GroupMember.where(member_id: 4, group_id: 7).create
それどころか、find_or_create_by(member_id: 4, group_id: 7)
はcreate
でGroupMember
を呼び出します:
GroupMember.create(member_id: 4, group_id: 7)
この関連する commit をRails/railsで参照してください。
ブロックをfind_or_create
に渡すことにより、オブジェクトが新しく作成された場合にオブジェクトに追加される追加のパラメーターを渡すことができます。これは、検索していないフィールドの存在を検証する場合に便利です。
想定:
class GroupMember < ActiveRecord::Base
validates_presence_of :name
end
それから
GroupMember.where(:member_id => 4, :group_id => 7).first_or_create { |gm| gm.name = "John Doe" }
member_id 4
and group_id 7
を持つグループが見つからない場合、「John Doe」という名前の新しいGroupMemberを作成します
できるよ:
User.find_or_create_by(first_name: 'Penélope', last_name: 'Lopez')
User.where(first_name: 'Penélope', last_name: 'Lopez').first_or_create
または、単に初期化するには:
User.find_or_initialize_by(first_name: 'Penélope', last_name: 'Lopez')
User.where(first_name: 'Penélope', last_name: 'Lopez').first_or_initialize