Ember.js embedded records don't work - ember.js

I have a json like
{
"meta":{
"per":20,
"page":1,
"total":2
},
"users":[
{
"id":119506,
"first_name":"erglk",
"last_name":"wfe",
"email":"bent#exemple.com",
"groups":[
{
"id":5282,
"name":"test"
},
{
"id":8880,
"name":"everybody"
}
]
},
{
"id":119507,
"first_name":"eriglk",
"last_name":"wife",
"email":"benit#exemple.com",
"groups":[
{
"id":5284,
"name":"testf"
},
{
"id":8880,
"name":"everybody"
}
]
}
]
}
For the moment no problem to access the user but I have some difficulties to access the groups array. I've tried hasMany and belongsTo without success. I had errors.
I've read few articles about EmbededRecordMixin but without any success.
If I declare in my models :
export default DS.Model.extend({
first_name: DS.attr('string'),
last_name: DS.attr('string'),
email: DS.attr('string'),
groups: DS.attr('group')
});
I get : Error while processing route: users Assertion Failed: Unable to find transform for 'group' Error: Assertion Failed: Unable to find transform for 'group'

We use DS.attr to tell Ember that this field is an attribute of a model, and optionally we can specify a type of this attribute. By default, only allowed types are string, number, boolean, and date. To support custom type, special class (transform) should be defined. That's what Embers is trying to tell you with this error message. How to define such class, you may find here
But, you don't need to define a custom transform for your task. You need to define a relationship:
export default DS.Model.extend({
first_name: DS.attr('string'),
last_name: DS.attr('string'),
email: DS.attr('string'),
groups: DS.hasMany('group', {async: false})
});
And use an EmbeddedRecordMixin, as described in official docs. I can assure you that it works as described there.

I ran into the same issue, and figured out a fix given Gennady & Beni's responses, but it still took some time for me to get up and running.
see http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html
I created app/serializers/user.js:
import DS from 'ember-data';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
isNewSerializerAPI: true,
attrs: {
groups: { embedded: 'always' },
}
});
and in app/model/user.js
export default DS.Model.extend({
groups: DS.hasMany('group', {async: false}),
});
And then the model loaded the embedded properties right up!

Related

Error loading belongsTo relationship in Ember 2

I am having issues loading a belongsTo relationship - no errors get displayed, and no requests sent. The UI just remains blank. Given the following models:
project.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr(),
items: DS.hasMany('line-item', {async: true}),
customer: DS.belongsTo('customer', {async: false})
});
customer.js
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr(),
email: DS.attr(),
projects: DS.hasMany('project', {async: true})
});
The relationship between project and customer exists. When accessing the projects endpoint, the project gets correctly returned:
{
"data":{
"type":"projects",
"id":"3861b834-e270-4296-b7be-9aca55676874",
"attributes":{
"created":"2016-04-27T22:36:01.061349Z",
"modified":"2016-04-27T22:36:01.061477Z",
"name":"Sample name",
},
"relationships":{
"customer":{
"data":{
"type":"customers",
"id":"9242bd41-6bb0-41ed-b5f3-21df26486d9e"
}
},
"items":{
"meta":{
"count":0
},
"data":[
]
}
}
}
}
However when trying to access the customer, nothing happens (with nothing I mean: no console output, no request to the customers endpoint etc. The UI just fails to load):
this.get('project').get('customer');
Accessing other attributes works (including the project's items).
Any idea as to where I am going wrong?
In your project model you defined customer as async: false, which means it should be provided when loading the projects from your server. From the json output you provided the customer data is missing.
So either include the customer record when returning the json from your server or make customer async: true, so it will get loaded when calling project.get('customer')

Emberdata How to make a request on a dynamic segment

This is my router:
Router.map(function() {
this.route('merchant', { path:'/merchant/:id' }, function() {
this.route('product-tag');
Currently my api works like this. So I'm trying to get all the product tags that belong to merchant with id: 1781.
http://localhost:3001/merchant/1781/product_tags
The closest I've gotten is using a the product-tag route doing something like this:
model: function() {
debugger;
var parentModel = this.modelFor('merchant').merchant;
return this.store.find('product-tag', { merchant_id: parentModel.id});
}
This will generate a request:
http://localhost:3000/product_tags?merchant_id=1781
I'd assume that because product_tag is a subroute of merchant it'd take into account the dynamic segment of merchant but that doesn't seem to be the case.
Thanks for the help.
My models are as follows:
Merchant:
export default DS.Model.extend({
user_id: DS.attr('number'),
tags: DS.hasMany('product-tag', {async: true})
});
product-tag:
export default DS.Model.extend({
merchant: DS.belongsTo('merchant', {async: true}),
name: DS.attr('string'),
active: DS.attr('boolean'),
taggings_count: DS.attr('number')
});
model hook has two arguments. The first one should content dynamic segments. So, something like this should work:
//Router
Router.map(function() {
this.route('merchant', { path:'/merchant/:merchant_id' }, function() {
this.route('product-tag');
//Route
model: function(params) {
return this.store.find('product-tag', { merchant_id: params.merchant_id});
}
As for the second part of your question, ember data doesn't support nested URLs. Discussion on this subject
Your application routes are unrelated to the API endpoints Ember Data will make requests to. Adapters will build the API request per model.
If you have control of the API server, the easiest way to retrieve merchant's product tags is to send a link with your merchant payload. I don't know what format your API uses, but should be something like:
"merchant": {
"id": "1",
"user_id": "10"
"links": {
"product-tags": "http://localhost:3001/merchant/1781/product_tags"
}
}

switch to RESTAdapter - EmberJS

I have been looking at EmberJS tutorials, but all of them use FixtureAdapter, and I'm trying to switch to RESTAdapter, but I'm facing a persistent error
Error: Assertion Failed: Expected an object as `data` in a call to push for Books.Book , but was undefined
here's the code for the adapter:
Books.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'http://localhost:24818/api/'});
and calling the api in the router:
Books.BooksRoute = Ember.Route.extend({
model: function() {
return this.store.find('book',1);
}
});
How do I get store.find to return a JSON to be used in the Handlebars template?
Many thanks,
Edit: this is my API response:
[{"Id":1,"Title":"Pride and Prejudice","Year":1813,"Price":9.99,"Genre":"Comedy of manners","AuthorId":1,"Author":null},{"Id":2,"Title":"Northanger Abbey","Year":1817,"Price":12.95,"Genre":"Gothic parody","AuthorId":1,"Author":null},{"Id":3,"Title":"David Copperfield","Year":1850,"Price":15.00,"Genre":"Bildungsroman","AuthorId":2,"Author":null},{"Id":4,"Title":"Don Quixote","Year":1617,"Price":8.95,"Genre":"Picaresque","AuthorId":3,"Author":null}]
Edit: add model definition:
Books.Book = DS.Model.extend({
title: DS.attr('string'),
year: DS.attr('number'),
price: DS.attr('number'),
genre: DS.attr('string'),
authorId: DS.attr('number'),
author: DS.attr('string')
});
I think you need to normalize your response. i.e
"books": [
{
"id":1,
"Title":"Pride and Prejudice",
"Year":1813,
"Price":9.99,
"Genre":"Comedy of manners",
"AuthorId":1,"Author":null
},
{
"id":2,
"Title":"Northanger Abbey",
"Year":1817,
"Price":12.95,
"Genre":"Gothic parody",
"AuthorId":1,
"Author":null
},
{
"id":3,
"Title":"David Copperfield",
"Year":1850,
"Price":15.00,
"Genre":"Bildungsroman",
"AuthorId":2,
"Author":null
},
]
If you have no control over the endpoint. You will need to setup a serializer or normalizer to normalize your response into this expected format.
--
Useful Links
Ember-data Model Maker
Ember JSON Conventions
REST Serializer

Ember-data Serialize/Deserialize embedded records on 3rd level

Pardon me for coming up with this title but I really don't know how to ask this so I'll just explain.
Model: Group (has) User (has) Post
Defined as:
// models/group.js
name: DS.attr('string'),
// models/user.js
name: DS.attr('string'),
group: DS.belongsTo('group')
// models/post.js
name: DS.attr('string'),
user: DS.belongsTo('user'),
When I request /posts, my server returns this embedded record:
{
"posts": [
{
"id": 1,
"name": "Whitey",
"user": {
"id": 1,
"name": "User 1",
"group": 2
}
}
]
}
Notice that the group didn't have the group record but an id instead.
With my serializers:
// serializers/user.js
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
group: {embedded: 'always'}
}
});
// serializers/post.js
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
user: {embedded: 'always'}
}
});
This expects that the User and Post model have embedded records in them. However, it entails a problem since in the json response doesn't have an embedded group record.
The question is, is there a way I can disable embedding records in the 3rd level?
Please help.
Here is a good article about serialization:
http://www.toptal.com/emberjs/a-thorough-guide-to-ember-data#embeddedRecordsMixin
Ember docs:
http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html
Basically, what it says is that you have 2 options 1) serialize and 2) deserialize. Those two have 3 options:
'no' - don't include any data,
'id' or 'ids' - include id(s),
'records' - include data.
When you write {embedded: 'always'} this is shorthand for: {serialize: 'records', deserialize: 'records'}.
If you don't want the relationship sent at all write: {serialize: false}.
The Ember defaults for EmbeddedRecordsMixin are as follows:
BelongsTo: {serialize:'id', deserialize:'id'}
HasMany: {serialize:false, deserialize:'ids'}

Strange behaviour with Ember-Data

I'm seeing some strange behaviour with my application using Ember(v1.5.1) and Ember-Data(1.0.0-beta.8.2a68c63a)
I have a simple data structure where Contacts belong to Organizations, and both have many Groups (async). Here is (using Ember-App-Kit):
CONTACT MODEL:
export default DS.Model.extend({
organization: DS.belongsTo('organization', {async: false}),
name: DS.attr('string'),
lastname: DS.attr('string'),
emails: DS.hasMany('email', {'async': true}),
groups: DS.hasMany('group', {'async': true}),
//... more internal functions
});
ORGANIZATION MODEL:
export default DS.Model.extend({
name: DS.attr('string'),
groups: DS.hasMany('group', {'async': true}),
//... more internal functions
});
GROUP MODEL:
export default DS.Model.extend({
name: DS.attr('string'),
//... more internal functions
});
And the result from my API is:
ORGANIZATION RESPONSE:
{
organization: {
id: 3,
name: "Organization X",
links: {
groups: "./groups"
}
}
}
CONTACT RESPONSE:
{
contact: {
id: 2,
organization: 3,
name: "John",
lastname: "Smith",
links: {
emails: "./emails",
groups: "./groups"
}
}
}
Now when I try to retrieve the groups from the Contact, everything works fine. But trying to retrieve the groups of an organization through a Contact it does nothing, I don't even see the network request in the console. More surprisingly is that if I try to retrieve another Organization from the store directly and access to the groups, that works fine. So that makes me think that the problem is not in the relationship definition, but somewhere else. Any ideas?
INSIDE ROUTE
setupController: function(controller, model) {
// Call _super for default behaviour
this._super(controller, model);
var toLoad = [
model.get('groups'), <--- THIS WORKS FINE
model.get('organization').get('groups'), <---- THIS RETURNS EMPTY ARRAY, WITHOUT NETWORK REQUEST
this.store.find('organization', 3)
];
return Em.RSVP.all(toLoad, 'loading dependencies')
.then(function(results) {
return results[2].get('groups');
}).then(function(groups){
console.log(groups.get('length'));
controller.set('orgGroups', groups); <---- THIS WORKS FINE TOO!!!!
});
},
Updated: I know that the relationship between contact and organization is working because I can access the organization from the user
You don't have organization defined in your contact json.
Update:
You've defined organization as non async (which it is by default, no need to explicitly call it out) yet you don't have the organization defined in the json returned, just the id.
{
contact: {
id: 2,
organization: 3,
name: "John",
lastname: "Smith",
links: {
emails: "./emails",
groups: "./groups"
}
},
organizations:[
{
id: 'foo',
name: 'bar'
}
]
}