diff --git a/ampersand-model.js b/ampersand-model.js index 22cb315..2c8bf8d 100755 --- a/ampersand-model.js +++ b/ampersand-model.js @@ -5,6 +5,10 @@ var assign = require('lodash.assign'); var isObject = require('lodash.isobject'); var clone = require('lodash.clone'); var result = require('lodash.result'); +var transform = require('lodash.transform'); +var keys = require('lodash.keys'); +var intersection = require('lodash.intersection'); +var reduce = require('lodash.reduce'); // Throw an error when a URL is needed, and none is supplied. var urlError = function () { @@ -20,6 +24,31 @@ var wrapError = function (model, options) { }; }; +// recursive function to find the attrs in the serialized object +var transformForPatch = function transformForPatch(obj, attrs) { + + if (!isObject(obj)) { + return obj; + } + + var attrKeys = keys(attrs); + + // If all attributes are within the object + if (intersection(keys(obj), attrKeys).length === attrKeys.length) { + // return the subset of passed-in attrs + return reduce(attrKeys, function(redux, attrKey) { + redux[attrKey] = attrs[attrKey]; + return redux; + }, {}); + } + + return transform(obj, function(result, val, key) { + // run transformForPatch on the next level + result[key] = transformForPatch(val, attrs); + }); +}; + + var Model = State.extend({ save: function (key, val, options) { var attrs, method; @@ -60,10 +89,18 @@ var Model = State.extend({ wrapError(this, options); method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); - if (method === 'patch') options.attrs = attrs; + + if (method === 'patch') { + options.attrs = transformForPatch(this.serialize(), attrs); + } // if we're waiting we haven't actually set our attributes yet so // we need to do make sure we send right data - if (options.wait && method !== 'patch') options.attrs = assign(model.serialize(), attrs); + if (options.wait && method !== 'patch') { + var clonedModel = new this.constructor(this.getAttributes({props: true})); + clonedModel.set(attrs); + + options.attrs = clonedModel.serialize(); + } var sync = this.sync(method, this, options); // Make the request available on the options object so it can be accessed diff --git a/package.json b/package.json index bcbbcda..4c0326a 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,12 @@ "ampersand-version": "^1.0.2", "lodash.assign": "^3.2.0", "lodash.clone": "^3.0.3", + "lodash.intersection": "^3.2.0", "lodash.isobject": "^3.0.2", - "lodash.result": "^3.1.2" + "lodash.keys": "^3.1.2", + "lodash.reduce": "^3.1.2", + "lodash.result": "^3.1.2", + "lodash.transform": "^3.0.4" }, "devDependencies": { "browserify": "^8.1.0", diff --git a/test/backbone.js b/test/backbone.js index da12d4e..744d689 100755 --- a/test/backbone.js +++ b/test/backbone.js @@ -870,6 +870,21 @@ var Backbone = { t.equal(env.syncArgs.options.attrs.a, undefined); }); + test("save with PATCH and `wait` with a custom serialize method only sends specified attributes in custom format", function (t) { + t.plan(4); + var Model = Backbone.Model.extend({ + id: 1, + serialize: function() {return {data: {attributes: this.getAttributes({props: true}, true)}};} + }); + var model = new Model({parent_id: 1}); + model.set({a: 1, b: 2, d: 4, e: 5}); + model.save({b: 2, d: 4}, {patch: true, wait: true }); + t.equal(env.syncArgs.method, 'patch'); + t.equal(_.size(env.syncArgs.options.attrs.data.attributes), 2); + t.equal(env.syncArgs.options.attrs.data.attributes.d, 4); + t.equal(env.syncArgs.options.attrs.data.attributes.a, undefined); + }); + test.skip("save doesn't validate twice", function (t) { t.plan(1); var model = new Backbone.Model();