Load data belonging to a parent model with Ember.js - javascript

Considering something similar to the example outlined here:
App.Router.map(function() {
this.resource("posts", function() {
this.resource("post", { path: "/posts/:post_id" }, function() {
this.resource("comments", { path: "/comments" });
});
});
});
using the DS.RESTAdapter. The Router would load all the posts when I access the PostsRoute with a call to the API URL /posts.
When I then access PostRoute, for example for the post with id=1, it doesn't hit the API again, i.e. it doesn't hit /post/1. It loads the post from the store.
I want then to access CommentsRoute for post with id=1. How do I load the comments?
Should I have sideloaded all the comments in the first place, when I loaded the post list? In this case though, I would need to load all the comments for all the posts. Is it possible to load the comments only when needed, i.e. when I access CommentsRoute?
In this case, how do I actually load the comments from the backend?
Specifically, how do I write the CommentsRoute to load the comments from the RESTful API when I access the page that actually displays them?
I guess one needs to have the following:
App.Post = DS.Model.extend({
comments: DS.hasMany('comment')
});
App.Comment = DS.Model.extend({
post: DS.belongsTo('post')
});
App.CommentsRoute = Ember.Route.extend({
model: function() {
/*
* How do I inject params.post_id here
* to make a request to the RESTful API?
* Which URL would be called?
* /comments?post_id=ID
* /post/post_id/comments
* ...
*/
// Doesn't work, hits /comments
return this.store.find('comment', { post_id: params.post_id });
}
});
Why does
return this.store.find('comment', { post_id: params.post_id });
hit /comments?

You just need to declare your CommentsRoute like this:
App.CommentsRoute = Ember.Route.extend({
model: function() {
return this.modelFor('post').get('comments');
}
});
What it does is, it gets the model of the PostRoute and fetches its comments.
Ember-data handles the logic behind it. If comments were sideloaded, it will just return these. Otherwise it will issue a GET request to fetch them.
For this to work, you need to include the links property on a serialized post. The links property needs to include the URL you wish ember-data to use in order to fetch the comments.
E.g. your serialized post may look like this:
{
"post": {
"id": 1,
"title": "Rails is omakase",
"links": { "comments": "/posts/1/comments" }
}
}
See DS.RESTAdapter#findHasMany.
The hasMany relationship probably needs to be declared async for this to work properly:
App.Post = DS.Model.extend({
comments: DS.hasMany('comment', { async: true })
});

You can use Ember's sideloaded relationships to make the posts API endpoint also return the relevant comments and Ember will figure it out.
http://emberjs.com/guides/models/the-rest-adapter/#toc_sideloaded-relationships
{
"post": {
"id": 1,
"title": "Node is not omakase",
"comments": [1, 2, 3]
},
"comments": [{
"id": 1,
"body": "But is it _lightweight_ omakase?"
},
{
"id": 2,
"body": "I for one welcome our new omakase overlords"
},
{
"id": 3,
"body": "Put me on the fast track to a delicious dinner"
}]
}
You'd then be able to pass the already loaded comments to the comments route.
It may be in the docs but it's quite a specific term! Some of the concepts like that can be a bit tricky to search for.

The following forces a call to the backend /comments?post_id=ID
App.CommentsController = Ember.ArrayController.extend({
needs: 'post'
});
App.CommentsRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('comment', { post_id: this.modelFor('post').get('id') });
}
});

Related

Angular Factory - Altering Returned Data

I'm following along on the Angular JS Tutorial and I was wondering if there is an alternate approach to how I'm modifying it.
Currently, I am returning data with a factory that is defined as such:
angular.
module('core.card').
factory('Card', ['$resource',
function($resource){
return $resource('cards/:cardId.json', {}, {
query: {
method: 'GET',
params: {cardId: 'cards'},
isArray: true
}
});
}
]);
This is all good and working, as cards.json has all of the cards available and that's exactly what I want to return.
The method that they're describing, such as dealing with a RESTful service, assumes that there are multiple other specific JSON files that could get returned based on the route. I understand how to use that with an actual service, but let's say I wanted to alter the returned JSON data before it gets bound to my module so I don't have a bunch of extra data that I don't need?
Lets say /cards/foo.json contains something like this:
[{
"id": "foo",
"name": "Bar",
"img": "foobar.png",
"unnecessaryKey": "remove me"
}]
But where would I write a function that only returns:
[{
"id": "foo",
"name": "Bar",
"img": "foobar.png"
}]
Would I assign it in the same place where the query function is, such as:
...
return $resource('cards/:cardId.json', {}, {
query: {
method: 'GET',
params: {cardId: 'cards'},
isArray: true
},
alterReturnedData: {
// doStuffToFormatData
}
});
...
Or would it be best to just modify it in my Component as I'm doing now?
function alterReturnedData(data){
// doStuffToFormatData
}
var unmodified = Card.get({cardId:'foo'}, function(){
self.data = alterReturnedData(unmodified);
});
I just feel like it'd be better to return the data from the Service I actually want to the Component Controller instead of having a lot of logic in there to skew it around.
Is my approach OK to run this function in the Controller?
Or is it best to alter it in the Service, and how would I do so?

Define the ember data model for nested rest url

I am trying to do something that sounds simple but I can't find the solution.
My application needs to edit documents which contains pages.
Here is my model :
MyApplication.Document = DS.Model.extend({
title: DS.attr('string'),
pages: DS.hasMany('page', {async: true})
});
MyApplication.Page = DS.Model.extend({
document: DS.belongsTo('document', {async: true}),
title: DS.attr('string'),
params: DS.attr(),
objects: DS.attr()
});
And the routes :
MyApplication.Router.map(function () {
this.resource('document', {path: '/document/:document_id'});
});
MyApplication.Document = Ember.Route.extend({
model: function (params) {
return this.store.find('document', params.document_id);
}
});
When I load the document 1, the application call http://www.myserver.com/api/document/1.
The problem is that when I want to find a page of the document, it calls
http://www.myserver.com/api/pages/ID
instead of
http://www.myserver.com/api/document/1/pages/ID
Theses nested URL are important in my application.
I found different things on the subject like adding links in the JSON response :
{
"document": {
"id": "1",
"title": "Titre du document",
"pages": ["1", "2", "3"],
"links": {"pages" : "pages"}
},
But when I call for the pages, it requests http://www.myserver.com/api/document/1/pages without the id.
I also try specify the document when I ask for the page :
this.store.find("page", 1, {document:1});
Can't find a complete documentation on this subject, so if someone can explain me what's wrong, I'll be happy.
Thank.
Depends : EMBER DATA >= V1.0.0-BETA.9
The way to handle nested routes is hidden under release notes
Need to send back the links with response like this
{
"document": {
"id": 1,
"title": "Titre du document",
"links": {"pages" : "/documents/1/pages"}
}
You'll need to customize the adapter:page's buldUrl method like
MyApplication.PageAdapter = DS.RestAdapter.extend({
// NOTE: this is just a simple example, but you might actually need more customization when necessary
buildURL: function(type, id, snapshot) {
return '/documents/' + snapshot.record.get('document.id') + '/pages/' + id;
}
});
#code-jaff answer adapted to Ember 2.1.0:
// app/adapters/page.js
import DS from './application'; // I suppose you have your adapter/application.js
export default DS.RESTAdapter.extend({
buildURL: function(type, id, snapshot) {
return this.host + '/' + this.namespace + '/documents/' + snapshot.record.get('document.id') + '/pages/' + id;
}
});
Your problem likely stems from the quotes that are surrounding the IDs in your JSON. If you modify your serializer so that there are no quotes for the the IDs both around the document ID and the pages IDs, you should get the behavior that you expect. Also, you need to modify the formatting of your links to point to the relative path:
The resulting JSON should look like:
{
"document": {
"id": 1,
"title": "Titre du document",
"pages": [1, 2, 3],
"links": {"pages" : "/documents/1/pages"}
}
Please see this answer for a description of why adherence to Ember's expectations with regard to the JSON format is important and for an overview of the expected format.

Backbone and best practice getting config JSON

I've got a JSON file that looks like this.
{
"config": {
"setting1": 'blabla',
"setting2": 'blablabla'
},
"content": {
"title": "Title of an exercise.",
"author": "John Doe",
"describtion": "Exercise content."
},
"answers": [
{
"id": "1",
"content": "Dog",
"correct": true
},
{
"id": "2",
"content": "Fish",
"correct": false
}
]
}
Than, I create a Backbone View, combined from content model, and answers (which are randomly selected, but It's not most important now).
I've also got a config, which has settings that will determinate which view and collection methods to use.
It seems like a simple task, but as I'm new to Backbone, I'm wondering which is the best way to fetch JSON file, creating one model with url to JSON and than using parse and initialize creating another models and collections (with answers), or using $.getJSON method that will create exactly the models that I need?
I was trying using $.getJSON
$.getJSON(source, function(data) {
var contentModel = new ContentModel(data.content);
var contentView = new ExerciseView({ model: contentModel });
var answerCollection = new AnswersCollection();
_.each(data.answers, function(answer) {
answerCollection.add(answer);
});
var answersView = new AnswersView({collection: answerCollection});
$(destination).html( contentView.render().el );
$('.answers').append( answersView.el );
)};
But It doesn't seem very elegant solution, I know that this application needs good architecture, cause It will be developed with many other Views based on 'config'.
Hope you guys give me some suggestions, have a good day!
I think what you've done works fine and is correct. But you may need to refactor a little bit since "it will be developed with many other Views based on 'config'".
IMHO, the first thing you need to do is to handle failure in your getJson callback to make the process more robust.
Second, it is useful to create a Factory to generate your views because your logic is to generate different views based on the config data from server. So the factory maybe:
contentViewFactory.generate = function(data) {
var config = data.config;
....
var ActualContentView = SomeContentView;
var contentModel = new ContentModel(data.content);
return = new ActualContentView({ model: contentModel });
}
If your logic is simple, you can have a dict map from config to view class like:
var viewMaps = {
"exercise" : ExerciseView,
"other": SomeOtherView,
//....
}
And if every workflow has a AnswersView you can keep that in your getJSON callback. So maybe now your getJSON looks like this:
$.getJSON(source, function(data) {
// keep the config->view logic in the factory
var contentView = contentViewFactory.generate(data);
var answerCollection = new AnswersCollection();
_.each(data.answers, function(answer) {
answerCollection.add(answer);
});
var answersView = new AnswersView({collection: answerCollection});
$(destination).html( contentView.render().el );
$('.answers').append( answersView.el );
})
.fail(){
//some failure handling
};
Furthermore, if you have common logics in you "ContentView"s, it's natural that you can have a "BaseContentView" or "ContentViewMixin" to extract the common logic and use extends to make your code more OO:
Backbone.View.extend(_.extend({}, ContentViewMixin, {
//.....
}
So if someone is trying to add a new ContentView, he/she just needs to add some code in the factory to make the new View be generated by config. Then extends the ContentViewMixin to implement the new View.

Ember js - Hasmany relationships breaks after updating other tables

I am using Ember.js with local-storage-adapter. I have a weird problem while updating records.
I have a post and comments model with hasMany relationships:
App.Post = DS.Model.extend({
title: DS.attr('string'),
comments: DS.hasMany('comment', {
async: true
})
});
App.Comment = DS.Model.extend({
message: DS.attr('string')
});
These are my post and comments controllers:
App.PostsController = Ember.ArrayController.extend({
newTitle: '',
actions: {
create: function() {
var title = this.get('newTitle');
var post = this.store.createRecord('post', {
title: title
});
this.set('newTitle', '');
post.save();
}
}
});
App.CommentsController = Ember.ArrayController.extend({
needs: "post",
post: Ember.computed.alias("controllers.post.model"),
newMessage: '',
actions: {
create: function() {
var message = this.get('newMessage');
var comment = this.store.createRecord('comment', {
message: message
});
var post = this.get('post');
var comments = post.get('comments');
if (comments.get('content') == null) comments.set('content', []);
comments.pushObject(comment);
comment.save();
post.save();
}
}
});
While creating records hasMany relations updated correctly.
{
"App.Post": {
"records": {
"0v66j": {
"id": "0v66j",
"title": "post1",
"comments": ["p31al", "tgjtj"]
}
}
},
"App.Comment": {
"records": {
"p31al": {
"id": "p31al",
"message": "comment 1"
},
"tgjtj": {
"id": "tgjtj",
"message": "comment 2"
}
}
}
}
The problem occured while editing post. The relationships are gone after editing the post record. I did some searching and found this code:
DS.JSONSerializer.reopen({
serializeHasMany: function(record, json, relationship) {
var key = relationship.key;
var relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship);
// alert(relationshipType);
if (relationshipType === 'manyToNone' || relationshipType === 'manyToMany' || relationshipType === 'manyToOne') {
json[key] = Ember.get(record, key).mapBy('id');
// TODO support for polymorphic manyToNone and manyToMany
// relationships
}
}
});
This did the trick and it worked fine. But now I have another problem. If I edit any other record, all the id references are replaced by whole object like this:
{"App.Post":{"records":{"0v66j":{"id":"0v66j","title":"post2","comments":[**{"message":"comment 1"},
{"message":"comment 2"}**]},"8nihs":{"id":"8nihs","title":"post3","comments":["b4v2b","dbki4"]}}},
"App.Comment":{"records":{"p31al":{"id":"p31al","message":"comment 1"},"tgjtj":{"id":"tgjtj","message":"comment 2"},
"b4v2b":{"id":"b4v2b","message":"comments3"},"dbki4":{"id":"dbki4",
"message":"comments4"}}}}
Comment refrences should be comments":["p31al","tgjtj"] like this. but the ids are replaced as "comments":[{"message":"comment 1"},{"message":"comment 2"}]
When using ApplicationSerializer which extends LSSerializer, it seems to work.
Maybe it got fixed since asked?
I've noticed a few things in my path with Ember... and especially Ember-Data.
One of them is when dealing with associations I've had to manually re-add in the associations saving and having to re-save, and use addObject to in-memory associations as you're using a bit here. :)
Note that this usually only happens when I'm updating more than one new object at once. For example, if your post is new, and your comment is also new.
I'm a little worried to see the following code in your codebase, because it shouldn't need to be there. You shouldn't ever have null or non-array objects in your associations. I'm not sure what hackery you did with the Adapter and why it was necessary, but I hope that wasn't the reason:
if(comments.get('content') == null)
comments.set('content', []);
Anyway, the following code is how I would probably write your create action. It might help. I hope it does.
create: function() {
// get the post for association on the new comment
var post = this.get('post');
// get the message to store on the new comment
var message = this.get('newMessage');
var comment = this.store.createRecord('comment', {
message : message,
post : post
});
comment.save().then(function(savedComment) {
post.get('comments').addObject(savedComment);
});
}
Note that it's a lot simpler. Generally if you're doing tricky complicated things, something's amiss and it's time to go back to basics and add one thing at a time, testing thoroughly between additions. :)
Good luck!

Can we set multiple values to modle in backbone

I am new to backbone and nodejs, I have made a demo which used backbone and nodejs for updating and inserting data. I'm able to send put request with single data at a time.
this.model.set({
user_id:Session.get('userid'),
seat_id:seatId
});
this.model.save({
success: function() {
// do some stuff here
alert("a")
},
error: function() {
// do other stuff here
alert("b")
}
})
The above code post single row info to server. I want to send multiple info to server at a time. Can we set model something like below
this.model.set([{
user_id:Session.get('userid'),
seat_id:2
},{
user_id:Session.get('userid'),
seat_id:3
}]);
Thanks
You can do that:
arr = [{
user_id:Session.get('userid'),
seat_id:2
},{
user_id:Session.get('userid'),
seat_id:3
}];
this.model.set(sessions, arr);

Categories

Resources