class Mongoid::Criteria
The Criteria
class is the core object needed in Mongoid
to retrieve objects from the database. It is a DSL that essentially sets up the selector and options arguments that get passed on to a Mongo::Collection in the Ruby driver. Each method on the Criteria
returns self to they can be chained in order to create a readable criterion to be executed against the database.
Constants
- CHECK
Static array used to check with method missing - we only need to ever instantiate once.
Attributes
Public Class Methods
Convert the given hash to a criteria. Will iterate over each keys in the hash which must correspond to method on a criteria object. The hash must also include a “klass” key.
@example Convert the hash to a criteria.
Criteria.from_hash({ klass: Band, where: { name: "Depeche Mode" })
@param [ Hash ] hash The hash to convert.
@return [ Criteria
] The criteria.
# File lib/mongoid/criteria.rb, line 55 def from_hash(hash) criteria = Criteria.new(hash.delete(:klass) || hash.delete('klass')) hash.each_pair do |method, args| criteria = criteria.__send__(method, args) end criteria end
Initialize the new criteria.
@example Init the new criteria.
Criteria.new(Band)
@param [ Class ] klass The model class.
Mongoid::Criteria::Modifiable#new
# File lib/mongoid/criteria.rb, line 233 def initialize(klass) @klass = klass @embedded = nil @none = nil klass ? super(klass.aliased_fields, klass.fields, klass.relations, klass.aliased_associations) : super({}, {}, {}, {}) end
Public Instance Methods
Returns true if the supplied Enumerable
or Criteria
is equal to the results of this Criteria
or the criteria itself.
@note This will force a database load when called if an enumerable is passed.
@param [ Object
] other The other Enumerable
or Criteria
to compare to.
@return [ true | false ] If the objects are equal.
Mongoid::Criteria::Queryable#==
# File lib/mongoid/criteria.rb, line 78 def ==(other) return super if other.respond_to?(:selector) entries == other end
Needed to properly get a criteria back as json
@example Get the criteria as json.
Person.where(:title => "Sir").as_json
@param [ Hash ] options Options
to pass through to the serializer.
@return [ String ] The JSON string.
# File lib/mongoid/criteria.rb, line 140 def as_json(options = nil) entries.as_json(options) end
Get the documents from the embedded criteria.
@example Get the documents.
criteria.documents
@return [ Array<Document> ] The documents.
# File lib/mongoid/criteria.rb, line 150 def documents @documents ||= [] end
Set the embedded documents on the criteria.
@example Set the documents.
@param [ Array<Document> ] docs The embedded documents.
@return [ Array<Document> ] The embedded documents.
# File lib/mongoid/criteria.rb, line 161 def documents=(docs) @documents = docs end
Is the criteria for embedded documents?
@example Is the criteria for embedded documents?
criteria.embedded?
@return [ true | false ] If the criteria is embedded.
# File lib/mongoid/criteria.rb, line 171 def embedded? !!@embedded end
Is the criteria an empty but chainable criteria?
@example Is the criteria a none criteria?
criteria.empty_and_chainable?
@return [ true | false ] If the criteria is a none.
# File lib/mongoid/criteria.rb, line 301 def empty_and_chainable? !!@none end
Extract a single id from the provided criteria. Could be in an $and query or a straight _id query.
@example Extract the id.
criteria.extract_id
@return [ Object
] The id.
# File lib/mongoid/criteria.rb, line 182 def extract_id selector['_id'] || selector[:_id] || selector['id'] || selector[:id] end
Adds a criterion to the Criteria
that specifies additional options to be passed to the Ruby driver, in the exact format for the driver.
@example Add extra params to the criteria. criteria.extras(:limit => 20, :skip => 40)
@param [ Hash ] extras The extra driver options.
@return [ Criteria
] The cloned criteria.
# File lib/mongoid/criteria.rb, line 195 def extras(extras) crit = clone crit.options.merge!(extras) crit end
Get the list of included fields.
@example Get the field list.
criteria.field_list
@return [ Array<String> ] The fields.
# File lib/mongoid/criteria.rb, line 207 def field_list if options[:fields] options[:fields].keys.reject{ |key| key == klass.discriminator_key } else [] end end
Finds one or many documents given the provided _id values, or filters the documents in the current scope in the application process space after loading them if needed.
If this method is not given a block, it delegates to +Findable#find+ and finds one or many documents for the provided _id values.
If this method is given a block, it delegates to +Enumerable#find+ and returns the first document of those found by the current Crieria object for which the block returns a truthy value.
Note that the “default proc” argument of Enumerable is not specially treated by Mongoid
- the decision between delegating to Findable
vs Enumerable
is made solely based on whether find
is passed a block.
@note Each argument can be an individual id, an array of ids or
a nested array. Each array will be flattened.
@example Finds a document by its _id, invokes Findable#find
.
critera.find("1234")
@example Finds the first matching document using a block, invokes Enumerable#find.
criteria.find { |item| item.name == "Depeche Mode" }
@example Finds the first matching document using a block using the default Proc, invokes Enumerable#find.
criteria.find(-> { "Default Band" }) { |item| item.name == "Milwaukee Mode" }
@example Tries to find a document whose _id is the stringification of the provided Proc, typically failing.
enumerator = criteria.find(-> { "Default Band" })
@param [ [ Object
| Array<Object> ]… ] *args The id(s). @param &block Optional block to pass. @yield [ Object
] Yields each enumerable element to the block.
@return [ Document
| Array<Document> | nil ] A document or matching documents.
@raise Errors::DocumentNotFound
If the parameters were _id values and
not all documents are found and the +raise_not_found_error+ Mongoid configuration option is truthy.
@see ruby-doc.org/core/Enumerable.html#method-i-find
# File lib/mongoid/criteria.rb, line 124 def find(*args, &block) if block_given? _enumerable_find(*args, &block) else _findable_find(*args) end end
Find documents by the provided javascript and scope. Uses a $where but is different from +Criteria#where+ in that it will pass a code object to the query instead of a pure string. Safe against Javascript injection attacks.
@example Find by javascript.
Band.for_js("this.name = param", param: "Tool")
@param [ String ] javascript The javascript to execute in the $where. @param [ Hash ] scope The scope for the code.
@return [ Criteria
] The criteria.
@deprecated
# File lib/mongoid/criteria.rb, line 466 def for_js(javascript, scope = {}) code = if scope.empty? # CodeWithScope is not supported for $where as of MongoDB 4.4 BSON::Code.new(javascript) else BSON::CodeWithScope.new(javascript, scope) end js_query(code) end
When freezing a criteria we need to initialize the context first otherwise the setting of the context on attempted iteration will raise a runtime error.
@example Freeze the criteria.
criteria.freeze
@return [ Criteria
] The frozen criteria.
# File lib/mongoid/criteria.rb, line 223 def freeze context and inclusions and super end
Merges another object with this Criteria
and returns a new criteria. The other object may be a Criteria
or a Hash
. This is used to combine multiple scopes together, where a chained scope situation may be desired.
@example Merge the criteria with another criteria.
criteria.merge(other_criteria)
@example Merge the criteria with a hash. The hash must contain a klass
key and the key/value pairs correspond to method names/args. criteria.merge({ klass: Band, where: { name: "Depeche Mode" }, order_by: { name: 1 } })
@param [ Criteria
] other The other criterion to merge with.
@return [ Criteria
] A cloned self.
# File lib/mongoid/criteria.rb, line 260 def merge(other) crit = clone crit.merge!(other) crit end
Merge the other criteria into this one.
@example Merge another criteria into this criteria.
criteria.merge(Person.where(name: "bob"))
@param [ Criteria
| Hash ] other The criteria to merge in.
@return [ Criteria
] The merged criteria.
# File lib/mongoid/criteria.rb, line 274 def merge!(other) other = self.class.from_hash(other) if other.is_a?(Hash) selector.merge!(other.selector) options.merge!(other.options) self.documents = other.documents.dup unless other.documents.empty? self.scoping_options = other.scoping_options self.inclusions = (inclusions + other.inclusions).uniq self end
Returns a criteria that will always contain zero results and never hits the database.
@example Return a none criteria.
criteria.none
@return [ Criteria
] The none criteria.
# File lib/mongoid/criteria.rb, line 291 def none @none = true and self end
Overriden to include _type in the fields.
@example Limit the fields returned from the database.
Band.only(:name)
@param [ [ Symbol | Array<Symbol> ]… ] *args The field name(s).
@return [ Criteria
] The cloned criteria.
# File lib/mongoid/criteria.rb, line 313 def only(*args) args = args.flatten return clone if args.empty? if (args & Fields::IDS).empty? args.unshift(:_id) end if klass.hereditary? args.push(klass.discriminator_key.to_sym) end super(*args) end
Set the read preference for the criteria.
@example Set the read preference.
criteria.read(mode: :primary_preferred)
@param [ Hash ] value The mode preference.
@return [ Criteria
] The cloned criteria.
# File lib/mongoid/criteria.rb, line 333 def read(value = nil) clone.tap do |criteria| criteria.options.merge!(read: value) end end
Returns true if criteria responds to the given method.
@example Does the criteria respond to the method?
crtiteria.respond_to?(:each)
@param [ Symbol ] name The name of the class method on the Document
. @param [ true | false ] include_private Whether to include privates.
@return [ true | false ] If the criteria responds to the method.
# File lib/mongoid/criteria.rb, line 361 def respond_to?(name, include_private = false) super || klass.respond_to?(name) || CHECK.respond_to?(name, include_private) end
Convenience for objects that want to be merged into a criteria.
@example Convert to a criteria.
criteria.to_criteria
@return [ Criteria
] self. @deprecated
# File lib/mongoid/criteria.rb, line 374 def to_criteria self end
Convert the criteria to a proc.
@example Convert the criteria to a proc.
criteria.to_proc
@return [ Proc ] The wrapped criteria.
# File lib/mongoid/criteria.rb, line 385 def to_proc ->{ self } end
Adds a criterion to the Criteria
that specifies a type or an Array of types that must be matched.
@example Match only specific models.
criteria.type('Browser') criteria.type(['Firefox', 'Browser'])
@param [ Array<String> ] types The types to match against.
@return [ Criteria
] The cloned criteria.
# File lib/mongoid/criteria.rb, line 399 def type(types) any_in(self.discriminator_key.to_sym => Array(types)) end
This is the general entry point for most MongoDB queries. This either creates a standard field: value selection, and expanded selection with the use of hash methods, or a $where selection if a string is provided.
@example Add a standard selection.
criteria.where(name: "syd")
@example Add a javascript selection.
criteria.where("this.name == 'syd'")
@param [ [ Hash | String ]… ] *args The standard selection
or javascript string.
@raise [ UnsupportedJavascript ] If provided a string and the criteria
is embedded.
@return [ Criteria
] The cloned selectable.
# File lib/mongoid/criteria.rb, line 420 def where(*args) # Historically this method required exactly one argument. # As of https://jira.mongodb.org/browse/MONGOID-4804 it also accepts # zero arguments. # The underlying where implemetation that super invokes supports # any number of arguments, but we don't presently allow mutiple # arguments through this method. This API can be reconsidered in the # future. if args.length > 1 raise ArgumentError, "Criteria#where requires zero or one arguments (given #{args.length})" end if args.length == 1 expression = args.first if expression.is_a?(::String) && embedded? raise Errors::UnsupportedJavascript.new(klass, expression) end end super end
Overriden to exclude _id from the fields.
@example Exclude fields returned from the database.
Band.without(:name)
@param [ Symbol… ] *args The field name(s).
@return [ Criteria
] The cloned criteria.
# File lib/mongoid/criteria.rb, line 347 def without(*args) args -= id_fields super(*args) end
Get a version of this criteria without the options.
@example Get the criteria without options.
criteria.without_options
@return [ Criteria
] The cloned criteria.
# File lib/mongoid/criteria.rb, line 446 def without_options crit = clone crit.options.clear crit end
Private Instance Methods
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.
# File lib/mongoid/criteria.rb, line 492 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
Clone or dup the current Criteria
. This will return a new criteria with the selector, options, klass, embedded options, etc intact.
@api private
@example Clone a criteria.
criteria.clone
@example Dup a criteria.
criteria.dup
@param [ Criteria
] other The criteria getting cloned.
@return [ nil ] nil.
Mongoid::Criteria::Queryable#initialize_copy
# File lib/mongoid/criteria.rb, line 512 def initialize_copy(other) @inclusions = other.inclusions.dup @scoping_options = other.scoping_options @documents = other.documents.dup @context = nil super end
For models where inheritance is at play we need to add the type selection.
@example Add the type selection.
criteria.merge_type_selection
@return [ true | false ] If type selection was added.
# File lib/mongoid/criteria.rb, line 549 def merge_type_selection selector.merge!(type_selection) if type_selectable? end
# File lib/mongoid/criteria.rb, line 530 def method_missing(name, *args, &block) if klass.respond_to?(name) klass.send(:with_scope, self) do klass.send(name, *args, &block) end elsif CHECK.respond_to?(name) return entries.send(name, *args, &block) else super end end
Get a new selector with type selection in it.
@api private
@example Get a selector with type selection.
criteria.selector_with_type_selection
@return [ Hash ] The selector.
# File lib/mongoid/criteria.rb, line 592 def selector_with_type_selection type_selectable? ? selector.merge(type_selection) : selector end
Is the criteria type selectable?
@api private
@example If the criteria type selectable?
criteria.type_selectable?
@return [ true | false ] If type selection should be added.
# File lib/mongoid/criteria.rb, line 561 def type_selectable? klass.hereditary? && !selector.keys.include?(self.discriminator_key) && !selector.keys.include?(self.discriminator_key.to_sym) end
Get the selector for type selection.
@api private
@example Get a type selection hash.
criteria.type_selection
@return [ Hash ] The type selection.
# File lib/mongoid/criteria.rb, line 575 def type_selection klasses = klass._types if klasses.size > 1 { klass.discriminator_key.to_sym => { "$in" => klass._types }} else { klass.discriminator_key.to_sym => klass._types[0] } end end