icereval / backbone-documentmodel

A plugin to create entire Document structures with nested Backbone.js Models & Collections with deep model references and event bubbling.
MIT License
66 stars 8 forks source link

Calling set() multiple times on a model that contains a nested array of primitives throws an error #22

Open azpublic opened 9 years ago

azpublic commented 9 years ago

When calling fetch() to populate a DocumentCollection, then calling set() 2 times on one of the models of the collection triggers an exception if the model data contains an array.

example :


            var Job7 = Backbone.DocumentModel.extend({
                 className : 'Job7',
            });

            var JobCollection7 = Backbone.DocumentCollection.extend({
                model : Job7
            });

            function doTest7(){
                $.getJSON("job1_1.json", function(job1) {

                    var coll2 = new JobCollection7([job1],{});
                    coll2.fetch({url : 'job1_1.json'}).complete(function(){

                        console.error("coll2 - 1");
                        console.error(coll2);
                        console.error( JSON.stringify(coll2) );
                        debugger;
                        console.error("");

                        $.getJSON("job1_1.json", function(job2) {

                            coll2.findWhere({ objectId : '545c8659e7e0a59538543a79'} ).set(job2);

                            console.error("coll2 - 2");
                            console.error(coll2);
                            console.error( JSON.stringify(coll2) );
                            debugger;
                            console.error("");

                            $.getJSON("job1_1.json", function(job3) {

                                coll2.findWhere({ objectId : '545c8659e7e0a59538543a79'} ).set(job3);

                                console.error("coll2 - 3");
                                console.error(coll2);
                                debugger;
                                console.error( JSON.stringify(coll2) );
                                console.error("");

                            });
                        });
                    });
                });
            }

content of the json :


{
    "jobStatus": "CREATED",
    "objectId": "545c8659e7e0a59538543a79",

    "jobTasksByDate": 
    [
        "545c8659e7e0a59538543a7b",
        "545c8659e7e0a59538543a7e"
    ]
}

The error is thrown durin the 2nd call to set() on the model that is inside the collection and throws :

Uncaught TypeError: Cannot read property 'cid' of undefined

It seems only to happen when fetch is used and only when the underlying model has a member array.

I am having a hard time figuring out why.

azpublic commented 9 years ago

Ok so I've tracked down this issue a little further. It seems related to the lengh of the nested collection that is not modified correctly during the first call to set() on the model in my example. The length is not decremented correctly during the set() and therefore instead of having an expected lenght of 2 after the call to the first set, the nested collection has length of 4.

Here's why (warning : nasty bug ahead):

It seems that on the first call to set() (the one after fetch has successfully populated our collection), the model goes through documentModelSet() then the nested collection (jobTasksByDate in our example) is being processed and documentCollectionSet() is called which itself triggers Backbone.js regular set() method and then Backbone.js remove() is called to "// Remove nonexistent models if appropriate." (see backbone's source near line 720 ).

This remove() call iterates through the models and makes this very specific call (near line 648) which breaks backbone-documentmodel :

model = models[i] = this.get(models[i]);   //(line 648) 
if (!model) continue;  //(line 649) 
  ...
this.length--;

the call to this.get(models[i]); is what breaks it because it returns undefined whereas it should return the model / attribute that preexisted in our nested collection. Since it is undefined the collection's length is not decremented appropriately by backbone hence our wrong collection length during our seccond call to set() on our model.

I have yet to figure out how to fix this and if it is only related to primitive arrays turned into documentmodel collections or not.

Any hints appreciated.

P.S. this project does not seem very active lately. Is anyone still supporting it ?

azpublic commented 9 years ago

Ok so here's the final test that actually reproduces the problem. It had nothing to do with fetch() or with a parent DocumentColelction. The problem was actually with DocumentModels containing arrays of primitives. My pull request with fix will follow.


                var json1 = {
                        "objectId": "545c8659e7e0a59538543a79",
                        "jobTasksByDate": [
                            "task1"
                        ]
                    };
                var json2 = {
                        "objectId": "545c8659e7e0a59538543a79",
                        "jobTasksByDate": [
                            "task2"
                        ]
                    };
                var json3 = {
                        "objectId": "545c8659e7e0a59538543a79",
                        "jobTasksByDate": [
                            "task3"
                        ]
                    };

                var j = new Job10(json1);

                j.set(json2);
                j.set(json3);
odbol commented 9 years ago

Hmmm... I think I may have found the same issue. Here's my workaround:

https://github.com/icereval/backbone-documentmodel/pull/25

Also includes a good test written if you want to check your solution. Here's an additional test that fails right now:

https://github.com/odbol/backbone-documentmodel/commit/90574963e1bac1c0af3ee4eb532c936a1db8caef