Search code examples
mongodbassociationscountingmongoid3

How to select all records where related collection is empty with Mongoid


I'm still getting the hang of MongoDB and Mongoid and have hit this snag.

Let's say I have a User that has_and_belongs_to_many items and of course an Item that has_and_belongs_to_many users.

I'd like to be able to count the Users with any Items.

this question recommended adding a scope so I tried adding a scope to User such as

scope :has_no_items, where(:items.empty?)

but User.count - User.has_no_items.count returns 0.

I've looked at .with_size but that's specific to Array fields.

What is the correct way to do this other than

count = 0
User.each { |u| count += 1 unless u.items.empty? }

which works but it doesn't seem very elegant.

How do I do this efficiently?


Solution

  • The following works for me with Rails 3.2.13, Mongoid 3.1.4, Moped 1.5.0.

    app/models/user.rb

    class User
      include Mongoid::Document
      field :name, type: String
      has_and_belongs_to_many :items
      scope :has_items, where(:item_ids.ne => nil)
      scope :has_no_items, where(:item_ids => nil)
    end
    

    app/models/item.rb

    class Item
      include Mongoid::Document
      field :name, type: String
      has_and_belongs_to_many :users
    end
    

    test/unit/user_test.rb

    require 'test_helper'

    class UserTest < ActiveSupport::TestCase
      def setup
        User.delete_all
        Item.delete_all
      end
    
      test "users without items" do
        fagin = User.create(:name => 'Fagin')
        oliver = User.create(:name => 'Oliver')
        fagin.items << Item.create(:name => 'cane')
        assert_equal 2, User.count
        assert_equal 1, Item.count
        assert_equal 1, User.has_items.count
        assert_equal 1, User.has_no_items.count
        puts
        puts "User.has_items: #{User.has_items.to_a.inspect}"
        puts "User.has_no_items: #{User.has_no_items.to_a.inspect}"
      end
    end
    

    rake test

    Run options:
    
    # Running tests:
    
    [1/1] UserTest#test_users_without_items
    User.has_items: [#<User _id: 51df00987f11ba3d7d000001, name: "Fagin", item_ids: ["51df00987f11ba3d7d000003"]>]
    User.has_no_items: [#<User _id: 51df00987f11ba3d7d000002, name: "Oliver", item_ids: nil>]
    Finished tests in 0.042205s, 23.6939 tests/s, 94.7755 assertions/s.
    1 tests, 4 assertions, 0 failures, 0 errors, 0 skips
    

    Hope that this helps.