Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Add an attributes parameter to toJSON on model, which returns whitelisted properties. #573

Closed
wants to merge 1 commit into from

5 participants

@keithamus

Add an attributes parameter to toJSON method which, when passed, runs a whitelist filter on returned attributes

Example Use:
model.toJSON(['one', 'two']) == {one: 1, two: 2}

Use Case:

When getting JSON representations of the model we want to send certain attributes to certain endpoints, and other attributes to other endpoints

@keithamus keithamus Add an attributes parameter to toJSON method which, when passed, runs…
… a whitelist filter on returned attributes

Example Use:
    model.toJSON(['one', 'two']) == {one: 1, two: 2}

Use Case:

  When getting JSON representations of the model we want to send certain attributes to certain endpoints, and other attributes to other endpoints

Co Authors:
  Keith Cirkel
  Lenny Martin
b3904be
@bruth

I like the idea and could also make use of differentiating data being sent to various endpoints. The one issue here (though one could say it's out of scope) is that nested data (a key points to an object) cannot be whitelisted. That is relatively minor though and for API simplicity sake, it probably shouldn't be added.

From a endpoint perspective, what are your thoughts on using names to reference either an array of whitelisted properties or to handle more dynamic requirements (nested data), a function that returns acts as the pre-processor for the toJSON method.

The typical answer is to override toJSON, but if written correctly, models can be reused (DRY) to target various endpoints depending on the context. I envision something like this contrived example:

var Model = Backbone.Model.extend({
    ...
    endpoint1: ['id', 'foo', 'bar'],
    endpoint2: function() {
        var attrs = _.clone(this.attributes);
        delete attrs['something']['uninteresting']
        attrs['something']['interesting'] = { ... }
        return attrs;
    }
});

var model = new Model;
model.toJSON(); // typical behavior
model.toJSON('endpoint1'); // only 'id', 'foo', 'bar'
model.toJSON('endpoint2'); // uses the endpoint2 method to get the data

Of course the power of this can be coupled with extending the toJSON method to alter the data for all endpoints.

@tbranyen
Collaborator

@bruth re: endedpoint2 why would you not just call it explicity?

var model = new Model;
model.endpoint2(); // uses the endpoint2 method to get the data
@bruth

Sorry I didn't finish my thought. I could imagine it becoming an option that is passed in to the various CRUD methods specifying the pre-processor/white listed attributes since Backbkne.sync calls toJSON.

model.save({preproc: 'endpoint2'});

(disclaimer: the names I am using are kind of terrible..)

@jashkenas
Owner

Yep -- by all means, the right thing to do here is to override toJSON().

@jashkenas jashkenas closed this
@keithamus

I still think the original pull request has validity. Could you tell me why it has been declined?

@jashkenas
Owner

Sure -- because it's just as easy, and more flexible, to override toJSON to provide this feature ... either just returning the appropriate attributes, or adding the functionality in this ticket.

90% of the time, you don't need to filter model attributes because you just ignore the ones you don't care about.

@dmitry

What do you think about the case, when there are some dirty attributes that have been changed, but should not be sent to the server after the save method invoke. Right now I've just created method that selects only these attributes that need to be sent right now.

saveAttributes: (attributes, options={}) ->
  data = {}
  _(attributes).each (attribute) =>
    data[attribute] = @get(attribute)

  params =
    data: $.param(data)

  _.extend(params, options)

  Backbone.sync('update', @, params)

But the same method I need to use in other models too. So actually easy to extend other models, but it feels a little dirty, and I guess there are must have something similar like that.

Please let me know any thoughts on that.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 19, 2011
  1. @keithamus

    Add an attributes parameter to toJSON method which, when passed, runs…

    keithamus authored
    … a whitelist filter on returned attributes
    
    Example Use:
        model.toJSON(['one', 'two']) == {one: 1, two: 2}
    
    Use Case:
    
      When getting JSON representations of the model we want to send certain attributes to certain endpoints, and other attributes to other endpoints
    
    Co Authors:
      Keith Cirkel
      Lenny Martin
This page is out of date. Refresh to see the latest.
Showing with 47 additions and 2 deletions.
  1. +17 −2 backbone.js
  2. +30 −0 test/model.js
View
19 backbone.js
@@ -165,8 +165,23 @@
initialize : function(){},
// Return a copy of the model's `attributes` object.
- toJSON : function() {
- return _.clone(this.attributes);
+ toJSON : function(attributes) {
+ var returnedAttrs = {};
+ if (!attributes) {
+ return _.clone(this.attributes);
+ } else if (!_.isArray(attributes)) {
+ attributes = _.keys(attributes);
+ }
+
+ var i = attributes.length;
+
+ while(i--) {
+ if (this.attributes.hasOwnProperty(attributes[i])) {
+ returnedAttrs[attributes[i]] = this.attributes[attributes[i]];
+ }
+ }
+
+ return returnedAttrs;
},
// Get the value of an attribute.
View
30 test/model.js
@@ -422,5 +422,35 @@ $(document).ready(function() {
b = new B({a: a});
a.set({state: 'hello'});
});
+
+ test("Model: toJSON returns expected JSON", function() {
+ var A = new (Backbone.Model.extend())();
+
+ A.set({one: 1, two: 2});
+
+ deepEqual(A.toJSON(), {one: 1, two: 2}, 'Expects the toJSON object to return {one:1,two:2}');
+ });
+
+ test("Model: toJSON returns expected JSON, when provided attributes parameter", function() {
+ var A = new (Backbone.Model.extend({
+ defaults: {
+ 'x': 'y'
+ }
+ }))();
+
+ A.set({one: 1, two: 2});
+
+ deepEqual(A.attributes, {x: 'y', one: 1, two: 2}, 'Ensure the attributes are as expected');
+
+ deepEqual(A.toJSON(['one']), {one: 1}, 'Expects the toJSON object to return {one:1}');
+
+ A.set({three: 3, four: 4});
+
+ deepEqual(A.toJSON({one:true,two:true}), {one: 1, two: 2}, 'Expects the toJSON object to return {one:1,two:2}');
+
+ deepEqual(A.toJSON({one:true,fake:true}), {one: 1}, 'Expects the toJSON object to return {one:1}');
+
+ deepEqual(A.toJSON(['one', 'x']), {one: 1, x: 'y'}, 'Expects the toJSON object to return {one:1,x:"y"}');
+ });
});
Something went wrong with that request. Please try again.