EmberJS: Customize the REST URL of a related model - javascript

In the Ember Documentation, it mentions this:
An adapter is an object that knows about your particular server
backend and is responsible for translating requests for and changes to
records into the appropriate calls to your server.
For example, if your application asks for a person record with an ID
of 1, how should Ember Data load it? Is the URL /person/1 or /resources/people/1?
In my example, I have a User model with related Messages. When the Ember store retrieves related messages for a user, it generates a query in the form /messages/?ids=18 using something like this:
user = this.store.find('user', params.user_id);
user.get('messages');
I'd like to load these messages with a URL like /users/2/messages/.
I looked through Ember's documentation on adapters and wasn't able to find anything to implement this specifically. How do I customize the URL?

You can create a message adapter that builds the right url for the end point.
import Ember from 'ember';
import DS from 'ember-data';
export default DS.ActiveModelAdapter.extend({
find: function(store, type, id) {
return this.ajax(this.buildURL(type.typeKey, id), 'GET');
},
createRecord: function(store, type, record) {
var data = {};
var serializer = store.serializerFor(type.typeKey);
serializer.serializeIntoHash(data, type, record, { includeId: true });
return this.ajax(this.buildURL(type.typeKey, null), "POST", {data: data});
},
buildURL: function(type, id) {
var url = [],
host = Ember.get(this, 'host'),
prefix = this.urlPrefix(),
user_id = this.container.lookup('controller:user').get('id');
// append user and id to the url
url.push('users/' + user_id);
if (type) { url.push(this.pathForType(type)); }
if (id) { url.push(id); }
if (prefix) { url.unshift(prefix); }
url = url.join('/');
if (!host && url) { url = '/' + url; }
return url;
}
});
I hope this is helpful.
Cheers

Related

Model getting a URL as a resource in ember-data

I have a model that gets a URL as a source of data, currently an array of other models. I want to map it as a store and thus I created a model that acts as an intermediate.
This is the schema:
app/models/user_info.js
export default DS.Model.extend({
....
studies: DS.belongsTo('studies-collection-id);
....
app/models/studies-collection-id.js
export default DS.Model.extend({
studies: DS.hasMany('study')
});
app/adapters/studies-collection-id.js
export default ApplicationAdapter.extend({
urlForFindRecord (id, modelName, snapshot) {
return(id);
}
});
This works fine and I get the request that I need fired and the server responds with the array of studies that I was expecting. The problem is I can't get those studies to reach the store in any way.
I tried serialising the response but currently have no success on that. I post what I'm using now:
/app/serializers/studies-collection-id.js
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
primaryKey: 'self',
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
let jsonApiStudiesArray = [];
jsonApiStudiesArray['data'] = payload.slice(0);
jsonApiStudiesArray['self'] = id;
jsonApiStudiesArray['type'] = primaryModelClass.modelName;
jsonApiStudiesArray['data'] = [];
jsonApiStudiesArray['data']['attributes'] = payload.slice(0).map(function(item){
let returnArray = [];
returnArray['type'] = 'study';
returnArray['self'] = item['self'];
returnArray['attributes'] = item;
return(returnArray);
});
return this._super(store, primaryModelClass, jsonApiStudiesArray, id, requestType);
}
});
Anyone has a suggestion on this?. I feel like I'm trying to hack ember on this and it should be easier.
Thank you very much for your help
Update: I think I found a solution for ember to digest this by using a dummy model in between. My API returns fields with a URL that points to resources. In order to be able to perform the two queries and have them loaded in the appropriate stores I did modify the serialiser in this way:
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
let jsonApiStudiesArray = {};
jsonApiStudiesArray['self'] = id;
jsonApiStudiesArray['studies'] = payload.slice(0).map(function(item){
return(item['self']);
});
return this._super(store, primaryModelClass, jsonApiStudiesArray, id, requestType);
}
});

Meteor Iron Router : custom id route

Having a bit of trouble with iron router and passing in a custom id from a collection.
Some context: I have a collection of "groups" in which they all have their own special id other than the default _id that is auto generated. I am trying to make a route that is like
" localhost:3000/groups/:groupid "
so each group will have its own rendered template with the groups information.
HTML :
<template name="Group">
<h1>Group: {{groupName}}</h1>
</template>
CLIENTSIDE:
grabbing a groupid from a session...
I tested this and it works so its not an issue with grabbing it from the session but rather with the router
var groupid = Session.get('groupID');
Router.go('/groups/:_id',{_id: groupid})
ROUTER:
Router.route('/groups/:_id', function () {
this.render('Group', {
data: function () {
return GroupsList.findOne({_id: this.params._id});
}
});
});
This will render a route with groupid as the params instead of the actual number
UPDATE:
CLIENT
Router.go('/groups/'+ groupid);
ROUTER
Router.route('/groups/:groupid', function () {
this.render('Group', {
data: function () {
console.log(this.params.groupid)
console.log(GroupsList.findOne({groupID: this.params.groupid}))
return GroupsList.findOne({groupID: this.params.groupid});
}
});
});
This seems to get the route to work but it wont render the groupname in the template
From the Iron Router Guide:
Now that we're using named routes in Router.go you can also pass a
parameters object, query and hash fragment options.
Router.go('post.show', {_id: 1}, {query: 'q=s', hash: 'hashFrag'});
However when you call Router.go, you are not passing a route name, but a url.
Try this:
Router.go('/groups/' + groupid);
Router.route('/groups/:_id', function () {
this.render('Group', {
data: function () {
return GroupsList.findOne({groupid: this.params._id});
}
});
});
On a side note from the answer just in case anyone else has this issue , I actually figured out there was an issue in the data type of the "this.params._id" , it seems it was coming up as a data type that was not a string or number and therefore could not be successfully used in the findOne method. In the end I just had to parseInt and this was the solution at the end :
Router.go('/groups/'+ groupid);
Router.route('/groups/:groupid', function () {
this.render('Group', {
data: function () {
var id = parseInt(this.params.groupid)
return GroupsList.findOne({groupID: id});
}
});
});

Backbone - Populating multiple models from one fetch call in my controller

Say I have a collection (of search results, for example) which needs to be populated and a pagination model that needs to take values for current page, total number of pages, etc. In my controller, I make a GET call to an API which returns both search results and pagination information. How, then, can I fetch all this information and parse it into a collection and a separate model? Is this possible?
I am using AirBNB's Rendr, which allows you to use a uniform code base to run Backbone on both the server and the client. Rendr forces me to parse the API response as an array of models, keeping me from being able to access pagination information.
In Rendr, my controller would look like this:
module.exports = {
index: function (params, callback) {
var spec = {
pagination: { model: 'Pagination', params: params },
collection: { collection: 'SearchResults', params: params }
};
this.app.fetch(spec, function (err, result) {
callback(err, result);
});
}
}
I apologize if this is not clear enough. Feel free to ask for more information!
This is super old so you've probably figured it out by now (or abandoned it). This is as much a backbone question as a Rendr one since the API response is non-standard.
Backbone suggests that if you have a non-standard API response then you need to override the parse method for your exact data format.
If you really want to break it up, the way you may want to code it is:
a Pagination Model
a Search Results Collection
a Search Result Model
and most importantly a Search Model with a custom parse function
Controller:
index: function (params, callback) {
var spec = {
model: { model: 'Search', params: params }
};
this.app.fetch(spec, function (err, result) {
callback(err, result);
});
}
Search Model
var Base = require('./base'),
_ = require('underscore');
module.exports = Base.extend({
url: '/api/search',
parse: function(data) {
if (_.isObject(data.paginationInfo)) {
data.paginationInfo = this.app.modelUtils.getModel('PaginationInfo', data.paginationInfo, {
app: this.app
});
}
if (_.isArray(data.results)) {
data.results = this.app.modelUtils.getCollection('SearchResults', data.results, {
app: this.app,
params: {
searchQuery: data.searchQuery // replace with real parameters for client-side caching.
}
});
}
return data;
}
});
module.exports.id = 'Search';

Fetch BackboneJS model from id (I think)

I'm responsible to make a cache-layer, that uses local storage, that is between the server and the client to lower the bandwidth and server load.
I have two models, 'model' and 'modelContent'. These are connected to each other with an ID.
model is populated as it should be with all the parameters that are in the model.
modelContent is not populated when its content is fully fetched from the server.
I want to wait until the modelContent has it's attribute 'body' fully populated and then add it to the cache. Right now modelContent's 'body'-attribute is just an empty string (which is the default value).
I have created a base-model that model and modelContent inherits from that contains an override for sync for the read-method. The reason for this is that I read http://engineering.linkedin.com/mobile/linkedin-ipad-using-local-storage-snappy-mobile-apps and that is the way I want it to work but with two models that belong together.
I'm very new at BackboneJS and Grails (which the project uses as well) so I'm hoping someone can point me in the right direction.
My base-model:
var basic_model = Backbone.Model.extend({
sync: function(method, model, options) {
if ( method === 'read' ) {
uid = this.get("UID");
if ( uid ) {
var success = options.success;
options.success = function(resp, status, xhr) {
console.log(resp);
}
} else {
Backbone.sync(method, model, options);
}
} else {
Backbone.sync(method, model, options);
}
}
modelContent:
var MailContentItem = basic_model.extend({
urlRoot: project_webroot +'modelContent',
idAttribute: "UID",
defaults: function() {
return _.extend({}, basic_model.prototype.defaults, {
isHTML: false,
body: ""
});
}
});

How to post json to api method in backbone.js?

i have rest api based on django rest framework, that include next method of creation object, that takes the data in JSON-format on 'myapp/create_obj/' and if the data is correct object will created, otherwise it returns an error also in JSON-format.
def create_obj(request):
stream = StringIO(request.raw_post_data)
data = JSONParser().parse(stream)
serializer = ObjSerializer(data=data, many=True)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data, status=201)
else:
return JSONResponse(serializer.errors, status=400)
Also i tried to create a module on backbone.js, that post the input in form data to this method. Im very new to js, in particular to backbone and i bad understand how backbone works with server api. i have something like
App.module('Createobj', function(Mod, App, Backbone, Marionette, $, _) {
Mod.id = 'create-obj';
Mod.controllers = {};
Mod.Obj = Backbone.Model.extend({
defaults: {
real_ref : '',
share : ''
}
});
Mod.View = Marionette.ItemView.extend({
id: 'create-obj-page',
template: '#tpl-create-obj-page',
model: Mod.obj,
ui: {
'real_ref': 'input[name=real_ref]',
'share': 'input[name=share]',
'error': 'div.error'
},
hammerEvents: {
'tap button': 'submit:tap'
},
hammerOptions: {
tap: true
},
showError: function(message) {
this.ui.error
.text(message)
.show();
},
hideError: function() {
this.ui.error.hide();
},
});
Mod.Controller = SRClient.PageController.extend({
id: Mod.id + '.main',
ViewClass: Mod.View,
setup: function() {
this.listenTo(this.view, 'submit:tap', this.submit);
},
submit: function() {
var real_ref = this.view.ui.real_ref.val(),
share = this.view.ui.share.val();
if (!real_ref || !share) {
this.view.showError($t('create-obj.error_empty_fields'));
return;
}
App.vent.trigger('loading-screen:show', $t('app.please_wait'));
var obj = new Mod.obj({
real_ref : this.view.ui.real_ref.val(),
share : this.view.ui.share.val()
});
}});
Mod.addInitializer(function() {
Mod.Controllers = {
default: Mod.Controller
};
App.pageControllers[Mod.id] = Mod;
});
});
What i need to do, that data which i input in webform sends to 'myapp/create_obj' in json-format? Thanks!
Backbone expects a RESTful api so instead of being the endpoint an action like create_obj, REST works with Resources and with HTTP methods. In your case you could have a Model like this:
var Obj = Backbone.Model.extend({
defaults: {
real_ref : '',
share : ''
}
});
and a collection like this
var Objects = Backbone.Collection.extend({
url: 'myapp/obj',
model: Obj
});
the collection has a propetry url that specifies the server endpoint. So the operations will be
POST /myapp/obj/ for create a new item
GET /myapp/obj/:id/ if you want to retreive an specific item
GET /myapp/obj/ retreving the whole list
PUT /myapp/obj/:id/ update an item
DELETE /myapp/obj/:id/ delete an item
Tastypie is a good framework to create RESTful api with Django.

Categories