Is there a standard way to deal with non-saveable values in Backbone.
e.g.
MyModel = Backbone.extend(Backbone.Model, {
initialize: function () {
this.set({'inches': this.get('mm') / 25});
}
})
If I call save() on this model it will throw an error as there is no corresponding database field for inches
. I can think of a few ways to fix this, but am wondering if there's a tried and tested approach generally best used for this?
At the moment my preferred solution is to extend Backbone's toJSON
method and to allow passing of a boolean parameter dontCleanup
to allow for it to still return all the model's values (including the non saveable ones) when it's needed e.g. for passing to a template.
I think this should do it. Define your
Model
defaults
as your valid schema and then return only the subset ofthis.attributes
that is valid duringtoJSON
.Note that
_.pick
would make the code a bit shorter once you have underscore 1.3.3 available. I haven't seen a "tried and tested" convention in my travels through the backbone community, and since backbone leaves so many options open, sometimes conventions don't emerge, but we'll see what this stackoverflow question yields.Dealing with non-persisted attributes in Backbone.js has been doing my head in for a while, particularly since I've started using ember/ember-data, which handles the various situations through computed properties, ember-data attributes, or controllers.
Many solutions suggest customising the
toJSON
method. However, some popular Backbone plugins (particularly those that deal with nested models), implement their owntoJSON
method, and make a call toBackbone.Model.prototype.toJSON
to obtain an object representation of a model's attributes. So by overwriting thetoJSON
method in a model definition, you'll lose some (potentially crucial) features of those plugins.The best I've come up with is to include an
excludeFromJSON
array of keys in the model definition, and overwrite thetoJSON
method onBackbone.Model.prototype
itself:In this way, you'll only have to define the non-persisted keys (if you forget to do so, you'll soon be reminded when your server throws an error!).
toJSON
will behave as normal if noexcludeFromJSON
property is present.In your case,
inches
is a computed property, derived frommm
, so it makes sense to implement this as a method on your model (ensuring that the value for inches is correct when mm is changed):However, this has the downside of being accessed differently to everyother attribute. Ideally you'll want to keep it consistent with accessing other attributes. This can be achieved by extending the default
get
method:Which will let you do:
This approach doesn't touch the underlying
attributes
hash, meaning thatinches
will not appear in thetoJSON
representation, unless youset
the value ofinches
later on, in which case you'll need something like theexcludeFromJSON
array.If you have the need to
set
theinches
value, you may also want to listen for changes and adjust the value ofmm
:See the complete example on JSBin.
It's also worth noting that the (official?) purpose of the
toJSON
method has recently been redefined as preparing a model for syncing with a server. For this reason, callingtoJSON
should always return only the "persistable" (or "saveable") attributes.I like Peter Lyon's idea. I've thought about that a few times, but never actually put it in place. For all the ways that I have handled this, though, here are my two favorites:
Non-Attribute Values
This one is simple: don't store the values you need in the model's standard attributes. Instead, attach it directly to the object:
The big problem here is that you don't get all of the events associated with calling
set
on the model. So I tend to wrap this up in a method that does everything for me. For example, a common method I put on models isselect
to say that this model has been selected:In your case, I'm not sure this would be a good approach. You have data that needs to be calculated based on the values that are in your attributes already.
For that, I tend to use view models.
View models.
The basic idea is that you create a backbone model that is persist-able, as you normally would. But the you come along and create another model that inherits from your original one and adds all the data that you need.
There are a very large number of ways that you can do this. Here's what might be a very simple version:
The big benefit of wrapping this with
Object.create
is that you now have a prototypal inheritance situation, so all of your standard functionality from the model is still in place. We've just overridden thetoJSON
method on the view model, so that it returns the JSON object with theinches
attribute.Then in a view that needs this, you would wrap your model in the initialize function:
You could call
new MyViewModel(this.model)
if you want, but that's not going to do anything different, in the end, because we're explicitly returning an object instance from theMyViewModel
function.When your view's render method calls
toJSON
, you'll get theinches
attribute with it.Of course, there are some potential memory concerns and performance concerns with this implementation, but those can be solved easily with some better code for the view model. This quick and dirty example should get you down the path, though.