Ember 2.x get model's relationships at subroute of that model - ember.js

Imagine that I have two models, Author and Book.
// models/author.js
DS.Model.extend({
name: DS.attr(),
books: DS.hasMany('book')
});
// models/book.js
DS.Model.extend({
title: DS.attr(),
author: DS.belongsTo('author')
})
It would be nice to have an endpoint at
/api/authors/{authorID}/books to be able to get all of the authors books in one batch request, instead of making multiple calls to /api/books/{bookID}, but it doesn't seem that ember supports this. It is possible to do /api/books?authorID={authorID}, but that would lose some of the benefits of the store.
Is there an Ember idiomatic way of doing /api/authors/{authorID}/books? Again, the goal is to be able to make one batch request to get all books for an author instead of making one call for every book in the author's hasMany list.
For a little more context, I have the following routes structure:
// router.js
...
this.route('authors', function() {
this.route('author', { path: ':id' }, function() {
this.route('books');
});
});
...
In the 'authors' route I will load all of the authors. I do not want to synchronously load their related books, yet, as the list is potentially massive and not used on this route.
In the author route I'm using data of the author already retrieved (the name, in this case).
In the books route I would like to finally load all of the author's related books without needing to send a single request per book.

If you use modelFor() in the books route, you can get() the books for a single author.
authors author route
model(params) {
return this.store.find('author', params.id);
}
authors author books route
model() {
var author = this.modelFor('authors.show');
return author.get('books');
}
Here's an ember-twiddle: https://ember-twiddle.com/924dd7d31e0984b708f9

There is for RestAdapter but it's quite ugly. The query is then /api/books?ids[]=1&ids[]=2. But if you know you will need all authors books you can sideload them and when you call author.get('books') it won't trigger fetch anymore.

Related

How to use Ember Data with Nested Resources

My application backend has several resources. A model is exposed for each resource.
The entry point to all other resources is through the User model. What I mean is, given a User we can find BlogPost. Given a BlogPost we can find Comments etc.
In Ember terminology, we could say:
User hasMany BlogPost
BlogPost hasMany Comment
Comment belongsTo BlogPost
By backend exposes REST APIs of the form:
GET /api/v1/users/1
GET /api/v1/users/1/blog_posts/1
GET /api/v1/users/1/blog_posts/1/comments/1
I'm trying to figure out how to use Ember Data to fetch Comment belonging to a certain BlogPost belonging to a certain User.
From what I see, if I define a typical Ember model for Comment:
App.Comment = DS.Model.extend({
...
blogPost: DS.belongsTo('App.BlogPost')
});
and in the CommentRoute I have the following:
var CommentRoute = MessageRoute.extend({
model: function(params) {
this.store.find('comment')
},
The request is sent to:
/api/v1/comments
I don't even know where to begin in order for Ember Data to use urls of the form:
GET /api/v1/users/1/blog_posts/1/comments/1
I've seen several similar questions asked (see links below), but haven't seen a definitive answer to any of them. Most of them are almost a year old when ember-data, perhaps, did not have such functionality (or so it is claimed in some of these threads).
I ask again to confirm whether ember-data has such functionality or not.
Similar Questions:
Ember Data nested Models
Canonical way to load nested resources
Deep nested routes
The best way to handle it is with links. If you don't want to do it like that, it is far from supported, and difficult to hack in (the pipeline just doesn't easily pass the information around). Personally I'd recommend rolling your own adapter in that case (Ember without Ember Data).
App.Foo = DS.Model.extend({
name: DS.attr('string'),
bars : DS.hasMany('bar', {async:true})
});
App.Bar = DS.Model.extend({
foo: DS.belongsTo('foo'),
});
json:
{
id: 1,
name: "bill",
links: {
bars: '/foo/1/bars'
}
}
Example: http://emberjs.jsbin.com/OxIDiVU/971/edit

Temporary Non-Persistent Record with Ember-Data 1.0.0-beta

I'm new to Ember and Ember-data and am deciding whether to use Ember-Data or one of the other persistence libraries. In order to evaluate, I'm experimenting with writing a small Rails-backed app.
One of my routes can be considered similar to the Todo MVC app that is frequently used in examples.
In my template, I have a number of input fields that represent attributes within the model. Furthermore, I also have one element in the model that represents a hasMany relationship.
Models:
App.CompanyModel = DS.Model.extend
company: DS.attr()
desc: DS.attr()
contacts: DS.hasMany('company_contact')
App.CompanyContactModel = DS.Model.extend
firstname: DS.attr()
lastname: DS.attr()
...
Within my controller, I want to be able to create a new CompanyModel record (and by virtue, add one or more contacts models to it), but not have it appear within the controller's instance of the CompanyModel until I'm ready to do so.
Currently, when a user wants to add a new record, I have a component that calls an action in my controller as follows:
#set('new_company',
#store.createRecord('company')
)
This actually works fine, except for one thing. My view has to populate the individual attributes within "new_company", which it does, however, the record is immediately added to the controller's model instance and appears in the list of records; I only want the newly created record to be visible in the table once a particular action has taken place.
Instead of instantiating new_company with createRecord, I could do something like this:
#set('new_company',
Ember.Object.create
companyname: ''
desc: ''
contacts: [
firstname: ''
lastname: ''
]
)
And then do a #store.createRecord('company', #get('new_company')), however, given I've already defined my attributes in the model, it doesn't feel very DRY to me.
I'm using Ember 1.5.0 and Ember-Data 1.0.0-beta.7.
It appears I'm not the first person to have this issue (create temporarty non persistent object in Ember-Data), but it appears that Ember-Data has sufficiently changed to make all of these solutions inoperable.
Thanks for your help!
You're real issue is you're using what's considered a live collection. I'm going to assume in your route you've done something like this:
App.FooRoute = Em.Route.extend({
model: function(){
return this.store.find('company');
}
});
find with no parameters says, hey Ember Data, find me all the records that are company. Well Ember Data shoots off a request to your back-end, then returns store.all('company'). all is a live collection that will always have all the records of that type currently in the store. In your case, you are saying I want to avoid any record that is new. There are a couple of ways to handle this.
Create a static list. (You'll need to manually add/remove objects to/from this list).
App.FooRoute = Em.Route.extend({
model: function(){
return this.store.find('company').then(function(companies){
return companies.toArray();
});
}
});
Example: http://emberjs.jsbin.com/OxIDiVU/641/edit
Create a computed property that only shows records that aren't new
App.FooRoute = Em.Route.extend({
model: function(){
return this.store.find('company');
}
});
App.FooController = Em.ArrayController.extend({
savedRecords: function(){
return this.get('model').filterBy('isNew', false);
}.property('model.#each.isNew')
// shorthand this could be written like this
// savedRecords: Ember.computed.filterBy('model', 'isNew', false)
});
Then in your template you would iterate over the computed property
{{#each item in savedRecords}}
{{/each}}
Example: http://emberjs.jsbin.com/OxIDiVU/640/edit

Ember, the right way of creating interface for saving records with hasmany relationships

I have these models:
Gmcontrolpanel.Offer = DS.Model.extend({
name: DS.attr('string'),
date: DS.attr('string'),
duration: DS.attr('number'),
products: DS.hasMany('product', {async: true}),
});
Gmcontrolpanel.Product = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
offer: DS.belongsTo('offer'),
variations: DS.hasMany('variation', {async: true})
});
Gmcontrolpanel.Variation = DS.Model.extend({
description: DS.attr('string'),
quantity: DS.attr('number'),
price: DS.attr('string'),
product: DS.belongsTo('product')
});
I'm trying to build a reusable interface for creating/editing an offer; i've made two separate views for inserting a product and inserting a variation;
the product view has a + and a - button to add or remove products, and the same for variation view;
the model for the route is:
model: function() {
return this.get('store').createRecord('offer');
}
What I want is that, when clicking on save button, all (offer, products and variations) are saved;
First of all: which one is the best way of implementing this? containerViews? collectionViews or {{#each}} loops?
And then, how can I create the child records and bind them to the input fields on the child views? I mean: I can create a new product record every time a productView is inserted and the same for variations, but when saving how can I get all these records and set properly all the relationships fields?
Here is a skeleton example of how to set up the relationship:
var newOffer= store.createRecord('offer', {name:....});
//or you can do newOffer.set('name',...);
var newVariation = store.createRecord('variation', {description:.....});
var newProduct = store.createRecord('product', {name:..., description:.....});
newProduct.get('variations').pushObject(newVariation);
newOffer.get('products').pushObject(newProduct);
But for saving the model and persisting it in db, there is one slight problem. Saves are per model, so even when you have the relationship set up properly when we do save on offer model, it doesnot embed the data associated with hasMany relationed models. So we could do something like this :
Note: I have read about bulk save but haven't tried it yet - you might want to give it a shot but if that didnt work then i would do save on each model from bottom up like
newVariation.save().then(function(){
newProduct.get('variations').pushObject(newVariation);
//since the variation model is already saved, it has id associated with the model
//so ember data now knows that it should set variations as variations:[id of the variation model we just saved] when sending post request for product
newProduct.save().then{ //same pattern as above }
}, function(){//handle failure}
Here the case was simple, we had just one variation and one product but you may have multiple of them. We can do rsvp.all to sync up the promises for saves but it is bit sluggish becuse you have to make separate api calls for each save and since you may have multiple variations and products, the no of ajax calls can be bit insane. One way of getting around this would be that you create your own json structure by looping through the models, and combine the models into single json and make a single post request with jQuery ajax api call, save the content in your db and then make use of pushPayload(
http://emberjs.com/api/data/classes/DS.Store.html#method_pushPayload) to load up all the data back to the store.
This is what i have done in similar situation but there might be more elgant solutions out there, so i would wait on more opinions on this.
As for the view thing, i would think you would need a view for product only, this is what im thinking:
//offer.hbs
Bunch of form elemnts to update name description
+ - buttons to add product
{{#each product}}
{{#view productView product=this}}//this is just a normal view
{{/each}}
// your template associated with productView will be like
+ - buttons to add variations to product
{{#each product.variations}}
Show form elments to update name and description
{{/each}}
How about if we give this a try in setupController
setupController:function(controller, model){
model.reload().then(function(data){controller.set('model', data);});
}
Or, what if you create an action to transition to edit mode and in that action you reload the model first and within its then hook, you do the transitionToRoute with reloaded model data. Something like:
goToEdit: function(model){
model.reload().then(function(data){transitionToRoute('offer.edit', data});
}

Ember Relationships without Ember Data

I have an Ember App where some Models use Ember Data and some don't. My question relates to creating relationships between these Models and also the best way to structure the Model relationships.
Models
Currently I have the following Models:
Foods
not using Ember Data
makes $.ajax request to external API
extends a Ember.Object (see here and here for examples of the methodology)
Meals
uses Ember Data
has many Portions
Portions
uses Ember Data
hasOne Meal
hasOne Food
In my app I need a Portion to be a unique record which has a weight field. Each Portion should derive it's other values from a associated Food. A Meal should contain many Portions.
Questions
Should Portions be a Model in it's own right our should it be stored in some kind of array-like structure as a field on the Meal (eg: portions)? Consider that a Portion is not reusable and is only able to be associated with a single Meal.
If "Yes" to #1 then what could my Meal Model def look like?
As Food does not use Ember Data what's the best technique for defining a relationship between a Portion and a Food?
Ultimately the User experience should allow someone to
View a Food
Create a Portion of that Food
Associate the Portion with a Meal
View all Portions associated with a Meal
Your help is much appreciated.
Q1: Should Portions be a Model in it's own right our should it be stored in some kind of array-like structure as a field on the Meal (eg: portions)?
I'm not sure you are asking if Portions should be a model or Portion should be a model. But whatever I think the solution is to build Portion as a model and build portions relationship for Meal model. Because you have functionality to create a portion with a food. In my understanding the portion should be created without a meal (although it can link to a meal later).
Q2: If "Yes" to #1 then what could my Meal Model def look like?
The model definition is like this:
App.Portion = DS.Model.extend({
weight: DS.attr(),
meal: DS.belongsTo('meal', {async: true})
});
App.Meal = DS.Model.extend({
portions: DS.hasMany('portion', {async: true})
});
Q3: As Food does not use Ember Data what's the best technique for defining a relationship between a Portion and a Food?
It's better to still use Ember Data to define Food model, just define your custom adapter and serializer, Ember Data handles the rest. The DS.Adapter and DS.Serializer documentations are good place to start. Below is a simple example.
// Just name it "FoodAdapter" and Ember Data knows to use it for "Food".
App.FoodAdapter = DS.Adapter.extend({
find: function(store, type, id) {
// The returned value is passed to "serializer.extract" then "store.push"
return this._ajax({url: '/external/food', type: 'GET'});
},
createRecord: function() {},
updateRecord: function() {},
deleteRecord: function() {},
findAll: function() {},
findQuery: function() {},
_ajax: function(options) {
// Transform jQuery promise to standard promise
return Em.RSVP.cast($.ajax(options));
}
});
App.FoodSerializer = DS.Serializer.extend({
// Assume the json is:
// {
// "food_data": {
// "name": "XXX",
// "price": 100
// }
// }
extract: function(store, type, payload, id, requestType) {
return payload.food_data;
},
serialize: function() {}
});

Creating Child Records in hasMany Relationship with Ember.Js

I haven't found a satisfactory answer through my search, so I figured I'd ask here.
I'm currently using Ember.Js, Ember-Data, and Ember-Firebase-Adapter, and attempting to create a CRUD application which will create a Parent Record, and then subsequent Child Records to said Parent Records.
(note that DS.Firebase.LiveModel is the Firebase adapter equivalent of DS.Model/Ember.Model)
Here are my models, altered to be generic Post/Comment types
App.Post = DS.Firebase.LiveModel.extend({
name: DS.attr('string'),
body: DS.attr('string'),
date: DS.attr('date'),
comments: DS.hasMany('App.Comment', {embedded: 'always'})
});
App.Comment = DS.Firebase.LiveModel.extend({
message: DS.attr('string'),
timestamp: DS.attr('string'),
post: DS.belongsTo('App.Post', {key: "post_id"})
});
(Should my post_id = post?)
And here is my route for creating Comments:
App.PostsCommentRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', App.Comment.find());
}
});
Here's my controller for the PostsCommentRoute:
App.PostsCommentController = Ember.ObjectController.extend({
newMessage: null,
newTimestamp: null,
saveComment: function() {
App.Pbp.createRecord({
message: this.get('newMessage'),
timestamp: this.get('newTimestamp')
})
App.store.commit();
this.set('newMessage', null);
this.set('newTimestamp', null);
}
});
I think I may be missing the serializer? And I've read several things on addArray but the things I tried to plug in did not prove fruitful. Because my comments create fine, however they are not associated to the post in anyway in my JSON.
Is there a way for the created Comments to find the related Post_Id and then associate to said Post when created? So my Post JSON has an array of Comment_Ids which then allows them to be displayed with the post?
Any help, or links with good examples would be much appreciated. I know this is a relatively simple quandary yet I've been stuck on it for some time now :p
What you can try and do is this
post = App.Post.find(1);
post.get('comments').pushObject(App.Comment.createRecord({})); //This will add a new comment to your post and set the post_id as the post id
App.store.commit()
Hope it helps