Uploaded image for project: 'Mongoid'
  1. Mongoid
  2. MONGOID-5306

Use pluck instead of only to avoid projecting when referencing association _ids

    • Type: Icon: Bug Bug
    • Resolution: Fixed
    • Priority: Icon: Critical - P2 Critical - P2
    • 8.0.1
    • Affects Version/s: None
    • Component/s: Associations
    • None
    • Minor Change

      The logic to raise MissingAttributeError added in https://jira.mongodb.org/browse/MONGOID-5034 for associations that were not projected appears to be triggered when accessing associations that are not set (e.g. because the host object is a brand new, empty object). The issue here is that the host object was not obtained from the database using a query that employed projection, therefore the projection checks shouldn't be performed on it and the fact that an association is missing shouldn't cause an exception - in this case Mongoid should in fact return nil for the association.

      Sample code reproducing this is in https://github.com/p-mongo/tests/blob/master/ma/lib/tasks/test_5306.rake:

      class Company
        include Mongoid::Document
      
        has_many :users
        has_many :shops
      end
      
      class Shop
        include Mongoid::Document
      
        embeds_one :address
        belongs_to :company
      
        after_initialize :build_address1
      
        def build_address1
          self.address ||= Address.new
        end
      end
      
      class Address
        include Mongoid::Document
        embedded_in :shop
      end
      
      class User
        include Mongoid::Document
      
        belongs_to :company
      
        validate :break_mongoid
      
        def break_mongoid
          company.shop_ids
        end
      end
      
      
      
      company = Company.create!
      
      shop = Shop.create!(company: company)
      
      user = User.new
      user.company = company
      user.save!
      

      Result:

      ActiveModel::MissingAttributeError: Missing attribute: 'address'
      
                raise ActiveModel::MissingAttributeError, "Missing attribute: '#{field_name}'"
                ^^^^^
      /home/w/apps/mongoid/lib/mongoid/association/accessors.rb:118:in `get_relation'
      /home/w/apps/mongoid/lib/mongoid/association/accessors.rb:300:in `block (2 levels) in define_getter!'
      /home/w/apps/tests/ma/lib/tasks/test_5306.rake:19:in `build_address1'
      /home/w/apps/mongoid/lib/mongoid/interceptable.rb:128:in `run_callbacks'
      /home/w/apps/mongoid/lib/mongoid/document.rb:321:in `instantiate_document'
      /home/w/apps/mongoid/lib/mongoid/factory.rb:109:in `execute_from_db'
      /home/w/apps/mongoid/lib/mongoid/factory.rb:82:in `from_db'
      /home/w/apps/mongoid/lib/mongoid/contextual/mongo.rb:683:in `yield_document'
      /home/w/apps/mongoid/lib/mongoid/contextual/mongo.rb:154:in `block in each'
      /home/w/apps/mongoid/lib/mongoid/contextual/mongo.rb:153:in `each'
      /home/w/apps/mongoid/lib/mongoid/contextual/mongo.rb:331:in `map'
      /home/w/apps/mongoid/lib/mongoid/contextual/mongo.rb:331:in `map'
      /home/w/apps/mongoid/lib/mongoid/association/accessors.rb:322:in `block (2 levels) in define_ids_getter!'
      /home/w/apps/mongoid/lib/mongoid/association/proxy.rb:124:in `method_missing'
      /home/w/apps/tests/ma/lib/tasks/test_5306.rake:36:in `break_mongoid'
      /home/w/apps/mongoid/lib/mongoid/interceptable.rb:128:in `run_callbacks'
      /home/w/apps/mongoid/lib/mongoid/interceptable.rb:128:in `run_callbacks'
      /home/w/apps/mongoid/lib/mongoid/validatable.rb:88:in `valid?'
      /home/w/apps/mongoid/lib/mongoid/persistable/creatable.rb:104:in `prepare_insert'
      /home/w/apps/mongoid/lib/mongoid/persistable/creatable.rb:20:in `insert'
      /home/w/apps/mongoid/lib/mongoid/persistable/savable.rb:20:in `save'
      /home/w/apps/mongoid/lib/mongoid/persistable/savable.rb:39:in `save!'
      /home/w/apps/tests/ma/lib/tasks/test_5306.rake:48:in `block in <top (required)>'
      Tasks: TOP => test_5306
      (See full trace by running task with --trace)
      

      -------

      Original report: 

      This code reproduces a bug which I believe was introduced as a result of MONGOID-5034. I've extracted this from my app test suite. It is not occurring on the v7.4.0 tag.

      I'm seeing a number of related failures when running my suite against Mongoid master, so hopefully we can find a fundamental fix. (This one alone took ~3 hours to extract and reduce down to a reproducible test!)

       

      class Company
        include Mongoid::Document
      
        has_many :users
        has_many :shops
      end
      
      class Shop
        include Mongoid::Document
      
        embeds_one :address
        belongs_to :company
      
        after_initialize :build_address
      
        def build_address
          self.address ||= Address.new
        end
      end
      
      class Address
        include Mongoid::Document
        embedded_in :shop
      end
      
      class User
        include Mongoid::Document
      
        belongs_to :company, validate: false, autosave: false
      
        validate :break_mongoid
      
        def break_mongoid
          company.shop_ids
        end
      end
      
      
      
      company = Company.new
      company.save!
      
      shop = Shop.new
      shop.company = company
      shop.save!
      
      user = User.new
      user.company = company
      user.save!

      As a side note, it is very curious to me why Company.shop_ids would need to care anything about the embedded models of Shop (i.e. address.) One would think it would just run a pluck:

      Shop.where(company_id: company._id).pluck(:_id)

      Is it loading the full Shop models from the database?

            Assignee:
            neil.shweky@mongodb.com Neil Shweky (Inactive)
            Reporter:
            shields@tablecheck.com Johnny Shields
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: