Ember data relationship and fixtures - ember.js

With ember data relationship can be {async: true} or {async: false}. How to create a model FIXTURES that mimic the behavior of an synced relashionship as described in the documentation :
var attr = DS.attr,
hasMany = DS.hasMany,
belongsTo = DS.belongsTo;
App.Post = DS.Model.extend({
title: attr(),
comments: hasMany('comment'),
user: belongsTo('user')
});
App.Comment = DS.Model.extend({
body: attr()
});
Ember Data expects that a GET request to /posts/1 would return the JSON in the following format:
{
"post": {
"id": 1,
"title": "Rails is omakase",
"comments": ["1", "2"],
"user" : "dhh"
},
"comments": [{
"id": "1",
"body": "Rails is unagi"
}, {
"id": "2",
"body": "Omakase O_o"
}]
}

You'll want to override the find method of the adapter. Here is the current implementation of the find method in the FixtureAdapter. You can see that it simply finds the record with the given ID, then returns it. You're going to want to modify that so that it side-loads the proper records. Something like this:
var json = {};
json[type.typeKey] = requestedRecord;
type.eachRelationship(function(name, meta) {
if (!meta.async) {
json[meta.type.typeKey.pluralize()] = [ /* put related records here */ ];
}
});
The syntax may not be perfect, but you should get the idea.

Related

Ember: accessing sideloaded data in setupController with ember-data

I'm using RESTAdapter and trying to figure out how to access sideloaded data.
A sample of the payload is:
{
"category": {
"categoryName": "test category",
"id": 6,
"products": [
4419,
502,
3992
]
},
"products": [{
"description": "Whatevs",
"id": 4419,
"name": "Product 1",
"price": 114.95,
"skuid": "S21046"
}, {
"description": "Whatevs",
"id": 502,
"name": "Product 2",
"price": 114.95,
"skuid": "SOLS2594"
}, {
"description": "Whatevs",
"id": 3992,
"name": "Product 3",
"price": 114.95,
"skuid": "S21015"
}]
}
I can see 'category' and 'product' data models (and data) in the ember inspector, so I know they are being loaded.
I can even access the products in the template model.products. BUT I can't access model.products in the route's setupController. The error I get is:
TypeError: Cannot read property '_relationships' of undefined
This is really perplexing me! My route model hook is:
model(params) {
return this.get('store').queryRecord('category', {
id: params.id
})
}
The setupController hook (that causes the error) is:
setupController(controller, model) {
controller.set('results', model.products);
}
The 'category' model:
export default DS.Model.extend({
products: hasMany('product'),
categoryName: attr('string')
});
The 'product' model:
export default DS.Model.extend({
name: attr('string'),
skuid: attr('string'),
price: attr('number'),
description: attr('string')
});
My template (which works, if I remove the 'setupController' hook from the route):
{{#each model.products as |product|}}
{{product.name}} {{product.skuid}}<br />
{{/each}}
I'd like to be able to access model.products from the route's setupController so I can call it something else. Any help appreciated.
Relationship returns Promises. so to get the result you need to use then. but accessing it in template will work because template is by default promise aware.
setupController(controller, model) {
//controller.set('results', model.products);
model.get('products').then((result) => {
controller.set('results',result);
});
}
Please give read on relationships as promises guide.

Ember Data and each loop and nested objects in Handlebars

I have an issue when trying to display a list of objects where each object has nested objects and there is even another level of objects in those objects. The API-respons gives me this (simplified) JSON-data where there are many freight orders:
{
"freightOrders": [{
"id": 1,
"comment": "Freight order comment",
"shipments": [{
"id": 1,
"shipment_lines": [{
"id": 1,
"description": "A description",
"package_weight": 900,
"package_length": 1200,
"package_height": 400,
"package_width": 800
}],
"pickup_address": {
"id": 1,
"address": "The pickup address",
"zip": "9000"
},
"delivery_address": {
"id": 2,
"address": "The delivery address",
"zip": "8000"
},
}],
}]
}
What I want is to display a list of all freight orders, and for the time being, access directly the first shipments-line for each order. In Handlebars I have tried
{{#each model as |order|}}
<span>
{{order.shipments.0.pickup_address.address}},
{{order.shipments.0.pickup_address.zip}}
</span>
{{/each}}
and
{{#each model as |order|}}
{{#each order.shipments as |shipment|}}
<span>
{{shipment.pickup_address.address}},
{{shipment.pickup_address.zip}}
</span>
{{/each}}
{{/each}}
Edit: Here is the order model as requested:
import DS from 'ember-data';
export default DS.Model.extend({
comment: DS.attr('string'),
shipments: DS.hasMany('shipment', { inverse: null })
});
and the shipment model for good measure:
import DS from 'ember-data';
export default DS.Model.extend({
pickup_address: DS.belongsTo('address', { inverse: null }),
delivery_address: DS.belongsTo('address', { inverse: null }),
shipment_lines: DS.hasMany('shipment-line', { inverse: null })
});
Whatever I try to do, I am not able to access shipments element or any nested objects of the order object.
I can mention that I have also tried to create the shipments part as a component and pass order.shipments to the component, but to no prevail.
Searching SO and google does not reveal any hints only some examples of how to do nested each in ember 1.x
So, how can one access nested objects in an each-loop in Handlebars and Ember Data?
I don't know if I got enough information, but let's start with an observation:
The JsonApi spec describes the use of hyphens instead of underscores. So your payload should be shipment-lines (etc). Ember uses the JsonApi as default, so you should follow this, or fix it with serializers.
For example:
export default DS.JSONAPISerializer.extend({
keyForAttribute: function(attr, method) {
return Ember.String.underscore(attr);
}
});
Note that ember 'understands' that the underscores should be capitalized in your model. Your payload could be enhanced to look look this [1]:
{
"freightOrders": [{
"id": 1,
"comment": "Freight order comment",
"shipments": [{
"id": 1,
"shipment-lines": [{
"id": 1,
"description": "A description",
"package-weight": 900,
"package-length": 1200,
"package-height": 400,
"package-width": 800
}],
"pickup-address": {
"id": 1,
"address": "The pickup address",
"zip": "9000"
},
"delivery-address": {
"id": 2,
"address": "The delivery address",
"zip": "8000"
},
}],
}]
}
And your shipment model:
import DS from 'ember-data';
export default DS.Model.extend({
pickupAddress: DS.belongsTo('address', { inverse: null }),
deliveryAddress: DS.belongsTo('address', { inverse: null }),
shipmentLines: DS.hasMany('shipment-line', { inverse: null })
});
In your template you should be able to do a simple loop:
{{#each model.shipments as |shipment|}}
<span>{{shipment.pickupAddress.address}}</span>
{{/each}}
[1] Better would be if you use attributes and relations in your payload to be full JSON API compliant, see for more info: http://jsonapi.org/

Ember-Data 2.1.0 hasMany not working (cannot read property 'replace' of undefined)

I searched everywhere but can't seem to find an answer for this simple problem here on SO.
Problem:
I have a hasMany relationship in a model that is loaded by the route in a findAll(). Payload looks fin according to many answers I've seen here, but I get "TypeError: Cannot read property 'replace' of undefined". More details below.
How can I get this hasMany relationship to work? I'm using asynch:false and sending the sideload as recommended.
Using: Ember 2.1.0, Ember-Data 2.1.0.
Stacktrace:
TypeError: Cannot read property 'replace' of undefined
at Object.func (ember.debug.js:36026)
at Object.Cache.get (ember.debug.js:13165)
at decamelize (ember.debug.js:36068)
at Object.func (ember.debug.js:35974)
at Object.Cache.get (ember.debug.js:13165)
at Object.dasherize (ember.debug.js:36072)
at ember$data$lib$system$normalize$model$name$$normalizeModelName (normalize-model-name.js:13)
at ember$data$lib$serializers$json$serializer$$default.extend.modelNameFromPayloadKey (json-api-serializer.js:267)
at ember$data$lib$serializers$json$serializer$$default.extend._extractType (json-api-serializer.js:258)
at ember$data$lib$serializers$json$serializer$$default.extend.normalize (json-api-serializer.js:290)
Route:
app/routes/search.js
export default Ember.Route.extend({
model(params) {
if(params.query){
return this.store.findAll('search-result');
}
return null;
},
actions:{
sendSearch: function(queryString){
this.store.unloadAll('search-result');
this.refresh();
}
}
});
Models:
app/models/search-result.js
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
url: DS.attr('string'),
tags: DS.hasMany('search-result-tag', {async:false})
});
app/models/search-result-tag.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
});
Adapter (for search-result)
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'http://localhost:8080',
urlForFindRecord(id, modelName, snapshot) {
let url = this._super(...arguments);
let query = Ember.get(snapshot, 'adapterOptions.query');
if (query) {
url += '?' + Ember.$.param(query); // assumes no query params are present already
}
return url;
},
urlForFindAll(modelName) {
var queryDict = {};
location.search.substr(1).split("&").forEach(function(item) {queryDict[item.split("=")[0]] = item.split("=")[1]})
let url = this._super(...arguments);
let query = queryDict['query'];
if (query) {
url += '?query=' + query; // assumes no query params are present already
}
return url;
}
});
Payload
{
"search-result-tags": [
{
"name": "this-is-tag-#-0",
"id": 0
}
],
"search-results": [
{
"description": "This is description for blabla2",
"id": 0,
"title": "Blabla 2",
"url": "http://blablabla2.com",
"tags": []
},
{
"description": "This is description for blabla",
"id": 1,
"title": "Blabla",
"url": "http://blabla.com",
"tags": [
0
]
}
]
}
You need to use the RESTSerializer in addition to the RESTAdapter. So app/serializers/application.js would be -
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
});
See the docs. You may need to override keyForAttribute, if you need to change cases / underscores of your keys.
Note that if you are using Rails API for the backend you want ActiveModelAdapter and ActiveModelSerializer, which are available as an addon.

Ember cli - Models composed of multiple words and relationships (ember-data)

Currently developing an app using Ember-cli and having some dificulties with models with 2 words and relationships.
Model residents-profile
//models/residents-profile.js
DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
picture: DS.attr('string'),
phone: DS.attr('string'),
gender: DS.attr('string'),
residentsAccount: DS.belongsTo('residentsAccount')
}
** Model Residents-account **
//models/residents-account.js
DS.Model.extend({
email: DS.attr('string'),
password: DS.attr('string'),
profileId: DS.attr('number'),
residentsProfile: DS.belongsTo('residentsProfile', this.profileId),
});
the model hook on the residents route:
//routes/residents.js
model: function() {
return Ember.RSVP.hash({
residentsProfile: this.store.find('residentsProfile'),
residentsAccount: this.store.find('residentsAccount')
})
}
As soon as I try and fetch just the residents profile, i get an error "residents.index Cannot read property 'typeKey'"
But if I remove the relationship key from residents-profile and call only the residents profile the data is fetched correctly.
I'm using the RESTAdapter and a Restful API,
The models are being returned separately and the response from the server is as follows:
GET /residentsProfile
{
"residentsProfiles": [{
"id": 20,
"picture": null,
"phone": null,
"firstName": "Rocky",
"lastName": "Balboa",
"blockId": null,
"unitId": null,
"createdAt": "2014-09-17 19:54:28",
"updatedAt": "2014-09-17 19:54:28",
"residentsAccount": [5]
}]
}
GET /residentsAccount
{
"residentsAccounts": [{
"id": 5,
"email": "rocky#balboainc.me",
"admin": false,
"resident": true,
"profileId": 20,
"createdAt": "2014-09-17 19:54:29",
"updatedAt": "2014-09-17 19:54:29",
"residentsProfile": [20]
}]
}
EDIT
Besides the changes proposed by #Kingpin2k, which were spot on, i used setupcontroller in the following way:
setupController: function(controller,
controller.set('residentsAccount', models.residentsAccount);
controller.set('residentsProfile', models.residentsProfile);
}
Now everything works.
Three problems, both of your relationships should be async (since they aren't returned in the same response)
residentsAccount: DS.belongsTo('residentsAccount', {async: true})
residentsProfile: DS.belongsTo('residentsProfile', {async:true})
And both of the json response from them should be a single id, not an array
{
"residentsProfiles":[
{
"id":20,
"picture":null,
"phone":null,
"firstName":"Rocky",
"lastName":"Balboa",
"blockId":null,
"unitId":null,
"createdAt":"2014-09-17 19:54:28",
"updatedAt":"2014-09-17 19:54:28",
"residentsAccount": 5
}
]
}
Lastly, I'm not sure what you're trying to accomplish with this.profileId here, but it probably isn't doing what you think it is. this in that scope is probably the window, meaning you're passing in undefined likely.

Force ember route to request additional data before rendering

I'm trying to get a nested route to make a request for additional data, update the record, and then render the view. See below:
// models
var attr = DS.attr,
hasMany = DS.hasMany,
belongsTo = DS.belongsTo
App.List = DS.Model.extend({
title: attr('string'),
links: hasMany('link')
})
App.Link = DS.Model.extend({
list: belongsTo('list'),
subtitle: attr('string'),
})
// JSON for index route
{
"list": [
{
"id": 532,
"title": "first list"
},
{
"id": 991,
"title": "second list"
},
{
"id": 382,
"title": "third list"
}
]
}
// JSON for list route - /user/532
{
"list":
{
"id": 532,
"title": "list numero uno",
"links" : [1, 2]
},
"link": [
{
"id": 1,
"subtitle": "this is a subtitle of the firsto listo!"
},
{
"id": 2,
"subtitle": "this is a subtitle of a second part in the list"
}
]
}
// routing
this.resource('user', function(){
this.route('list', {path: ':id'})
})
App.UserRoute = Ember.Route.extend({
model: function(){
return this.store.find('list')
}
})
App.UserListRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('list', params.id)
}
})
I want the index route to display a basic list with just the {{title}}, and clicking on one links it to UserListRoute which then makes another ajax call to update the record with the additional link data. I've tried adding:
afterModel: function(modal){
model.reload()
}
to the UserListRoute but it initially uses the index route model, and then it reloads with the new data. I'm trying to avoid this, and make it send an ajax request immediately and then render the content - so I don't want it to depend on the parent model/data.
create a different record type, and extend from the original, or just use a completely different model.
App.BasicList = DS.Model.extend({
foo: DS.attr()
});
App.FullList = App.BasicList.extend({
//foo
bar: DS.attr()
});
App.UserListRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('fullList', params.id)
}
})
I tried with kingpin2k's answer, and it worked on a page refresh but navigating from the user index to the nested still caused it to use the parent model. I figured out why it wasn't used the correct model. My links on the user index template were:
{{#link-to 'user.list' this}} {{title}} {{/link-to}}
So clicking on link correctly redirects me to /users/:id, but it passes on the data of the item selected. To remedy this I did:
{{#link-to `user.list` id}} {{title}} {{/link-to}}
So by changing this to id, when it transitions to the route, it uses the appropriate model specified in the route.