21

I am setting up the backbone sync mechanism and am a bit confused where to generate the id's for the models.

When I create a new model, should backbone be generating and setting the id, or am i supposed to implement an id generation method, or is there some sort of mechanism where I "PUT" the data to the server, which generates the id and returns a model with the id?

Nippysaurus
  • 20,110
  • 21
  • 77
  • 129
  • You (may) manually allocate it. Backbone internally assigns a `cid` to the model element that has **not** been saved to the server. Once saved, you return an `id` of the element that backbone uses to assign to your model. See the docs (http://backbonejs.org/#Model-id) or @orangewrap's beautiful answer :) – PhD May 29 '12 at 07:03

3 Answers3

57

I'm providing a second answer to simplify the code you need to study to get the main points you're pondering about - the actual round about from model to server and how ids play their role.

Say you define a model - Let's go with Jurassic Park.

// Define your model
var Dinosaur = Backbone.Model.extend({
    defaults: {
        cavemanEater: undefined    // Has one property, nom nom or not.
    },
    urlRoot: 'dino'               // This urlRoot is where model can be saved or retrieved
});

var tRex = new Dinosaur({'cavemanEater':true});

You now have instantiated a dinosaur that is a meat eater. Roar.

console.log(tRex);

What you should notice is that in the properties of tRex, your model does not have an id. Instead, you will see a cID which you can think of as a temporary id that Backbone automatically assigns to your models. When a model doesn't have an id it is considered new. The concept of persisting a model (either to a database or local storage) is what allows you to go back to that resource after you've created it and do things like save (PUT) or destroy (DELETE). It would be hard to find that resource if you had no way to point directly at it again! In order to find that resource, your model needs an id, something it currently does not have.

So as the above answers have explained it is the job of your database (or localstorage, or some other solution) to provide Backbone with a resource id. Most of the time, this comes from the resource id itself, aka - the primary key id of your model in some table.

With my setup, I use PHP and mySQL. I would have a table called Dinosaur and each row would be a persistent representation of my dino model. So I'd have an id column (unique auto-incrementing int), and cavemanEater (bool).

The data communication flow happens like this.

  1. You create a model.
  2. The model is new so it only has a cID - no proper ID.
  3. You save the model.
  4. The json representation of your model is SENT to your server (POST)
  5. Your server saves it to the table and gives it a resource id.
  6. Your server SENDS BACK a json representation of the data {id:uniqueID}
  7. Backbone RECEIVES this json representation with id
  8. Backbone automagically updates your model with an id.

Here is what annotated code looks like.

CLIENT:

tRex.save();
// {'cavemanEater':true} is sent to my server
// It uses the urlRoot 'dino' as the URL to send. e.g. http://www.example.com/dino

SERVER:

// Is setup to accept POST requests on this specific ROUTE '/dino'
// Server parses the json into something it can work with, e.g. an associative array
// Server saves the data to the database. Our data has a new primary id of 1.
// Data is now persisted, and we use this state to get the new id of this dino.

$dinoArray = array('id'=>1, 'cavemanEater'=>true);
$dinoJSON = json_encode($dinoArray);

// Server does something to send $dinoJSON back.

CLIENT:

// If successful, receives this json with id and updates your model.

Now your tRex has an id = 1. Or should I say...

tRex.toJSON();
// RETURNS {'id':'1', 'cavemanEater':'true'}

Congrats. If you do this tRex.isNew() it will return false.

Backbone is smart. It knows to POST new models and PUT models that already have a resource id.

The next time you do this:

tRex.save();

Backbone will make a PUT request to the following URL.

http://www.example.com/dino/1

That is the default behavior by the way. But what you'll notice is that the URL is different than save. On the server you would need a route that accepts /dino/:id as opposed to /dino

It will use the /urlRoot/:id route pattern for your models by default unless you tweak it otherwise.

Unfortunately, dinosaurs are extinct.

tRex.destroy();

This will call... Can you guess? Yep. DELETE request to /dino/1.

Your server must distinguish between different requests to different routes in order for Backbone to work. There are several server side technologies that can do this.

Someone mentioned Sinatra if you're using Ruby. Like I said, I use PHP and I use SLIM PHP Framework. It is inspired by Sinatra so it's similar and I love it. The author writes some clean code. How these RESTful server implementations work is outside the scope of this discussion though.

I think this is the basic full travel of new Backbone data with no id, across the internets to your server where it generates, and sends back the resource id, to make your model live happily ever after. (Or destroy() not...)

I don't know if this is too beginner for you but hopefully it will help someone else who runs into this problem. Backbone is really fun to program with.

Other similar Answers: Ways to save Backbone JS model data

Community
  • 1
  • 1
jmk2142
  • 8,581
  • 3
  • 31
  • 47
  • 3
    By the way, you CAN actually manually assign ids to a model. `someModel.set('id',777);` but why you would want to do that is beyond me. :-) – jmk2142 May 29 '12 at 05:49
  • 2
    +1 Beautifully explained. Saved me the effort of typing it :D – PhD May 29 '12 at 07:04
  • It says that Backbone automatically uses the id to add it to the model. Can somebody tell in which function of Backbone.js this happens? It shoult be something like a `???.id = ` in the sourcecode, but I cannot find that. Any help? – humanityANDpeace Nov 10 '13 at 21:00
  • 1
    You mean how Backbone automatically uses say, an object ID from a database and turns it into a Backbone Model id? Are you working with an off standard id format or something? Not sure if this is what you're getting at but look for the keyword `idAttribute` This also might be along the lines of what I think you're after... Just guessing though. [LINK: idAttribute](http://backbonejs.org/#Model-idAttribute) – jmk2142 Nov 14 '13 at 16:39
  • i honestly don't know of a better way - how do you do it? – Alexander Mills Jan 15 '15 at 10:40
  • You add a comment if that's not too long or you ask a new question referencing this one if it's relevant. – nikoshr Jan 15 '15 at 10:41
  • @orangewarp - this is great answer. explained a lot. thank you. curious though, let's say i am hitting an API which returns a collection of JSON objects that DO NOT have ID's associated with them - something like {type: 'T-Rex', size: 'Large', scary: true}. If I am understanding you correctly, in order to properly use .save() and destroy() on these models, I would have to manually add an ID to them. How do you recommend one to manually .set() ID's if I am .fetch()ing them from an endpoint. thanks! – cheshireoctopus Mar 05 '15 at 22:46
  • 1
    @cheshireoctopus Hm. Well, the use of a unique object ID is an integral part of a lot of REST type interactions. It's hard to imagine why you'd want to save an object and NOT assign it an ID. If there is some other way of uniquely identifying a resource, you can configure Backbone to use that property as the ID instead of the traditional id name. And if you're manually setting IDs the question is how do you ensure that those IDs are unique? If you're manually setting just so the client side has some way of identifying things... there is a cid or clientID automatically attached to backbone obj. – jmk2142 Mar 05 '15 at 23:29
  • 1
    See [idAttribute and cid](http://backbonejs.org/#Model-idAttribute). It does worry me though that you want to `.save()` and `.destroy()` specific resources that can't be identified uniquely. You can set the id from the client... just do `model.set('id','someID')` but the problem is if you have multiple clients - how are you ensuring uniqueness? Then in the end the server still has to lookup in the DB and validate. In that case isn't it a lot easier just to have the DB automatically assign an uniqueID? :-) I'm not sure of the specific details but maybe this scenario warrants a new question? – jmk2142 Mar 05 '15 at 23:33
  • @orangewarp - thank you for the follow-up, and for the additional info re: REST practices. You had mentioned locating another unique identifier for a resource, aside from an `id` attribute, and setting this unique identifier to the id using `idAttribute`; I believe this was the missing piece to my puzzle. – cheshireoctopus Mar 07 '15 at 19:42
  • 1
    @cheshireoctopus Great. But to reiterate. You can set the `idAttribute` to whatever field you wish. Just make sure it's your database that is setting that uniqueID automatically to ensure uniqueness. If you assign this id via client - you must ensure that the id is unique or you'll have a terrible day when by accident - a resource is identified as having an id which already exists in your DB... and then all your `.save()` calls start overwriting what you did not intend to overwrite. ;-) – jmk2142 Mar 07 '15 at 21:33
  • Side note: Sometimes it is useful to set your own id on the client side when using UUIDs. e.g. when internet connection is down, you can generate a queue of objects to save to a remote db once connection is restored, and still allow your app to modify them locally in the meantime. – csum Jul 13 '16 at 23:26
9

or is there some sort of mechanism where I "PUT" the data to the server, which generates the id and returns a model with the id?

Kind of. When you call the save method of your model, backbone make a POST XHR and your application server should respond with JSON contains an id. You can see an example here: http://addyosmani.com/blog/building-backbone-js-apps-with-ruby-sinatra-mongodb-and-haml/

Quoting from the link:

post '/api/:thing' do 
  # parse the post body of the content being posted, convert to a string, insert into 
  # the collection #thing and return the ObjectId as a string for reference 
  oid = DB.collection(params[:thing]).insert(JSON.parse(request.body.read.tos)) 
  "{\"id\": \"#{oid.to_s}\"}" 
end

If you don't know Ruby keep in mind what the last expression that is evaluated is automatically returned by the method.

theotheo
  • 2,664
  • 1
  • 23
  • 21
  • Although all three answers are right, this one was the first right one and was the one which helped me fix my code. Thank you all for your time and efforts to answer this question, it is greatly appreciated :) – Nippysaurus May 29 '12 at 21:06
4

What I understand from your question is that you want to have a collection with models that exist on the server. In order to get these models into the collection you'd have to add call 'fetch()' on the collection.

The url would be "/users" or something similar, that would have to return an array of objects with user data in there. Each item in the array would then be passed to UserCollection.add(). Well, actually it would be passed all at once, but you get the point.

After this your collection is populated. The url on the Model is meant for updating and saving the individual model. The url of the collection will also be used for creating models. Backbone's sync is RESTful, like Ruby on Rails. You can actually learn more about it on the documentation of Ruby on Rails:

http://guides.rubyonrails.org/routing.html

What you would generally do is have a different url for your model than for your controller. After populating your collection you have id's for each model in there because they came from the server.

Now when you add a new model based on user input you'd do something like this:

var HomeModel = Backbone.Model.extend({
    defaults: {
        lead: "not logged in",
    },

    url: 'test.php',

    initialize: function(){
        _.bindAll(this, 'handleSave', 'handleError');
        // Save already knows if this.isNew.
        this.save(undefined, {success: this.handleSave, error: this.handleError});
    },

    handleSave: function(model, response){
        this.model.reset(model);
    },

    handleError: function(){

    },
});

var HomeView = Backbone.View.extend({
    initialize: function() {
        _.bindAll(this, 'render');
        this.model = new HomeModel();
        this.model.bind("change", this.render);
    },

    el: 'div',

    render: function() {    
        // Do things to render...
    }
});

var homeView = new HomeView();

The example is from someone else's question I answered, I just add the relevant things.

The general idea is to save the model when it is created, if you need it somewhere else you can just move the code into a function of the model and call that based on events or anything else.

Mosselman
  • 1,718
  • 15
  • 27
  • I have refined and updated my question. I'm just not sure where the id's for my models are supposed to come from. When the page initially loads the collection will be populated with reset([]), but that data will already contain an id for each model as it already existed. What I need to know is where to get the id for new data which is created in backbone as a result of user interaction. – Nippysaurus May 29 '12 at 00:55
  • You'd perform the 'save' action of the model. I'll update my answer. – Mosselman May 29 '12 at 04:29