13
votes

I have a requirement where I need to run a MongoDB query like the following:

db.collection.find({ $or : [{"field1" : "value1"}, {"field2" : "value2"}], 
$or : [{"field3" : "value3"}, {"field4" : "value4"}]}) 

i.e.

(field1 == value 1 or field2 == value2) and (field3 == value3 or field4 
== value4) 

I want to achieve this through criteria chaining because the query gets formed dynamically from different parts of the code. But if I try doing something like the following

criteria = Collection.any_of({"field1" => "value1"}, {"field2" => 
"value2"})
criteria = criteria.any_of({"field3" => "value3"}, {"field4" => "value4"}) 

I get the resultant query where all these are combined into a single $or statement like

db.collection.find({ $or : [{"field1" : "value1"}, {"field2" : "value2"}, 
{"field3" : "value3"}, {"field4" : "value4"}]}) 

What is the way to achieve "and" of the two "any_of" using criteria chaining?

5
which version of Mongoid you use ?shingara
There is an issue with the way any_of / $or is handled by Mongoid. Have opened an issue on Github: github.com/mongoid/mongoid/issues/issue/569Rakeesh
This answer helped - stackoverflow.com/a/17905048/297679Nobu

5 Answers

4
votes

You can do it with avoid any_of.

criteria = Collection.where('$or' => [{"field1" => "value1"}, {"field2" => "value2"}])
criteria = criteria.where('$or' => [{"field3" => "value3"}, {"field4" => "value4"}])
4
votes

you can write this with mongoid 2.4.0:

  Collection.all_of({"$or" => [{"field1" => "value1"}, {"field2" => "value2"}]}, {"$or" => [{"field3" => "value3"}, {"field4" => "value4"}]})
2
votes

For Mongoid 3 and 4 there is a relatively nice way of chaining or criteria without merging.

TL;DR

MyModel.all_of(MyModel.xxx.selector, MyModel.yyy.selector)

Mongoid maintainer Durran showed the above trick in this Github issue: https://github.com/mongoid/mongoid/issues/2170

You can arbitrarily chain criteria using the same technique and some array trickery like this:

selectors = []
selectors << MyModel.search(term) if term
selectors << MyModel.some_any_of if some_condition
selectors << MyModel.some_other_any_of if some_other_condition
...
MyMode..all_of(*selectors)

Each row will add another and condition.

It may be worth noting too that you do not have to build each selector from the model. You can get an initial scope back from your permission system or something ans just call .selector on it before adding on more criteria.

1
votes

It took me forever to get this right, but this is what worked for me:

scope :for_user, lambda {|user| 
    any_of(
      {recipient_id: Moped::BSON::ObjectId.from_string(user.id)}, 
      {sender_id: Moped::BSON::ObjectId.from_string(user.id)},
      {sender_email: user.email},
      {recipient_email: user.email}
    )
  }

You have to make sure to wrap the individual criteria for your $or condition in {} so that it nests the OR properly. (Using Mongoid3/Moped)

0
votes

This is still the case. I was able to work around this only by using the Ruby Mongo Driver API directly.