Question

I have a Gift model:

class Gift
  include Mongoid::Document
  include Mongoid::Timestamps

  has_many :gift_units, :inverse_of => :gift
end

And I have a GiftUnit model:

class GiftUnit
  include Mongoid::Document
  include Mongoid::Timestamps

  belongs_to :gift, :inverse_of => :gift_units
end

Some of my gifts have gift_units, but others have not. How do I query for all the gifts where gift.gift_units.size > 0?

Fyi: Gift.where(:gift_units.exists => true) does not return anything.

Was it helpful?

Solution

That has_many is an assertion about the structure of GiftUnit, not the structure of Gift. When you say something like this:

class A
  has_many :bs
end

you are saying that instance of B have an a_id field whose values are ids for A instances, i.e. for any b which is an instance of B, you can say A.find(b.a_id) and get an instance of A back.

MongoDB doesn't support JOINs so anything in a Gift.where has to be a Gift field. But your Gifts have no gift_units field so Gift.where(:gift_units.exists => true) will never give you anything.

You could probably use aggregation through GiftUnit to find what you're looking for but a counter cache on your belongs_to relation should work better. If you had this:

belongs_to :gift, :inverse_of => :gift_units, :counter_cache => true

then you would get a gift_units_count field in your Gifts and you could:

Gift.where(:gift_units_count.gt => 0)

to find what you're looking for. You might have to add the gift_units_count field to Gift yourself, I'm finding conflicting information about this but I'm told (by a reliable source) in the comments that Mongoid4 creates the field itself.

If you're adding the counter cache to existing documents then you'll have to use update_counters to initialize them before you can query on them.

OTHER TIPS

I tried to find a solution for this problem several times already and always gave up. I just got an idea how this can be easily mimicked. It might not be a very scalable way, but it works for limited object counts. The key to this is a sentence from this documentation where it says:

Class methods on models that return criteria objects are also treated like scopes, and can be chained as well.

So, get this done, you can define a class function like so:

def self.with_units
  ids = Gift.all.select{|g| g.gift_units.count > 0}.map(&:id)
  Gift.where(:id.in => ids)
end

The advantage is, that you can do all kinds of queries on the associated (GiftUnits) model and return those Gift instances, where those queries are satisfied (which was the case for me) and most importantly you can chain further queries like so:

Gift.with_units.where(:some_field => some_value)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top