How to use ember data with nested hasMany relationships? - ember.js

I have some data models that look like this:
App.Order = DS.Model.extend({
name: DS.attr('string'),
legs: DS.hasMany('leg', {async: false})
});
App.Leg = DS.Model.extend({
name: DS.attr('string'),
order: DS.belongsTo('order'),
stops: DS.hasMany('stop', {async: false})
});
App.Stop = DS.Model.extend({
name: DS.attr('string'),
leg: DS.belongsTo('leg'),
});
The returned JSON looks like this:
{
"orders": [
{
"id": 1,
"name": "Order 1",
"legs": []
},
{
"id": 2,
"name": "Order 2",
"legs": [1]
},
{
"id": 3,
"name": "Order 3",
"legs": [2,3]
}
],
"legs": [
{
"id": 1,
"name": "Leg 1",
"stops": [1,2]
},
{
"id": 2,
"name": "Leg 2",
"stops": [2,3]
},
{
"id": 3,
"name": "Leg 3",
"stops": [1,3]
}
],
"stops": [
{
"id": 2,
"name": "Stop 2"
},
{
"id": 1,
"name": "Stop 1"
},
{
"id": 3,
"name": "Stop 3"
}
]
}
When I was using async:true, ember made http requests for the related legs (legs?ids[]=1 and legs?ids[]=2&ids[]=3) but would never make a request for the related stops.
So I switched to sideloading as shown above, but the stops are still NOT available from the related legs.
Can you help me understand how the loading of Models with nested hasMany relationships SHOULD be done? Or where I've gone wrong? I'm open to using either async: true or async: false, as long as I can get the nested data loaded in to the model.

Related

Load multiple model data in same api call emberjs?

So here is two models that i have defined in emberjs
match.js
import DS from 'ember-data';
export default DS.Model.extend({
team: DS.belongsTo('team', {async:true}),
opponent: DS.belongsTo('team', {async: true}),
type: DS.attr('string'),
squad: DS.attr('boolean')
});
and
team.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
logo: DS.attr('string')
});
I am already loading the match as a model. In the same api call i also want to load the model data for team. The api response that i have till now is
{
"meta":{
"type":"match"
},
"data":[
{
"id":1119536,
"type":"match",
"attributes":{
"id":1119536,
"team":{
"type":"team",
"id":1,
"attributes":{
"id":1,
"name":"England",
"logo":null
}
},
"opponent":{
"type":"team",
"id":3,
"attributes":{
"id":3,
"name":"Pakistan",
"logo":null
}
}
}
}
]
}
The match model data get loaded properly but i am having issues for the same with team data. The response is from network in browser and i already checked the model using ember plugin on browser that team data doesn't load. How can i use the same api call to load multiple models.
a few things to notice:
dont put the id in attributes
dont name an attribute type. Really dont! It's a reserved keyword.
relationships are not attributes and should be under relationships
use the included array to sideload data
ids must be strings
so for example this would be a valid payload:
{
"meta": {
"type": "match"
},
"data": [
{
"id": "1119536",
"type": "team",
"attributes": {
"match-type": "match"
},
"relationships": {
"team": {
"data": {
"type": "team",
"id": "1"
}
},
"opponent": {
"data": {
"type": "team",
"id": "3"
}
}
}
}
],
"included": [
{
"type": "team",
"id": "1",
"attributes": {
"name": "England",
"logo": null
}
},
{
"type": "team",
"id": "3",
"attributes": {
"name": "Pakistan",
"logo": null
}
}
]
}

Ember: Access sideloaded model relationship data in template

I am trying to display a model's relationships in a template from sideloaded data, but there seems to be some problem. With Ember Inspector, I see that the relationships are correctly loaded from the data. However, the data is not displayed on the page.
Looking for solutions or suggestions on where to start debugging. Much appreciated.
Handlebars:
<dt>Categories</dt>
<dd>
<ul>
{{#each model.categories as |category| }}
<li>{{ category.name }}</li>
{{/each}}
</ul>
</dd>
The route:
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model(params) {
return this.store.findRecord('datasheet', params.id);
}
});
The models:
// app/models/datasheet.js
export default DS.Model.extend({
name: DS.attr('string'),
name_en: DS.attr('string'),
news: DS.attr('string'),
news_en: DS.attr('string'),
basic_information: DS.attr('string'),
basic_information_en: DS.attr('string'),
id_gradient_default: DS.attr('string'),
icon_name: DS.attr('string'),
icon_color: DS.attr('string'),
order: DS.attr('string'),
item_count: DS.attr('string'),
categories: DS.hasMany('category')
});
// app/models/category.js
export default DS.Model.extend({
name: DS.attr('string')
});
This is the JSON returned from the adapter method:
{
"data": {
"type": "datasheet",
"id": "21",
"attributes": {
"name": "Projekty",
"name_en": "Projects",
"news": "",
"news_en": "",
"basic_information": "",
"basic_information_en": "",
"id_gradient_default": "27",
"icon_name": "pin_flag",
"icon_color": "",
"order": "14"
},
"relationships": {
"categories": ["18", "19", "20", "51", "52"]
}
},
"included": [{
"type": "category",
"id": "18",
"attributes": {
"name": "Project"
}
}, {
"type": "category",
"id": "19",
"attributes": {
"name": "Activity"
}
}, {
"type": "category",
"id": "20",
"attributes": {
"name": "Project phase"
}
}, {
"type": "category",
"id": "51",
"attributes": {
"name": "Program"
}
}, {
"type": "category",
"id": "52",
"attributes": {
"name": "Milestone"
}
}]
}
Ember Inspector screenshot:
This is not correct JSONAPI:
"relationships": {
"categories": ["18", "19", "20", "51", "52"]
}
This is the correct JSONAPI equivalent:
"relationships": {
"categories": {
data: [{
id: '18',
type: 'category'
},{
id: '19',
type: 'category'
},{
id: '20',
type: 'category'
},{
id: '51',
type: 'category'
},{
id: '52',
type: 'category'
}]
}
}
So your data are loaded but not correctly linked. you can see this in the ember-inspector when you check the categories relationship.

Displaying data for multi-level relationships with sideloaded json and Ember Data

I have an app that has many layers of relationships. I have a Tournament with n rounds, each round has n matchups, each matchup has n seats, each seat has 1 entry. Here's a sample of the json structure:
{
"tournament": {
"id": 1,
"title": "March Madness!!!",
"rounds": [1],
"active_round": 1
},
"rounds": [
{
"id": 1,
"tournament": 1,
"matchups": [1, 2]
},
{
"id": 2,
"tournament": 1,
"matchups": [3]
}
],
"matchups": [
{ "id": 1, "round": 1, "seats": [1, 2] },
{ "id": 2, "round": 1, "seats": [3, 4] },
{ "id": 3, "round": 2, "seats": [5, 6] }
],
"seats": [
{ "id": 1, "matchup": 1, "entry": 1 },
{ "id": 2, "matchup": 1, "entry": 2 },
{ "id": 3, "matchup": 2, "entry": 3 },
{ "id": 4, "matchup": 2, "entry": 4 },
{ "id": 5, "matchup": 3, "entry": "" },
{ "id": 6, "matchup": 3, "entry": "" }
],
"entries": [
{
"id": 1,
"seats": [1]
},
{
"id": 2,
"seats": [2]
},
{
"id": 3,
"seats": [3]
},
{
"id": 4,
"seats": [4]
}
]
}
I'm having trouble getting the content out. Here's my router.js:
App.Router.map( function() {
this.resource('tournament', { path: "/" });
});
App.TournamentRoute = Ember.Route.extend({
model: function() {
return new Ember.RSVP.Promise( function (resolve, reject) {
[..we just get the data return the json object above to setupController...]
});
},
setupController: function (controller, model) {
controller.set('model', model);
[i do a little data computation here prior to the renderTemplate function]
},
renderTemplate: function () {
var controller = this.controllerFor('tournament');
this.render('tournament');
}
});
My tournament.hbs template looks like this:
<h1>{{tournament.title}}</h1>
{{#each round in rounds}} Round id: {{round.id}} <br/>
{{#each matchup in round.matchups}} matchup id: {{matchup.id}}
<div class="matchup">
{{#each seat in matchup.seats}}
<div class="entry">
{{seat.entry.id}}
</div>
{{/each}}
</div>
{{/each}}
{{/each}}
And I'm getting the following on screen:
March Madness!!!
Round id: 1
matchup id:
matchup id:
Round id: 2
matchup id:
So, a little bit of it is working. I've done some work in the console and at the matchup level, the matchup object is actually the values "1" and "2", not matchups[0] and matchups[1], as expected, which is why there is no "id" attribute next to the matchup levels. I'm not sure how much "magic" there is in Ember data by using conventions, and can't find any examples that use this level of hierarchy. Thanks
UPDATE:
I'm including my models so as they are now, with the first responder's recommendations. I'm seeing the same results.
App.Tournament = DS.Model.extend({
title: DS.attr('string'),
active_round_index: DS.attr('number'),
rounds: DS.hasMany('App.Round', { embedded: 'always' })
});
App.Round = DS.Model.extend({
tournament: DS.belongsTo('App.Tournament'),
matchups: DS.hasMany('App.Matchup', { embedded: 'always' })
});
App.Matchup = DS.Model.extend({
round: DS.belongsTo('App.Round'),
seats: DS.hasMany('App.Seat', { embedded: 'always' })
});
App.Seat = DS.Model.extend({
matchup: DS.belongsTo('App.Matchup'),
entries: DS.hasMany('App.Entry', { embedded: 'always' })
});
App.Entry = DS.Model.extend({
title: DS.attr('string'),
seats: DS.hasMany('App.Seat')
});
** ANOTHER UPDATE **
So, as it turns out, the documented usage of the Ember.RSVP.Promise won't use all of the "magic" of Ember Data, which needs the RESTAdapter to do the fanciness. I plugged in the RESTAdapter and now things are working much better.
I can't see what your models look like, but one problem could be that you don't have the hasMany relationship definitions on your model marked as {embedded: 'always'}. This tells the ember serializer how to handle your related models when serializing and extracting.
example:
App.Round = DS.Model.extend({
tournament: DS.belongsTo('tournament'),
matchups: DS.hasMany('matchup', { embedded: 'always' })
});

Failed to implement computed property in emberjs

My fixture data contains multiple array.Out of this multiple array cart_items contains some product data.
I am trying to calculate total no of products available in cart data (based on length of cart_items items) but i am not able to calculate no of items are present in cart_items.
In router i have selected application fixture as model for current route,as follow :
Astcart.IndexRoute = Ember.Route.extend({
model: function() {
return Astcart.Application.find();
}
});
Computed property code :
Astcart.IndexController = Ember.ArrayController.extend({
tot_cart_prd: function() {
return this.get("model.cart_items").get('length');
}.property("#each.isLoaded")
});
And my fixture data is :
Astcart.Application.adapter = Ember.FixtureAdapter.create();
Astcart.Application.FIXTURES = [
{
"logo_url": "img/logo.jpg",
"logged_in": {
"logged": true,
"username": "sachin",
"account_id": "4214"
},
"category_list": [
{
"id": "1",
"name": "Mobiles & Accessories"
},
{
"id": "2",
"name": "Computers & Software"
},
{
"id": "3",
"name": "Fashion"
},
{
"id": "4",
"name": "Electronics"
},
{
"id": "5",
"name": "Watches & Jewelry"
},
{
"id": "6",
"name": "Health & Beauty"
},
{
"id": "7",
"name": "Games"
},
{
"id": "8",
"name": "Books & Entertainment"
},
{
"id": "9",
"name": "Gaming"
},
{
"id": "10",
"name": "Shoes & Bags"
}
],
"cart_items": [
{
"id": "1",
"name": "Samsung Galaxy Tab 2",
"qty": "1",
"price": "1245.12",
"subtotal": "7842.23"
},
{
"id": "2",
"name": "Samsung Galaxy Tab 2",
"qty": "1",
"price": "1245.12",
"subtotal": "7842.23"
},
{
"id": "3",
"name": "Samsung Galaxy Tab 2",
"qty": "1",
"price": "1245.12",
"subtotal": "7842.23"
}
]
}
];
I have posted my code here(JSFiddle).
Can any one tell me why this.get("model.cart_items") is returning null?
Because your IndexController receive an array of Astcart.Application, from the route. You need to iterate in each application and get the length of each category list .
Your computed property need to be the following:
Astcart.IndexController = Ember.ArrayController.extend({
tot_cart_prd: function() {
var result = this.get('model').map(function(application) {
return application.get('category_list.length');
});
return result;
}.property('model.#each.category_list.length')
});
Here's an updated fiddle http://jsfiddle.net/marciojunior/PZZym/
I just looked at this and your core issue has something to do with the relationship setup between Application and Cart_items. The reason that this.get("model.cart_items").get('length') is failing is that this.get("model.cart_items") returns null. If you can get your relationship working you should be on the right track. I don't know anything about EmberModel, so I can't be of much help there.

How to sideload ember hasMany and belongsTo Relationship?

I have a Person Model as follows
App.Person= DS.Model.extend({
id: DS.attr('string'),
name: DS.attr('string'),
visits: DS.hasMany('App.Visit'),
events: DS.hasMany('App.Event') ,
allergies: DS.hasMany('App.Allergies'),
get_allergies : function(){
return this.get('allergies').getEach('allergy_name').reduce(function(accum, item) {
return (accum.length > 0) ? (accum +', '+ item) : (item);
}, '');
}.property('allergies.#each.allergy_name')
});
App.Visit = DS.Model.extend({
visit_id: DS.attr('string'),
date: DS.attr('date'),
admission: DS.belongsTo('App.Admission')
});
App.Admission = DS.Model.extend({
loc: DS.attr('string'),
admission_date: DS.attr('date'),
care_team: DS.belongsTo('App.CareTeam')
});
As you can see Person hasMany "allergies", and along with person, allergies is also getting loaded for me because in the UI I am calling the get_allergies method while other hasMany relationships like "visits" and "events" are not getting loaded.
In UI {{person.get_allergies}}
I tried to sideload the relationships "visits" and "events"(using example on net), but that is not working? Can someone tell what is the proper way of sideloading ember-data because I couldnt find any proper documention with example on net except for few questions on stackoverflow itself?
According to the documentation, you should just add additional Visit and Event data in the response from the server.
{
"person": {
"id": 1,
...
"event_ids": [5, 6, 7]
},
"events": [{
"id": 5,
...
},
{
"id": 6,
...
},
{
"id": 7,
...
}]
}
The main point here is that Events data should be outside of Person data.
Do you use the standard REST adapter? Could you please add a sample json returned by the server?
To sideload data in my app, I configure ember-data to know about the kinds of things I'll be sideloading. E.g. to sideload events and visits, do this:
DS.RESTAdapter.configure('App.Event', {
sideloadsAs: 'events'
});
DS.RESTAdapter.configure('App.Visit', {
sideloadsAs: 'visits'
});
App.Store = DS.Store.extend({
revision: 11
});
and then in your json can look like this:
{
"person": {
"id": 1,
"event_ids": [5, 6, 7],
"visit_ids": [1, 2, 3]
},
"events": [
{ "id": 5 },
{ "id": 6 },
{ "id": 7 }
],
"visits": [
{ "id": 1 },
{ "id": 2 },
{ "id": 3 }
]
}
I had the same problem but set up a bit different. visits were unaware of their person (i.e. couldn't do visit.get('person.name')). I had to add a serializer for visit:
export default AppSerializer.extend({
attrs: {
person: 'personId'
},
});
My payload looks like this:
{
person: {
id: 1,
name: 'John',
visitIds: [1, 2, 3]
},
visits: [
{ id: 1,
personId: 1
},
{ id: 2,
personId: 1
},
{ id: 3,
personId: 1
}
]
}
person model:
export default DS.Model.extend({
name: DS.attr('string'),
visits: DS.hasMany('visit')
});
visit model:
export default DS.Model.extend({
person: DS.belongsTo('person')
});