diff --git a/src/deep-model.js b/src/deep-model.js index 0a4ee17..94de0e7 100644 --- a/src/deep-model.js +++ b/src/deep-model.js @@ -58,7 +58,12 @@ if (return_exists && !_.has(result, fields[i])) { return false; } - result = result[fields[i]]; + if (result instanceof Backbone.Model) { + result = result.get(fields[i]); + } + else { + result = result[fields[i]]; + } if (result == null && i < n - 1) { result = {}; @@ -98,7 +103,12 @@ //If the last in the path, set the value if (i === n - 1) { - options.unset ? delete result[field] : result[field] = val; + if (result instanceof Backbone.Model) { + options.unset ? result.unset(filed, options) : result.set(field, val, options); + } + else { + options.unset ? delete result[field] : result[field] = val; + } } else { //Create the child object if it doesn't exist, or isn't an object if (typeof result[field] === 'undefined' || ! _.isObject(result[field])) { @@ -106,13 +116,19 @@ } //Move onto the next part of the path - result = result[field]; + //Jump into the Backbone object's attributes instead of its internal properties + if (result instanceof Backbone.Model) { + result = result.get(field); + } + else { + result = result[field]; + } } } } - function deleteNested(obj, path) { - setNested(obj, path, null, { unset: true }); + function deleteNested(obj, path, options) { + setNested(obj, path, null, _.extend(options || {}, { unset: true })); } var DeepModel = Backbone.Model.extend({ @@ -194,11 +210,11 @@ //: Using getNested, setNested and deleteNested if (!_.isEqual(getNested(current, attr), val)) changes.push(attr); if (!_.isEqual(getNested(prev, attr), val)) { - setNested(this.changed, attr, val); + setNested(this.changed, attr, val, options); } else { deleteNested(this.changed, attr); } - unset ? deleteNested(current, attr) : setNested(current, attr, val); + unset ? deleteNested(current, attr) : setNested(current, attr, val, options); // } diff --git a/test/deep-model.test.js b/test/deep-model.test.js index caf44ec..bd5e173 100644 --- a/test/deep-model.test.js +++ b/test/deep-model.test.js @@ -940,4 +940,122 @@ test('set: Trigger model change:[attribute] event for parent keys (like wildcard ok(triggered); })(); }); + + +test('set: calls setters on backbone object rather than changing object directly when passed object literal', function() { + var model = new Backbone.DeepModel(); + var value = 'value'; + model.set({ nested: new Backbone.DeepModel() }); + + model.set({ nested: { property: value }}); + + ok(model.get('nested') instanceof Backbone.DeepModel); + ok(_.isUndefined(model.get('nested').property)); + strictEqual(model.get('nested').get('property'), value); +}); + +test('set: calls setters on backbone object rather than changing object directly when passed dot notated string', function() { + var model = new Backbone.DeepModel(); + var value = 'value'; + model.set({ nested: new Backbone.DeepModel() }); + + model.set('nested.property', value); + + ok(model.get('nested') instanceof Backbone.DeepModel); + ok(_.isUndefined(model.get('nested').property), 'property is not set on model object itself'); + strictEqual(model.get('nested').get('property'), value, 'property is set on models attributes'); +}); + +test('set: calls set method on backbone object rather than changing attributes hash directly', function() { + var model = new Backbone.DeepModel(); + var value = 'value'; + var setCalled = false; + var CustomModel = Backbone.DeepModel.extend({ + set: function () { + setCalled = true; + } + }); + model.set('nested', new CustomModel()); + + model.set('nested.property', value); + + ok(setCalled, 'set function called on nested object rather than modifying attributes directly'); +}); + +test('set: silent param propagated to nested models set options with expanded syntax', function() { + var model = new Backbone.DeepModel(); + var setCalledWithSilent = false; + var CustomModel = Backbone.DeepModel.extend({ + set: function (key, val, options) { + setCalledWithSilent = options && options.silent; + } + }); + model.set('nested', new CustomModel()); + + model.set('nested.property', 'value', { silent: true }); + + strictEqual(setCalledWithSilent, true, 'set function called on nested object with silent attribute passed'); +}); + +test('get: calls getters on backbone object rather than accessing object directly', function() { + var model = new Backbone.DeepModel(); + var value = 'value'; + + model.set({ nested: new Backbone.DeepModel({ property: value }) }); + + strictEqual(model.get('nested.property'), value); +}); + +test('get: calls get method on backbone object rather than accessing attributes hash', function() { + var model = new Backbone.DeepModel(); + var value = 'value'; + var getCalled = false; + var CustomModel = Backbone.DeepModel.extend({ + get: function () { + getCalled = true; + } + }); + model.set({ nested: new CustomModel() }); + + model.get('nested.property'); + + ok(getCalled, 'get function called on nested object rather than accessing attributes directly'); +}); + +test('get: supports retrieving multi-tier nested models', function() { + var model = new Backbone.DeepModel(); + var value = 'happy'; + model.set({ + one: new Backbone.DeepModel({ + two: new Backbone.DeepModel({ + three: new Backbone.DeepModel({ + property: value + }) + }) + }) + }); + + strictEqual(model.get('one.two.three.property'), value, 'property on nested models is set appropriately'); + strictEqual(model.get('one').get('two').get('three').get('property'), value, 'property is set on models attributes not on the object'); +}); + +test('set: supports setting multi-tier nested models', function() { + var model = new Backbone.DeepModel(); + var value = 'happy'; + model.set({ + one: new Backbone.DeepModel({ + two: new Backbone.DeepModel({ + three: new Backbone.DeepModel({ + property: 'sad' + }) + }) + }) + }); + + model.set('one.two.three.property', value); + + strictEqual(model.get('one.two.three.property'), value, 'property on nested models changed'); + strictEqual(model.get('one').get('two').get('three').get('property'), value, 'property is set on models attributes not on the object'); +}); + // - @restorer