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'
}
]
}
Related
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"
}
}
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!
I was wondering if you can side-load a hasMany relationship in ember-data - hooked on a non-id column. Here are my code snippets-
App.Profile = DS.Model.extend({
firstName: DS.attr(),
lastName: DS.attr(),
photo: DS.hasMany('photo', {async:true})
});
App.Photo = DS.Model.extend({
path: DS.attr('string'),
title: DS.attr('string'),
owner: DS.belongsTo('user', {async:true}),
});
App.ProfileSerializer = DS.RESTSerializer.extend({
attrs:{
photo: {embedded: 'load'}
},
});
The JSON returned by localhost:/api/profiles/ is:
[
{
"photos": [
"media/pic3.jpeg",
"media/pic4.jpeg"
],
"id": "5441b6b2bc8ae304d4e6c10e",
"first_name": "Dave",
"last_name": "Gordon",
"profile_pic": "media/profilePic.jpg",
"member_since": "2014-01-03T00:00:00",
"membership": "Silver",
"theme_pic": "media/profilePic.jpg"
}
]
As we see here, I am trying to hook up photos using 'path' field of photo instead of id of photos. I can't seem to get ember to send an async call. Is it possible to tell ember to make an async call based off of an non-id field. I feel there should be a way coz I intend to send an async call based off of a custom generated key. ANy help is greatly appreciated. Thank You
I wouldn't think of that as an association, but rather just another property with a type of array.
You should be able to just change your model like this:
App.Profile = DS.Model.extend({
firstName: DS.attr(),
lastName: DS.attr(),
photos: DS.attr()
});
Then you should be able to access the property as an array (possible object) in your template.
If necessary, you might need to create a custom transform like this:
App.ArrayTransform = DS.Transform.extend({
deserialize:function(value) {
return value;
}
});
Then you can do:
App.Profile = DS.Model.extend({
firstName: DS.attr(),
lastName: DS.attr(),
photos: DS.attr('array')
});
I have two models:
App.Offer = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
products: DS.hasMany('product')
});
App.Product = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
offer: DS.belongsTo('offer')
});
And the server is answering with record and array of ids in this way (for example if the rest adapter asks for /offers/1):
{ "offer": [ { "id": 1, "name": "aaaaaaaaa", "description": "aaaa", "product_ids": [ 1, 2 ] } ] }
but now how can I get the products? I have a route like this:
App.OffersRoute = Ember.Route.extend({
model: function() {
var offer = this.get('store').find('offer', 1);
return offer
}
});
In Ember guide is written that if you want the products you should do:
offer.get('products');
Ok, but where should I put this? in the model hook? in a Controller property?
I've tried many things but I can see no network request to products?id[]=1&id[]=2 as I expected (the server is responding correctly to this request);
Can someone please give an example showing how I can find an offer, its products and use this data in my template?
If you're using the RESTAdapter your data needs to be in this format (if you don't want to return it in this format you can create a custom serializer and fix up the json).
2 differences:
the item under offer shouldn't be an array, since you were looking for a single item it should be an object
the key product_ids should be products, product_ids is the format that the ActiveModelAdapter/ActiveModelSerializer use.
JSON
{
"offer":
{
"id":1,
"name":"aaaaaaaaa",
"description":"aaaa",
"products":[
1,
2
]
}
}
The hasMany relationship should be marked as async if you're expecting it to be returned in a separate payload.
App.Offer = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
products: DS.hasMany('product', {async:true})
});
I hooked it up in jsbin below, but I didn't hook up a result from products?ids[]=1&ids[]=2 (note ids[]=, not id[]=), if you check the network tab you'll see the request being issued (but it'll crash since there is no result).
http://emberjs.jsbin.com/OxIDiVU/345/edit
I am working on an application using ember.js and a couch DB backend. So far, i used ember-resource as database driver, but I am considering switching to ember-data, since this seems to be more sustainable.
Since I am working with couch DB, I am using the Couch DB-Adapter.
The documents in my database contain complete object structures, so I have to specify embedded objects in the database driver.
But although I am specifying my sub-objects as embedded, ember-data seems to fetch these objects with separate requests, instead of just getting them out of the main json.
My object definitions are as follows:
App.UserProfile = DS.Model.extend({
type: DS.attr('string'),
fullname: DS.attr('string'),
email: DS.attr('string'),
pictureUrl: DS.attr('string'),
social: DS.hasMany('App.SocialWebAccount', { embedded: true }),
.....
});
App.SocialWebAccount = DS.Model.extend({
profile: DS.belongsTo('CaiMan.UserProfile'),
site: DS.attr('string'),
account: DS.attr('string'),
.....
});
and the server data ist something like this:
{
"_id": "thoherr",
"_rev": "55-d4abcb745b42fe61f1a2f3b31c461cce",
"type": "UserProfile",
"fullname": "Thomas Herrmann",
"email": "test#thoherr.de",
"pictureUrl": "",
"social": [
{
"site": "socialFacebook",
"account": "thoherr"
},
{
"site": "socialXing",
"account": "Thomas_Herrmann7"
},
{
"site": "socialEmail",
"account": "test#thoherr.de"
}
]
}
After loading, the UserProfile does contain an ArrayProxy for my social data, which is populated by three entries, but they are all undefined instead of instances of SocialWebAccount!
If i try to access this array, ember-data seems to do a separate database access to fetch the data, which then leads to an error because the couch DB-adapter accesses an _id field, which is not available in undefined....
What am i missing?
I thought the "embedded" flag signals that the data is already in the json and the objects can be instantiated from the json?
Why does ember-data try to fetch the embedded data?
Any hint?
It seems that the embedded option has changed recently. I found some information in the test files on the ember-data github.
In these test files, the embedded content is defined like this
Comment = App.Comment = DS.Model.extend({
title: attr('string'),
user: DS.belongsTo(User)
});
Post = App.Post = DS.Model.extend({
title: attr('string'),
comments: DS.hasMany(Comment)
});
Adapter = DS.RESTAdapter.extend();
Adapter.map(Comment, {
user: { embedded: 'always' }
});
or
Adapter = DS.RESTAdapter.extend();
Adapter.map(Comment, {
user: { embedded: 'load' }
});
'always' seems to be used for embedded data without ids (your case),eg
id: 1,
title: "Why not use a more lightweight solution?",
user: {
name: "mongodb_expert"
}
'load' seems to be used for embedded data with ids
id: 1,
user: {
id: 2,
name: "Yehuda Katz"
}
Hoping it will help in your particular case. I've had a lot of trouble with hasMany relationships recently (I had to modify my adapter to get it work)