I want to fetch an api and the format is like apps/[:id]/result.
For most of the example or documentation about setting the urlRoot is based on having the same root url.
But my problem is the api share the same root and the end of the api name.
I try to use below code, but it doesn't work.
var Model = Backbone.Model.extend({
urlRoot: "/apps"
})
var model = new Model({id: 123123 + "/content"]}) //123123 just fake id
Does it any way to change only the middle of the id?
Or in this situation, use urlRoot is inappropriate?
Backbone's persistence methods, which are built around url and urlRoot, are designed for RESTful persistence. Each method is designed to communicate with a server using well-defined semantics (GET /path means list, PUT /path/:id means update, etc).
Chaning the meaning of id is a bad idea because you depart break those semantics, meaning that a) your model will not work with create, update or delete REST operations, and b) your code will become difficult to understand since id will no longer be a descriptive variable name. Backbone uses id frequently in Models and Collections, so it would be a very bad idea to change its meaning.
If you are not using a REST API, or you have a particular operation that does not fit that paradigm (like searching), it is best to implement your own methods to make custom HTTP requests.
In your case, something like this should work as you expect:
var Model = Backbone.Model.extend({
urlRoot: "/apps",
fetchResult: function() {
return $.ajax({
url: this.url() + '/result'
}).then(function(response) {
// do something with response
// return response, or the result of your processing,
// for downstream promise handlers
return response;
}, function() {
console.error('fetchResult failed');
})
},
})
With this approach the RESTful URLs work as designed and you don't lose the semantics of id.
Related
I've really be trying to wrap my head around this as much as possible, but having a very difficult time doing so. Maybe I'm missing the obvious.
Given a typical REST API (with an idempotent update method and a create method):
http://www.domain.com/api/clients GET // returns all clients
http://www.domain.com/api/clients POST // create a new client
http://www.domain.com/api/clients/:id GET // returns single client
http://www.domain.com/api/clients/:id PUT // updates a single client - idempotent
http://www.domain.com/api/clients/:id DELETE // delete single client
If I create a standard resource with the following URL:
Client = $resource("http://www.domain.com/api/clients/:id")
Then I automatically get (where Client is the $resource and client is the returned entity):
Client.get()
Client.query()
Client.save()
client.$save()
client.$remove/delete()
The problem I have is by default there is no PUT method to save (typically used to identify idempotent updates).
Am I misunderstanding something or is this a deficiency in Angular's API? I would have expected the $save() to use a PUT and not a POST. The way it is currently structured, I have to create my own $update() method definition and then rely on the developer not to accidentally use the $save() method.
Am I structuring my API incorrectly? Should the REST API be structured differently?
You can simply specify the method in your resource like :
app.factory('someFactory', ['$resource', function($resource) {
return $resource('/api/:id', {
id: '#id'
}, {
update: {
method: 'PUT'
},
get: {
method: 'GET'
}
});
}]);
but I totally agree with $save being an odd verb for create and not update. This guy does too and it looks like he made a way to dual purpose the save by simply extending the object and checking for an id.
I apologize if this is a real newbie question: I'm coming to AngularJS and restangular as a cold start - I'm porting some older, back-end oriented code into this fancy new world of SPAs and XHRs.
Anyway, this is really simple: I want to load an object from my API. I'm able to do that reasonably easily this way:
App.factory('User', function($cookies, Restangular) {
if (!$cookies.user_id) {
return {name: 'Nobody', userId: -1};
} else {
return Restangular.one('users', $cookies.user_id).get();
}
});
App.controller('ApplicationCtrl', function ($scope, User) {
$scope.user = User;
});
This will (as I understand it) let me inject the current user wherever I need it (and if the cookies aren't right, they end up getting a 404 or 401 on the API call, depending.
The simple problem I'm having now is that when I want to access this user object in my templates, it's not bound to {{user}}, but rather {{user.user}}, as I injected $scope.user with my User object, which was a JSON-created object that restangular fetched (it looked like {"user":{"id":2,"name":"Eric Eslinger","email":"eric.eslinger#gmail.com"}} so if I want to say, "hello username" I have to say "hello, {{user.user.name}}". Which is unsatisfying.
Any advice about what I'm missing in setting up my object heirarchy is welcome; I have a feeling I'm just missing something simple but also important that I'm going to need to worry about later.
To answer my own question, this isn't really a restangular thing, it's a general angular thing (ngResource behaves the same way) and is a bit of orthoganality to how EmberJS works. In EmberJS, (or at least the version of Ember-Data that I was using), JSON api responses are expected to have a root element, which Ember-Data kind of disregards (or uses to type the result objects). $resource and restangular attend to the root elements. So you can alter your api to just return {"id:2" ... instead of {"user":{"id":2 ... or you can do a transformResponse on the result. Your call.
Since I'm writing both the API and the front end, and there's nobody else using this API right now (once it stabilizes, that will change), I just went and changed how the API output JSON. The backend uses active_model_serializers, so I just put a , root: false in a few choice locations for now. Easy enough.
I want to call save on a Backbone model and have it write data to the server, but not update the client. How do I do this?
To clarify: when Backbone saves a model, it sends all the data to the server, and then retrieves the data from the server and updates it on the client. This second step is what I want not to happen.
To clarify further: The model ('parent' model) has an attribute which is itself a model ('child' model); when it's saved to the server, this child model is converted to JSON. When the parent model updates after the save, the attribute that previously contained a reference to the child model is replaced with the parsed JSON object of the child model that was saved. This is what I need not to happen.
When the data is initially pulled from the server, the parent model "reconstitutes" that object into an appropriate child model, but this is an expensive process and there is no reason to re-do it every time save fires on the parent model, since the child model will never change.
It sounds like you do not want to parse your model when you receive the response from the server on a model.save
You can try something such as:
model.save(attributes,{
success: function() { ... },
parse : false // This will be passed to your parse function as an option
});
You would have to set-up your parse function in your corresponding model as follows:
parse: function(resp,options) {
// don't update model with the server response if {parse:false} is passed to save
if (options && !options.parse) return;
/ ... rest of your parse code ... /
Backbone currently defaults options.parse to true. Here is a short-discussion on the topic.
As discussed in that thread, perhaps you want to consider why you do not want want to update the server response to the client. There may be a cleaner way to achieve the results you desire.
Depending on how/what your server setup is, all you really have to do is issue a regular AJAX request. This is exactly what backbone does in the background so you'll just bypass the client side logic.
You could do this with native JavaScript, but I'm fairly sure you have some other library in use that can make things much easier.
For the completeness of this answer, I'll give an example with jQuery:
$.ajax({
type: "POST",
url: "http://same.as.your.model",
data: { "the" : "model" },
dataType: "JSON",
success: function(){
// once the request has returned
}
});
The $.ajax function also has some additional functionality, and you can read about it in the jQuery docs.
On client you mean Views? If you want to save your model but not render your views which happens since save will trigger a change event, you should call save with option silent:true, or set a custom option like dontchange:true when calling save and check it in when handling change. I prefer the custom option, because silent has side effects (at least in my version of backbone 1.0.0)
a little code:
when you save:
model.save({},{dontchange: true});
you install your event listeners in the view:
this.listenTo(model, 'change', function(model, options){
if (options.dontchange)
return;
this.render();
});
I ran into same problem. model.save(attrs,{patch:true, parse:false}) really did not invoke parse method but model was still merged with server response.
It is not elegant, but this worked for me:
model.clone().save(attrs,{patch:true})
I believe it's best to avoid this situation by clean REST api design.
I've been following some Backbone.js tutorials and am a bit confused about when to use 'url' inside Model when there is also a Collection which contains the model. For example I see a lot of code that looks like this, in order to synchronize with the server:
var PostModel = Backbone.Model.extend({});
var PostsModel = Bacbone.Collection.extend({
model: PostModel,
url: "/posts"
});
However I also see some where the model also has 'url' property, like so:
var PostModel = Backbone.Model.extend({
url: "/posts"
});
var PostsModel = Bacbone.Collection.extend({
model: PostModel,
url: "/posts"
});
I think I understand the first method, where I interact with the models only through the collections to which they belong, but I am not sure when you would want to have urls for both a model and its collection.
Sometimes you need a model that doesn't belong to any collection.
For example a login/sign in.
var LoginModel = Backbone.Model.extend({
url: "/login"
});
This way you can interact with login view and model just like you would with any other Backbone model, including save() (which posts the login form to the server) and validation.
There is however no login collection, so it doesn't make sense to model that.
Maybe the code you're seeing is something in between. Models are accessed via collection in some places of the application and without collection in others.
If it's a good design is another story, but there's no technical reason to not do it.
edit
As you noticed, using urlRoot is another option, since by default the url attribute is a method that uses urlRoot to construct the relative url.
It can be, however, given a string value to specify the model url directly.
What is the right way to perform PATCH request while saving model's attributes in Backbone.js?
As of Backbone.js v0.9.9, you can simply pass { patch: true } to save().
Read more: http://backbonejs.org/#changelog
In addition to James Cropchos answer I want add the following, because this stole some hours from me and maybe helps someone else:
If you use model.save(attributesToPatchObject,{patch: true}) like it is possible since backbone v.0.9.9 as stated in James Cropchos answer, you may wonder how to determine which attributes have changed since the last call of model.save() to pass them in as attributesToPatchObject which is the first argument from model.save() (or model.fetch() if you didn't save the model lately).
Backbone itself didn't keep track of those attributes. I thought the method model.changedAttributes() could fit, but as the backbone-doc says this method returns
a hash of only the model's attributes that have changed since the last
set, or false if there are none
So this method didn't fit for this need. After some research I found out that backbone itself didn't keep track of unsaved attributes (I know, not a brilliant finding if I had read the docs more carefully).
I found out that backbone.trackit is a backbone plugin which exactly add the needed feature to backbone, by adding the method unsavedAttributes() to the model. The docs of backbone.trackit says about this method:
Symmetric to Backbone's model.changedAttributes(), except that this returns a hash of the model's attributes that have changed since the last save, or false if there are none. Like changedAttributes, an external attributes hash can be passed in, returning the attributes in that hash which differ from the model.
It works like this:
//fetch an existing model from server
model.fetch({
success : function(model, respose, options) {
//tell backbone.trackit to track unsaved Attributes
model.startTracking();
}
});
//now some changes to the model happen
model.set("someProperty", "someValue");
/* save the model to server using the PATCH-Method
and only send the unsaved Attributes;
in this case only "someProperty" is sent
*/
model.save(model.unsavedAttributes(), {patch: true});
Since the unsavedAttributes() returns false if there are no unsaved Attributes, you could additionally wrap your save() statement within an if-condition which checks if unsavedAttributes() returns something other then false and only do your PATCH-Request if it's needed (because something changed).
NOTE: You didn't have to call fetch() to use startTracking() so you can use this method even with newly created models (model.isNew() returns true on that model), if there is a usecase for that.
Hopes this may save someone a little bit of research time.
You'll have to override Backbone.sync and extend the existing method mapper
var methodMap = {
'create': 'POST',
'update': 'PUT',
'delete': 'DELETE',
'read': 'GET',
'patch': 'PATCH'
};
you'll have to create your own patch method on a model like
Backbone.Model.prototype.patch = function(options)
{
// some code here that checks what attributes have changed since last save
var xhr = (this.sync || Backbone.sync).call(this, 'patch', this, options);
return xhr;
}
I'm sure you can extend Backbone further to include OPTIONS and HEAD if you needed to
Note though, that even through jQuery supports the PATCH, OPTIONS and HEAD methods, your end-users' browser may not.