Optimal URL structure for Backbone.js and Backbone implementation - javascript

I'm developing a RESTful API for a Quiz app, which is going to be built with Backbone.js and Marionette. I'm quite new to backbone and was wondering what de best URL structure would be. I have the following resources:
Answer,
Question which contains Answers,
Question Group which contains Questions,
Quiz which contains Question Groups.
Two possible URL structures come to mind:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /quizzes/:id/questiongroups/:id
GET /quizzes/:id/questiongroups/:id/questions
GET /quizzes/:id/questiongroups/:id/questions/:id
GET /quizzes/:id/questiongroups/:id/questions/:id/answers
or:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /questiongroups/:id
GET /questiongroups/:id/questions
...
Now, I have been trying to use both of these options. With the first one, I can't figure out how to define the collections as a property of the parent models in Backbone so that I can use fetch() on them. The problem with the second option is a bit different: as I understand it, Backbone derives the url for a model from its collection, but the collection is a child of another resource, whereas the url for getting a single resource uses another collection, namely the global set of resources.
I'm pretty sure I'd have to override url() in both cases. I tried some things but didn't come up with anything useable at all. Also, I'd rather not override every single url()-model in the app, changing the API structure to suit the preferences of Backbone seems like a better option to me.
Any pointers as to what seems the right way to do it with Backbone would be great!
Thanks

If questiongroups can only appear in a single quiz, then the first option (the hierarchical one) is an obvious choice. To comply with RESTful conventions, you might want to consider using singular nouns instead: /quiz/:id/questiongroups/:id/question/:id/answer/:id
To solve your fetching problem, I would recommend using nested backbone models as per this answer: https://stackoverflow.com/a/9904874/1941552. I've also added a cheeky little parentModel attribute.
For example, your QuizModel could look something like this:
var Quiz = Backbone.Model.extend({
urlRoot: '/quiz/', // backbone appends the id automatically :)
defaults: {
title: 'My Quiz'
description: 'A quiz containing some question groups.'
},
model: {
questionGroups: QuestionGroups,
},
parse: function(response){
for(var key in this.model){
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {
parse:true,
parentModel:this
});
}
return response;
}
});
Then, your QuestionGroups model could have the following url() function:
var QuestionGroups = Backbone.Model.extend({
// store metadata and each individual question group
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
Alternatively, if you don't need to store any metadata, you could use a Backbone.Collection:
var QuestionGroups = Backbone.Collection.extend({
model: QuestionGroup,
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
I'm afraid I haven't tested any of this, but I hope it can be useful anyway!

Related

Backbone fetching data from model vs collection

I created a simple backbone project, where it fetches all books details and show it in UI. I am fetching all the books details from the model. not at all using collection something like this
var BookModel= Backbone.Model.extend({
initialize: function(){
this.fetchData();
},
fetchData: function(){
this.url= "/get/all_books";
this.fetch({
success: success_callback,
error: error_callback
})
}
});
This is working fine. But why do I have to use collections ? If I would have used collection it would be something like as follows
var BookModel= Backbone.Model.extend({
defaults:{
id:'',
name: '',
author: ''
}
});
var BookCollection= Backbone.Collection.extend({
model: BookModel,
initialize: function(){
this.fetchData();
},
fetchData: function(){
this.url= "/get/all_books";
this.fetch({
success: success_callback,
error: error_callback
})
}
});
The same effect. I don't understand why to use Collections in my case. please help me to understand this concept with my example why do I have to use collections here. I Googled a lot and could find the best answer.
Thanks
Imagine that you have two 2 routes:
/books
/books/:id
Now for getting a specific book you can send a request to /book/:id route, where :id is the id of the book.
GET /books/1
< { id: 1, title: 'On the Genealogy of Morality', ... }
Now what happens if you want to get all the books? You send a request to /books route.
GET /books
< [{ id: 1, title: '...', ... }, { id: 2, title: '...', ... }, ...]
Backbone follows the same principle. Model for a single book. Collection for many books. When you use a collection, Backbone creates one Model for each book. Using a Model for more than one item is wrong.
You said "Backbone creates one Model for each book.". at what step it creates?
It creates the models on the sync event, i.e. when the request for getting all the items is complete.
...how does it helps me. In my case I always fetch all books, not single book.
Backbone Collections always use Backbone Models. If you don't set the model property of the Collection explicitly, Backbone uses a normal Model, i.e. the model property of a Collection should always refer to a Model.
// https://github.com/jashkenas/backbone/blob/master/backbone.js#L782
// Define the Collection's inheritable methods.
_.extend(Collection.prototype, Events, {
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model: Model,
Consider a Model as an object and a Collection as an array of objects.
More than anything, dividing your data into logical units (models) and grouping them into categories (collections) make it easy for you to reason about, manipulate and change your data. Once you build something that is even just a tiny bit more complex than what you have built, this becomes a priority. The point isn't that you are getting some magic functionality that you couldn't otherwise get. It's all javascript after all. The point is that models and collections provide data-structuring that is helpful when building dynamic applications. In fact that's the whole point of backbone and MV* in general, to provide helpful tools and abstractions. Collections are a solution to a whole host of problems that you will run into later down the road, when you add even the tiniest bit of extra complexity to your app.
So, you ask why you have to use collections and I guess the answer that you already knew is, you don't have to use collections. In fact, it sounds like you don't need to use an MV* library at all.

Backbone - built-in REST capability

I am confused by Backbone's built-in REST capability. I was under the impression that Backbone models, upon a model.save(), would automatically append the value identified by idAttribute to the end of the urlRoot. But I see a lot of examples online, like the one below, where the application imperatively appends the the id to the url. Why is that? Which one is better?
An example using RequireJS:
define(function(require) {
var Backbone = require('Backbone');
return Backbone.Model.extend({
urlRoot: 'http://rest-service.guides.spring.io/greeting',
url: function() {
return this.urlRoot + '?name=' + this.id;
}
});
});
Backbone assumes that you were following some common REST practices while you were designing your REST API.
For instance, an API which updates a user should be exposed as:
PUT /users/:id
rather than
PUT /users?id=:id
Of course there are some edge cases when you must rewrite the default URL function but, in general, leaving it as is means you were following the best practices while designing your REST API.
One case I can think of when rewrites are necessary is when a sub-resource is identified by multiple IDs:
PUT /apps/:appId/schedules/:scheduleId
then the url function will be:
url: function () {
return this.urlRoot + '/' + this.appId + '/schedules/' + this.id
}
When applying REST, the id is typically the unique identifier of an item that is contained by a collection (expressed in plural).
The url /greeting/id doesn't seem to make a lot of sense to me.
Parameters sent via the query string (behind the question mark) serve as filters to the collection that is currently queried.

Can I create custom methods in Backbone.collections?

Apologies I'm still learning the fundamentals of Backbone.
I'm a little confused about Backbone.collections. I understand that they consist of a group of collections, and can have url and model attributes. Can you define a custom method in a backbone.collection?
For example:
var Books = Backbone.Collection.extend({
url: '/books',
model: Book,
getBook: function(id, options){
......
}
});
Is this possible and if so, what would it do?
Thanks!
This is absolutely possible and a great way to encapsulate functionality specific to that collection. Although typically in practice I find myself expanding the functionality of the Models more.
As far as what it would do... If you had a collection of books you may want to write helper methods for accessing, pruning, or serializing your collection. I'm not sure what your business needs are.
Have look at underscore.js for available collection functions, so you don't reinvent the wheel.
Custom methods on collections would usually do one of two things:
Handle events fired by the models, if you need to trigger custom actions when that happens
Hold reusable looping/filtering/sorting so that you don't have to inline that stuff throughout your application
When I say collections would usually do this, it doesn't necessarily mean that you're doing anything wrong if your methods falls outside of these two categories, this is just to get an idea what kind of logic you might want to put into a collection custom method.
Take your books for instance, let's say you wanted to have a custom method that returns the total number of pages for all of the books in your collection. That could look something like this:
var Books = Backbone.Collection.extend({
url: '/books',
model: Book,
getTotalNumPages: function() {
var numPages = 0;
this.each(function(model) {
numPages += parseInt(model.get("pageCount"), 10);
});
return numPages;
}
});
// Elsewhere in your app
console.log("Total pages: ", books.getTotalNumPages());

grab multiple instances of ID within an application

I have the below that is setup and working properly.
require(['models/profile'], function (SectionModel) {
var sectionModel = new SectionModel({id: merchantId, silent: true});
sectionModel.fetch({
success: function (data) {
$('#merchant-name').html(data.attributes.merchantName);
}
});
});
But it will only work in one instance. I am wondering how to correctly edit the above code to allow multiple instances.
<h3 id="merchant-name"></h3>
The content is generated within 'Save' function.
merchantName:$('#merchantName').val(),
What you want to do is set up the rest of the components for the Backbone application. The beauty of Backbone.js is it's ability to separate collections, models and views so your logic stays in a proper place.
You'll want to use an AJAX call to retrieve your models from the server using a Collection. Then, use the collection's reset function.
Here's an example of how you might fetch a collection of models from the server.
var MyCollectionType = Backbone.Collection.extend({
getModelsFromServer:function()
{
var me = this;
function ajaxSuccess(data, textStatus, jqXHR)
{
me.reset(data);
}
$.ajax(/* Insert the ajax params here*/);
}
});
var collectionInstance = new MyCollectionType({
model:YourModelTypeHere
});
collectionInstance.getModelsFromServer();
Then, to render each one, you'll want to make a View for each model, and a Collection View. There are a lot of resources though on learning basic Backbone.js and I feel that you might benefit from looking at a few of those.
Keep in mind that Backbone collections will by default merge models with the same id. 'id' usually references a model in the backend of an application, so make sure each id is actually what you want it to be. I work with an application that has a non-Restfull back end, and so ID's are never transferred to the front end.
There are some excellent resources available to begin starting with Backbone.js.
https://www.codeschool.com/courses/anatomy-of-backbonejs
(This is a free course up to a point, and a great starter.)
http://net.tutsplus.com/tutorials/javascript-ajax/getting-started-with-backbone-js/
http://javascriptissexy.com/learn-backbone-js-completely/

Persisting & loading metadata in a backbone.js collection

I have a situation using backbone.js where I have a collection of models, and some additional information about the models. For example, imagine that I'm returning a list of amounts: they have a quantity associated with each model. Assume now that the unit for each of the amounts is always the same: say quarts. Then the json object I get back from my service might be something like:
{
dataPoints: [
{quantity: 5 },
{quantity: 10 },
...
],
unit : quarts
}
Now backbone collections have no real mechanism for natively associating this meta-data with the collection, but it was suggested to me in this question: Setting attributes on a collection - backbone js that I can extend the collection with a .meta(property, [value]) style function - which is a great solution. However, naturally it follows that we'd like to be able to cleanly retrieve this data from a json response like the one we have above.
Backbone.js gives us the parse(response) function, which allows us to specify where to extract the collection's list of models from in combination with the url attribute. There is no way that I'm aware of, however, to make a more intelligent function without overloading fetch() which would remove the partial functionality that is already available.
My question is this: is there a better option than overloading fetch() (and trying it to call it's superclass implementation) to achieve what I want to achieve?
Thanks
Personally, I would wrap the Collection inside another Model, and then override parse, like so:
var DataPointsCollection = Backbone.Collection.extend({ /* etc etc */ });
var CollectionContainer = Backbone.Model.extend({
defaults: {
dataPoints: new DataPointsCollection(),
unit: "quarts"
},
parse: function(obj) {
// update the inner collection
this.get("dataPoints").refresh(obj.dataPoints);
// this mightn't be necessary
delete obj.dataPoints;
return obj;
}
});
The Collection.refresh() call updates the model with new values. Passing in a custom meta value to the Collection as previously suggested might stop you from being able to bind to those meta values.
This meta data does not belong on the collection. It belongs in the name or some other descriptor of the code. Your code should declaratively know that the collection it has is only full of quartz elements. It already does since the url points to quartz elements.
var quartzCollection = new FooCollection();
quartzCollection.url = quartzurl;
quartzCollection.fetch();
If you really need to get this data why don't you just call
_.uniq(quartzCollecion.pluck("unit"))[0];

Categories