Mongoidには実はfind_by!
というクエリメソッドがあるのだが、公式ドキュメントのクエリセクションにはこの説明が載っていない。
かつ、mongoid.ymlで指定できるraise_not_found_error
オプションと、find
, find_by
, find_by!
の関係にちょっと癖があったので、それらをまとめるためにこの記事を残す。
find_by!
find_by!
の存在を知るためには、まずソースコードを読む必要がある。
lib/mongoid/findable.rb#L134にその記述がある
# Find the first +Document+ given the conditions, or raises
# Mongoid::Errors::DocumentNotFound
#
# @example Find the document by attribute other than id
# Person.find_by(:username => "superuser")
#
# @param [ Hash ] attrs The attributes to check.
#
# @raise [ Errors::DocumentNotFound ] If no document found.
#
# @return [ Document ] A matching document.
#
def find_by!(attrs = {})
result = where(attrs).find_first
raise(Errors::DocumentNotFound.new(self, attrs)) unless result
yield(result) if result && block_given?
result
end
メソッドのなかではMongoid.raise_not_found_error
をチェックする条件分岐がないので、その設定にかかわらずMongoid::Errors::DocumentNotFound
を投げることが分かる。
find_by
find_by
の実装は同ファイル中のlib/mongoid/findable.rb#L113にある。
# Find the first +Document+ given the conditions.
# If a matching Document is not found and
# Mongoid.raise_not_found_error is true it raises
# Mongoid::Errors::DocumentNotFound, return null nil elsewise.
#
# @example Find the document by attribute other than id
# Person.find_by(:username => "superuser")
#
# @param [ Hash ] attrs The attributes to check.
#
# @raise [ Errors::DocumentNotFound ] If no document found
# and Mongoid.raise_not_found_error is true.
#
# @return [ Document, nil ] A matching document.
#
# @since 3.0.0
def find_by(attrs = {})
result = where(attrs).find_first
if result.nil? && Mongoid.raise_not_found_error
raise(Errors::DocumentNotFound.new(self, attrs))
end
yield(result) if result && block_given?
result
end
find_by
の挙動はドキュメントによると
Criteria#find_by
Find a document by the provided attributes, and if not found raise an error or return nil *depending on the * raise_not_found_error
configuration option.
とのことだが、なるほど確かに実装をみると条件分岐中でMongoid.raise_not_found_error
の値をチェックしているのが分かる。
find
ここまではよろしかったが、このfindが問題である。findは実装上複数のメソッドに分けて隠蔽されていて、少し細かくみていかねばならない。
find
自体の実装はまずlib/mongoid/criteria/findable.rb#L37に記述されている
# Find the matchind document(s) in the criteria for the provided ids.
#
# @example Find by an id.
# criteria.find(BSON::ObjectId.new)
#
# @example Find by multiple ids.
# criteria.find([ BSON::ObjectId.new, BSON::ObjectId.new ])
#
# @param [ Array<BSON::ObjectId> ] args The ids to search for.
#
# @return [ Array<Document>, Document ] The matching document(s).
#
# @since 1.0.0
def find(*args)
ids = args.__find_args__
raise_invalid if ids.any?(&:nil?)
for_ids(ids).execute_or_raise(ids, args.multi_arged?)
end
処理の中心になるexecute_or_raise
はその上の行で定義されている。
# Execute the criteria or raise an error if no documents found.
#
# @example Execute or raise
# criteria.execute_or_raise(id)
#
# @param [ Object ] args The arguments passed.
#
# @raise [ Errors::DocumentNotFound ] If nothing returned.
#
# @return [ Document, Array<Document> ] The document(s).
#
# @since 2.0.0
def execute_or_raise(ids, multi)
result = multiple_from_db(ids)
check_for_missing_documents!(result, ids)
multi ? result : result.first
end
さらにcheck_for_missing_documents!
が出てくる。
定義先はlib/mongoid/criteria.rb#L455らしい。
# Are documents in the query missing, and are we configured to raise an
# error?
#
# @api private
#
# @example Check for missing documents.
# criteria.check_for_missing_documents!([], [ 1 ])
#
# @param [ Array<Document> ] result The result.
# @param [ Array<Object> ] ids The ids.
#
# @raise [ Errors::DocumentNotFound ] If none are found and raising an
# error.
#
# @since 3.0.0
def check_for_missing_documents!(result, ids)
if (result.size < ids.size) && Mongoid.raise_not_found_error
raise Errors::DocumentNotFound.new(klass, ids, ids - result.map(&:_id))
end
end
result配列が空なら例外を上げるという簡単な挙動であることが分かる。
しかし、ここでfind
メソッドに関するドキュメントを見てみたい。
Criteria#find
Find a document or multiple documents by their ids. Will raise an error by default if any of the ids do not match.
つまり、find
メソッドも「デフォルトでは例外を上げる」という説明があるのだが、はたしてその挙動がraise_not_found_error
オプションの影響を受けるということが書いていないので、注意が必要である。
まとめ
例外を上げる | 例外をあげない | |
---|---|---|
raise_not_found_error: true |
find , find_by , find_by! , |
(なし) |
raise_not_found_error: false | find_by! |
find , find_by
|
ActiveRecordと違って、raise_not_found_error というオプションがあるので注意して使いたい
|