Skip to content

keys with type "Set" returning an Array rather than set in 0.13.1 #614

Open

Description

@jamieorc

I’ve been on a version of 0.13.0 Beta 2 (:ref => ‘9b87dee507802a8cb96f2d316802c9fd241448c8’) for a couple of years. Just tried the current live gem, 0.13.1 and ran my specs. A few failed that have Sets for keys. I opened Rails Consoles with 0.13.0 Beta 2 and 0.13.1 running. Here’s what I see:

0.13.0 beta 2:

p.descendant_ids.class 
 => Set 

0.13.1:

p.descendant_ids.class 
 => Array 

I did a cursory search through the issues and didn’t see anything related.

Activity

  1. cheald commented on Dec 4, 2015

    @cheald
    Member

    Ah, interesting. That looks like a regression! I'll see what I can find!

  2. cheald commented on Dec 4, 2015

    @cheald
    Member
    describe "a key with a Set type" do
      DocWithSetKey = Doc do
        set_collection_name "docs_with_set_keys"
        key :foo, Set
      end
    
      before do
        DocWithSetKey.create foo: [1, 2, 3]
      end
    
      it "should persist as a Set" do
        expect(DocWithSetKey.first.foo).to be_a(Set)
      end
    end

    This spec passes for me. Can you help me figure out how to break it?

  3. jamieorc commented on Dec 4, 2015

    @jamieorc
    ContributorAuthor

    Thanks, I'll have a look this afternoon.

  4. jamieorc commented on Dec 7, 2015

    @jamieorc
    ContributorAuthor

    I've verified the problem again, so digging into it.

    p.components.first.ancestor_ids.class
     => Array 
    
  5. jamieorc commented on Dec 7, 2015

    @jamieorc
    ContributorAuthor

    I've found what causes the problem, but not the regression in the code:

    When :typecast is set, Array is returned instead of Set. Here, :foo_ids is typecast to ObjectId:

    2.1.7 :021 > class Foo; include MongoMapper::EmbeddedDocument; key :foo_ids, Set, typecast: "ObjectId"; key :bars, Set;end
     => #<MongoMapper::Plugins::Keys::Key:0x007ffbdb61a740 @type=Set, @name="bars", @options={}, @dynamic=false, @embeddable=false, @is_id=false, @typecast=nil, @accessors=[], @has_default=false, @ivar=:@bars> 
    2.1.7 :022 > f = Foo.new
     => #<Foo _id: BSON::ObjectId('5665d7335fd76c19f6000005'), bars: #<Set: {}>, foo_ids: []> 
    2.1.7 :023 > class Foo; include MongoMapper::EmbeddedDocument; key :foo_ids, Set; key :bars, Set;end
    

    Here :foo_ids is set to String:

    2.1.7 :026 >   class Foo; include MongoMapper::EmbeddedDocument; key :foo_ids, Set, typecast: "String"; key :bars, Set;end
     => #<MongoMapper::Plugins::Keys::Key:0x007ffbdb8d45a8 @type=Set, @name="bars", @options={}, @dynamic=false, @embeddable=false, @is_id=false, @typecast=nil, @accessors=[], @has_default=false, @ivar=:@bars> 
    2.1.7 :027 > f = Foo.new
     => #<Foo _id: BSON::ObjectId('5665d7fa5fd76c19f6000007'), bars: #<Set: {}>, foo_ids: []> 
    

    With no :typecast, a Set is returned, as expected:

     => #<MongoMapper::Plugins::Keys::Key:0x007ffbddad20d8 @type=Set, @name="bars", @options={}, @dynamic=false, @embeddable=false, @is_id=false, @typecast=nil, @accessors=[], @has_default=false, @ivar=:@bars> 
    2.1.7 :024 > f = Foo.new
     => #<Foo _id: BSON::ObjectId('5665d7645fd76c19f6000006'), bars: #<Set: {}>, foo_ids: #<Set: {}>> 
    
  6. jamieorc commented on Dec 7, 2015

    @jamieorc
    ContributorAuthor

    I've found the regression and will work up a pull request. It's around using map instead of map! when typecasting:

    • map(currently used) always returns an Array
    • map! (formerly used) operates on the existing Array or Set, so preserves the original list type
  7. jamieorc commented on Dec 7, 2015

    @jamieorc
    ContributorAuthor

    If you want to see a spec that fails, add this into functional/keys_spec.rb:

      context "when persisting a typecast Set" do
        TypecastedKeyModel = Doc do
          key :people, Set, :typecast => "Person"
        end
    
        Person = Struct.new(:name) do
          def self.to_mongo(value)
            value.name
          end
    
          def self.from_mongo(value)
            Person.new value
          end
        end
    
        it "should not mutate the model's state" do
          person = Person.new "Bob"
          doc = TypecastedKeyModel.new(:people => Set.new([person]))
    
          doc.save
    
          doc.people.should == Set.new([person])
        end
      end
    
  8. jamieorc commented on Dec 7, 2015

    @jamieorc
    ContributorAuthor

    Question for you, Chris: is there any reason not to use map! to modify what is returned by from_mongo? I'd rather not make several lists to get to the final one unless necessary.

  9. cheald commented on Dec 7, 2015

    @cheald
    Member

    I can't think of a reason off the top of my head, but generally I make those kinds of changes when there may be potential side-effects of mutating a provided list rather than returning a new copy.

    Where's the offending line? I might be able to provide better opinions if I can look at the blame history.

  10. jamieorc commented on Dec 7, 2015

    @jamieorc
    ContributorAuthor

    Line 65 in key.rb:

    https://github.com/mongomapper/mongomapper/blob/master/lib/mongo_mapper/plugins/keys/key.rb#L65

    I've got a spec and the change done on a fork. I'll make a pull request if you don't see any reason not to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions