I'm using Backbone with Rails. I have a model that I can create and destroy just fine. When I edit, though, I get this error:
Can't mass-assign protected attributes: created_at, id, updated_at
That makes sense. Those attributes are protected and they should be protected. Backbone shouldn't be trying to update these attributes, but Backbone doesn't know better.
One option, of course, would be to remove params[:created_at], etc. in my Rails controller, but I can imagine that getting really un-DRY pretty quick, and plus it just seems wrong to have to do that.
Is there a way for me to tell Backbone not to include these attributes in its forms?
Either don't send them to the client so that your Backbone model never knows about them or override toJSON in your model to exclude them.
The default toJSON implementation is very simple:
toJSON: function() {
return _.clone(this.attributes);
}
so you can replace it with this:
toJSON: function() {
var attrs = _(this.attributes).clone();
delete attrs.created_at;
delete attrs.updated_at;
return attrs;
}
You could even monkey patch that right into Backbone.Model.prototype if that made sense to you.
The downside of altering toJSON is that toJSON tends to do double duty in Backbone:
toJSON is used to serialize models and collections for the server.
toJSON is used to serialize models and collections for views.
If you still want to use updated_at and created_at in views then I'd recommend adding another method, say serialize_for_view, that does what the standard toJSON does:
serialize_for_view: function() {
return _(this.attributes).clone();
}
and then use things like var html = this.template({m: this.model.serialize_for_view()}) to build your view's HTML. You could also monkey patch serialize_for_view into Backbone.Model.prototype if you wanted to use it everywhere.
I found that putting
model.unset("created_at");
model.unset("updated_at");
model.save()
fixed the problem. This won't work if you need those attributes, but if they are not needed, this works.
Related
For most JS frameworks and libraries, the value they bring are often in the form of a new structure as to how to build an application (Backbone, React), or new ideas that effectively power-up the language (Angular), or simply the methods they offer are well tested, fast, and really convenient (jQuery).
Usually the ideas and methods they provide are pretty straightforward usage of JavaScript, but with a very clever team behind it that find interesting ways to do things which you can think through and get a solid guess as to how the guts work.
However, I've been unable to think through the ability to two-way bind JS models to view components. What is the secret sauce at the heart of this feature that makes this work? Changing an internal variable from a user input is simple, but what about the reverse? How would you be able to "know" when a JS variable has changed in order to update the display instantly? Surely it can't be polling, so what then?
Whenever a block of your JS runs that angular triggered it will run a digest cycle when the block finishes executing. This basically checks all the values that might of changed and would require updates to the view.
If angular didn't trigger the code then it won't know that something might of changed so your bindings can get out of sync. For example if you run something like this
setTimeout(function() {$scope.myValue = '123'});
Angular won't know that myValue changed and it actually won't update the view. That's why Angular has it's own services for doing everything. e.g. $timeout or $http.
If you have some callback function that Angular doesn't know about then you can manually tell it to check for changes by calling $scope.$apply()
there are several ways to do it. Object.observe is great, but lacks good support. You can poll for values as well, keeping a 2nd copy of the object around to compare. You can also write your own explicit set/get methods to update the model like backbone does.
One neat method i use a lot is using getters/setters to keep the model synced to the dom:
//a demo "model" of data:
model = {
name: "Fred"
};
function change(k,v){alert([k,v]);} // a stand-in change monitor for demo
// iterate model and replace values with getter/setter combos:
Object.keys(model).forEach(function(key) {
var val = model[key];
delete model[key];
Object.defineProperty(model, key, {
get: function() {
return val;
},
set: function(v) {
val = v;
change(key, val);
} //call change upon setting
});
change(key, val); //update view "onload"
}); // alerts "Fred";
//update model (fires change() with "name" and "sally" arguments:
model.name="sally"; // alerts "sally";
the change function is quite simple and for your case should just find elements bound to keys.
the advantage here is that you don't need special custom CRUD methods, you can just modify the object properties via assignment like it's 1999. It also doesn't poll, and works correctly all the way back to IE9 and any other ES5 environments. It's the simplest way to bind JS>DOM (afaik) without custom methods.
It does have some limits: nested objects are tricky to get/set upon, you can't do the whole object at once, you can only "watch" primitives. Arrays are a problem too: you can't really replace expando properties with getters/setters without side-effects. But, upon a relatively flat collection of JSON-safe data, get/set works a charm and needs no complex libs to get operational.
checkout a complete example using this method: http://pagedemos.com/xg3szbguqnwu/4
I can speak to how it's done in Backbone, which has a relatively low-level perspective on data-binding.
It's a combination of 1. the library having control over attribute setter methods 2. invoking callback functions when attributes change (e.g. by dispatching events) in order to update the UI.
The essential pseudocode is this:
class Model:
method set(name, value):
if value != this.attributes[name]
this.triggerEvent('change', name, value)
this.attributes[name] = value
m = new Model()
someInputWidget.onEvent('userChangedInput', function(value) {
m.set(someInputWidget.name, value)
})
m.onEvent('change', function(name, value) {
getInputWidgetByName(name).setValue(value)
})
Backbone does not do any data binding to the UI, but you can refer to Backbone's annotated source for the actual event-dispatching implementation.
I want my model to validate for duplicate entry, so I need to access the collection while in the model.
I'm using Backbone.JS together with Require.JS and this makes it a little more confusing.
I can't seem to load my collection as a dependency for the module.
I tried doing validation in the view, but I guess the best practice would be to keep validation in the model where it should be.
Any advice?
From Backbone Collection docs:
If you're adding models to the collection that are already in the collection, they'll be ignored, unless you pass {merge: true} ...
Which in the add -method translates to this:
if (existing = this.get(model)) {
if (options.merge) {
existing.set(attrs === model ? model.attributes : attrs, options);
if (sort && !doSort && existing.hasChanged(sortAttr)) doSort = true;
}
continue;
}
Basically that will trump adding any model with duplicate id, cid or idAttribute.
If that isn't enough, then I suggest you do the validation in the view, because bringing a model's collection as a dependency to that exact model would introduce a circular dependency, eg. you need the collection to be able to compile the model, but you also need the model to compile the collection.
So if you need to make sure you're not adding a model to a collection with exactly the same attributes as a model already in that collection, do it in the view. It would look something like this:
if (collection.where(modelToBeAdded.toJSON()).length == 0) {
collection.add(modelToBeAdded);
}
All, I am a newbie of Backbone. and I am trying to understand the Model of Backone. Especially how to define a Model. so far, I didn't saw a clear or formal way about how to define a Model for backbone.
For example Let's see the set method in help doc .
set
model.set(attributes, [options])
Set a hash of attributes (one or many) on the model.
Say we have some code like below . I think set method actually is assign a javascript object to the Model.
window.Employee = Backbone.Model.extend({
validate:function(attrs){
for(var key in attrs){
if(attrs[key] == ''){
return key + "can not be null";
}
if(key == 'age' && isNaN(attrs.age)){
return "age is numeric";
}
}
}
});
....
var attr = {}; // I can't not sure what is {} mean.
$('#emp-form input,#emp-form select').each(function(){
var input = $(this);//using jquery select input and select. and enumerate all of them.
attr[input.attr('name')] = input.val();//I am not sure what does it means
});
if(employee.set(attr)){
Employees.create(employee);
}
....
in this example ,I didn't saw the classical way which we can see in java class or c# class to define the class fields or methods. but only see a validate function .Is there anybody who can tell me more about it to help me understand? thanks.
To define a model in Backbone you have to extend the Backbone.Model object. For example if you'll like to create a new User model you could write something like this:
var User = Backbone.Model.extend({})
You can also overwrite some model methods to fill your needs. For example you can change the urlRoot attribute to tell the model where should he fetch the data.
Backbone models contain your data in the attributes attribute. You change those attributes by using the model set method and you can read the value stored in the model using the get method. So if you had some inputs where a user can enter information, for example creating a new user with his name and email and you have a form with a text input for both of them. You could do domething like this:
var user = new User;
user.set('name', $('#name').val());
user.set('email', $('#email').val());
attributes = {
name: user.get('name'),
email: user.get('email')
};
user.save(attributes);
There are a lot of ways to re-factor this code to make it look better but it help to see how you could use those methods. You should check the Backbone documentation to explore how they work a little bit more. Hope this helps!
PD: In my example I set an attribute a time, but you could also send a hash of attributes to set more values in one call.
The model in JS is basically a wrapper for data, with CRUD and simple validation functions. To work properly you need to make server functions to work with (ajax), I think this tutorial says it all http://backbonetutorials.com/what-is-a-model/. Instead of database the model works with your application server side.
If you have custom actions (not just add/edit/remove) on your data, you can manually "set()" data, use "onchange" event and refresh your view when needed. You can even attach "onchange" events only on specific fields and make custom functions in your view to handle each special field (for validation or display).
You can define fields at initialize and defaults value, but not custom functions (ofc you can do model.customFuntion() but I don't recommend it.
In order to make it more "clasical way" you need to use the other Backbone functions http://backbonejs.org/#Collection-Underscore-Methods and Backbone.Collection.
If a viewmodel is already defined, either manually or automatically with mapping plugin, is there any problem to "extend" (add properties/functions) the viewmodel later in the code by using prototype?
I'm aware of the create callback of mapping plugin, I just want to know if there are any implications of using prototype instead? The reason I'm asking is because I'm generating large parts of the viewmodels from server side code, but sometimes need to extend the viewmodel later than at initial generation.
I don't think there is any problem with this, I work with a large deep view model graph instantiated via the mapping plugin from correspondingly structured JSON and I use prototypes to define an "AbstractViewModel" with useful properties and toJSON "overrides" among other things.
There is no problem with this. Just make sure that the view responds appropriately when there's no data in that particular field in the viewModel.
There seems to be a couple ways of going about this.
For one, you can take a single object view model and utils.extend them via prototype:
ko.utils.extend(ViewModelClass.prototype, {
newPrototype1: function () { ... },
newPrototype2: function () { ... }
}
Or, you can add an extender to knockout and invoke it via the observable object itself:
(http://knockoutjs.com/documentation/extenders.html)
ko.extenders.numeric = function(target, precision) {
...details in link above...
}
...
self.NumericProperty = ko.observable(data).extend({ numeric: 0 });
...
Or create a function that is available to all instances of an observable, observableArray, computed...
(http://knockoutjs.com/documentations/fn.html)
ko.observable.fn.filterByProperty = function(propName, matchValue) {
return ko.computed(function() {
...details in link above...
}, this);
}
I tend to use combinations of these. I like extending prototypes of View Models in a separate file from the VMs and Mappings, so I have a structure like
ViewModel(s).js
ViewModel(s).Mappings.js
ViewModel(s).Prototypes.js
As you'll see, the timespan between these 'answers' is rather large, so some things have changed yet some things remain the same.
I'm writing a practice Backbone app, with Rails backend API, and I'm confused about the behavior of save on Backbone models.
Let's say a Team has many Players, and I want to save a team with numerous players in a single POST.
So in Rails I have:
class Team < ActiveRecord::Base
has_many :players
accepts_nested_attributes_for :players
end
class Player < ActiveRecod::Base
belongs_to :team
end
and for backbone client, I have a Player model and a Players collection defined (not shown)
and then the containing Team model (NOTE: no Teams collection)
Demo.Models.Team = Backbone.Model.extend({
urlRoot: '/teams',
defaults: {
'team_size': 12
},
initialize: function() {
this.players = new Demo.Collections.Players());
},
toJSON: function() {
var json = _.clone(this.attributes);
json.players_attributes = this.players.map(function(player) {
return player.toJSON();
});
return json;
}
}
When I examine my stringified JSON in the browser, everything looks good:
{"team_size":12, "players_attributes":[{"name":"Fred"},{"name":"Jim" },{"name":"Mark"}]}
Checking the server logs, the lone top level attribute ('team size') is repeated, once at the top level, and then repeated under a root key.
Started POST "/teams" for 127.0.0.1 at 2012-06-07 13:39:40 -0400
Processing by TeamsController#create as JSON
Parameters: {
"team_size"=>12, "players_attributes":[{"name":"Fred"},{"name":"Jim" },{"name":"Mark"}]},
"team"=>{"team_size"=>12}
}
I have a few questions:
What's the best way to ensure the player_attributes are nested inside the root key? I (So that I can do a nested save inside TeamController, in the standard rails manner: (i.e. Team.create(params[:team]) ) I can accomplish this with some javascript hackery inside toJSON, but I'm guessing there's an easier, cleaner way.
Is this standard, desirable behaviour? To send duplicates of attributes like this? I guess there's no harm, but it doesn't smell right.
Am I not defining the url / urlRoot correctly or some such?
thanks
1- You have to override the toJSON method in order to include the model name as the root of the JSON element sent to the server.
toJSON: function() {
return { team: _.clone( this.attributes ) }
},
Since you are already messing and overriding this method I don't see any reasons not to go this way.
2- This is a very strange behavior you're describing. Try:
class Team < ActiveRecord::Base
self.include_root_in_json = false
end
It will probably eliminate Rails duplicate params parsing. Another advantage you get from this is that Rails won't include the team as a root element of its generated JSON to the client.
3- Your definition of urlRoot is just fine.
I arrived here while looking for same issue. So even it's an old question I think it's worth giving the answer.
I actually found a Rails setting that explain these duplicate attributes: wrap_parameters
http://apidock.com/rails/v3.2.13/ActionController/ParamsWrapper/ClassMethods/wrap_parameters
Just set it to an empty array, and rails won't try to wrap parameters coming from your JSON requests.
Although you can use the toJSON hack mentioned by others, this is actually not such a good idea. For one, it produces an inconsistent result between sync and save with {patch: true} (this inconsistency is because the sync method calls toJSON if you don't patch, but doesn't call toJSON if you have patch set to true)
Instead, a better solution is to use a patched version of Backbone that overload the sync method itself. The backbone-rails gem does this automatically, or you can pull backbone_rails_sync.js into your own app. A more complete answer to this question can be found here: Backbone.js and Rails - How to handle params from Backbone models?