Mayank Badola
Mayank Badola

Reputation: 33

Mongoid Syntax for running range queries with or condition

Suppose we need to write a range query in Mongoid. Let the field to be queried be range_field, then we do something like this

where(:range_field.lte => some-date-time, :range_field.gte => some-date-time)

But if i want to run a query to choose any of multiple ranges, i'd have to do

.or({:range_field.lte => some-date-time1, :range_field.gte => some-date-time2},{:range_field.lte => some-date-time3, :range_field.gte => some-date-time4})

This apparently doesn't work. How can I run such queries with Mongoid?

Upvotes: 1

Views: 751

Answers (1)

mu is too short
mu is too short

Reputation: 434585

When you say:

:range_field.lte => some_date_time

you're calling a method, lte, that Mongoid monkey patches in Symbol. That method returns an Origin::Key instance that is wrapped around the underlying $lte operator. Somewhere inside Mongoid that Origin::Key will be converted to something that MongoDB will understand:

{ range_field: { $lte: some_date_time } }

If you look at what

where(:range_field.lte => t1, :range_field.gte => t2)

becomes by calling selector on the result, you'll see something like this:

{
  "created_at" => {
    :$gte => t2,
    :$lte => t1
  }
}

and everything will work fine.

However, if we use #or and call selector to see the underlying query, we see that Mongoid is expanding the Origin::Keys one by one and merging the results:

or({:range_field.lte => t1, :range_field.gte => t2})
# is expanded as though as you said
or({ :range_field => { :$lte => t1 }, :range_field => { :$gte => t2 } })
# which is the same as
or({ :range_field => { :$gte => t2 } })

Essentially, Mongoid is being inconsistent as to how it expands the Origin::Keys. You'll even get the same confusing result if you use :$or instead of #or:

where(:$or => [ {:range_field.lte => t1, :range_field.gte => t2} ]).selector

will say:

{ "$or" => [ { "range_field" => { "$gte" => t2 } } ] }

The solution is to not use the Symbol monkey patched methods and do that part by hand:

or(
  { :range_field => { :$lte => t1, :$gte => t2 } },
  { :range_field => { :$lte => t3, :$gte => t4 } }, 
  ...
)

Upvotes: 1

Related Questions