ExportXMLWordPrintableJSON

    • Type: Bug
    • Resolution: Unresolved
    • Priority: Major - P3
    • None
    • Affects Version/s: None
    • Component/s: None
    • None
    • None
    • Ruby Drivers
    • None
    • None
    • None
    • None
    • None
    • None

      Mongoid::Fields#dot_dollar_field? (lib/mongoid/fields.rb:262) tests fields.key? before testing the name shape:

      def dot_dollar_field?(name)
      n = aliased_fields[name] || name
      fields.key? && (n.include?('.') || n.start_with?('$'))
      end

      The effect is that validate_writable_field_name! rejects dotted and dollared names only when the field has been declared, and never checks undeclared names. The condition is inverted relative to its intent: the declared case is the one Mongoid already supports, while undeclared names are not validated at all.

      On a model including Mongoid::Attributes::Dynamic this means a dotted attribute name passes through write_attribute and is stored verbatim. Dirty tracking then emits the raw dotted key into the update modifier, and the server interprets it as a nested path.

      Repro:

      class Profile
      include Mongoid::Document
      embedded_in :account
      field :score, type: Integer, default: 0
      end

      class Account
      include Mongoid::Document
      include Mongoid::Attributes::Dynamic
      embeds_one :profile
      end

      acct = Account.create!(profile: Profile.new)
      acct.assign_attributes('profile.score' => 42)
      acct.atomic_updates

      1. => {"$set"=>{"profile.score"=>42}}
        acct.save!
        Account.collection.find(_id: acct.id).first['profile']
      2. => {"_id"=>..., "score"=>42}

      Other observed effects of the same path, all reproduced on master:

      • The declared type of the target field is not applied. assign_attributes('profile.score' => 'not-an-integer') stores the String "not-an-integer" in a field declared as Integer.
      • Keys that the embedded model would otherwise reject are written. assign_attributes('profile' => {'undeclared' => 1}

        ) raises Mongoid::Errors::UnknownAttribute, while assign_attributes('profile.undeclared' => 1) stores undeclared => 1.

      • The in-memory object and the stored document diverge. After the first repro above, acct.profile.score is still 0 while the stored document has 42, because the write is recorded against the dotted key on the parent and the embedded object is never touched.
      • remove_attribute produces the $unset equivalent. remove_attribute('profile.score') yields {"$unset"=>{"profile.score"=>true}} and removes the key from the embedded document.

      The condition is unchanged on 8.0-stable:263, 8.1-stable:263, 9.0-stable:265 and master:262, dating to MONGOID-5351.

      Suggested fix: test the name shape independently of fields.key?. No existing spec asserts that dotted writes to undeclared names succeed; spec/integration/dots_and_dollars_spec.rb only asserts that the guard raises for declared dotted fields. Tightening the check does make previously accepted writes raise, so it should target a minor or major release.

            Assignee:
            Unassigned
            Reporter:
            Dmitry Rybakov
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated: