Ember Data not loading hasMany on store.find() - ember.js

I currently have a model with two hasMany relationships as seen below:
Ember Model:
DS.Model.extend({
createdAt: DS.attr('date'),
updatedAt: DS.attr('date'),
name: DS.attr('string'),
address: DS.attr('string'),
city: DS.attr('string'),
state: DS.attr('string'),
zip: DS.attr('string'),
users: DS.hasMany('user',{async:true}),
templates: DS.hasMany('template',{async:true})
});
Using this model, I have new templates/controllers/etc setup to create new users and new templates. I can successfully save new/update existing organizations, but when I try to push objects into either users or templates, I lose what may already be in the other of the existing users or templates array.
I can see this is because my loading of the organization in the controller to create new records is not pulling in the other records, it pulls in it's own array, but not the other, for example this code on the new templates controller:
New Template Controller
organizations: function(){
return this.store.find('organization');
}.property(),
actions: {
createTemplate: function() {
var self = this;
console.log(self.get('organizations'));
}
}
Will show the templates in the console when attached to the record, but not the users. Vise versa, the same code on the new users controller does not show the templates it is pulling in but does show the users.
What can I do to get my find to return both arrays of relationships. They are both in the database, I can see them calling the rest api, but just not when they are found using ember.
I'm still pretty new with Ember, so any help would be greatly appreciated.
Thanks.
Edit
Sample JSON response from server
{
"organization": {
"id": 40,
"name": "Sample Business LLC",
"address": "1234 Fun Avenue",
"city": "Happytown",
"state": "NE",
"zip": "12345",
"users": [4,1],
"templates": [4,1],
"createdAt": "2014-02-18T23:10:08.466Z",
"updatedAt": "2014-02-18T23:10:08.466Z"}
},
"meta": {
"href": "http://server/organizations/40"
}
}

Related

Reflexive relation with nested data

I'm sorry if this is a basic question, but since I'm quite new to ember, I'd like to know if there is any best practice for a case like this. For example, I have the follow endpoints that returns the payloads below:
https://api.example.com/v1/user
[
{
"user": "user1",
"firstName": "Foo1",
"lastName": "Bar1",
"url": "https://api.example.com/v1/user/user1"
},
{
"user": "user2",
"firstName": "Foo2",
"lastName": "Bar2",
"url": "https://api.example.com/v1/user/user2"
}
]
And each of the "url" endpoint returns something like this:
https://api.example.com/v1/user/user1
{
"user": "user1",
"firstName": "Foo1",
"lastName": "Bar1",
"age": 21,
"address": "User1 Address"
... more info ...
}
We see that some properties in "/user" are repeated in "/user/user1".
What would be the best practice to create the "user" model?
Should I have two models? Like for example a "users" model for the "/user" and a "user" model for "/user/user1"?
Could somehow have just one model "user" that would fit both endpoints?
Thanks in advance!
This is almost the use case described in the one-to-one docs where you're defining the user data with one model and linking another model with a belongsTo attribute:
// app/models/user.js
import DS from 'ember-data';
export default DS.Model.extend({
user: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string'),
url: DS.attr('string'),
profile: DS.belongsTo('profile')
});
then setup a profile model with any extra values you're wanting to add and define the belongsTo attribute also:
// app/models/profile.js
import DS from 'ember-data';
export default DS.Model.extend({
age: DS.attr('string'),
address: DS.attr('string'),
user: DS.belongsTo('user')
});
In your routes file you'll want to setup the user id to define your URL structure like so:
//app/router.js
Router.map(function() {
this.route('users');
this.route('user', { path: '/user/:user_id' });
});
Then finally you'll need to load the data retrieving the related records and loading them in via your route file.
// app/routes/user.js
import Route from '#ember/routing/route';
export default Route.extend({
model(params) {
return this.store.findRecord('user', params.user_id, {include: 'profile'});
}
});
It's worth pointing out that you may also need a serializer to massage the data into the format you're wanting.

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.

Ember Data: option to not use identity map and ignore relationship

One of the powers of Ember Data is that it uses identity maps to ensure that there is a single source of truth, but to ensure that this happens, it's very prescriptive on how the models need to be configured and what the server needs to return. But sometimes in a mostly read-only app, the single source of truth is not necessary and changing the API on the server is more work than necessary. In these instances, is there a way to tell Ember Data to just accept an array or object as is and don't try to map it to an identity map? Here's a quick example using widget and category:
This is what Ember Data wants us to do:
App.Widget = DS.Model.extend({
name: DS.attr('string'),
categories: DS.hasMany('category')
});
App.Category = DS.Model.extend({
name: DS.attr('string')
});
And Ember Data wants us to return this from the server:
{
"widget": {
"name":"Awesome Device!",
"categories":[1,2]
},
"categories":[
{id:1,"name":"Device"},
{id:2,"name":"Fun"}
]
}
But if this is a mostly a read-only app where the categories won't change, is there a way to tell Ember Data to treat categories as just simple value objects?
App.Widget = DS.Model.extend({
name: DS.attr('string'),
categories: DS.attr('array') // any way to do something like this?
});
Then the server can simply return this:
{
"widget": {
"name":"Awesome Device!",
"categories": [
{ id: 1, "name": "Device" },
{ id: 2, "name": "Fun" }
]
}
}
You can create your own transform, some common ones that you might attempt are raw, or array. I generally gravitate toward raw when I'm lazy
App.RawTransform = DS.Transform.extend({
deserialize: function(serialized) {
return serialized;
},
serialize: function(deserialized) {
return deserialized;
}
});
App.register("transform:raw", App.RawTransform);
App.Widget = DS.Model.extend({
name: DS.attr('string'),
categories: DS.attr('raw')
});
http://emberjs.com/api/data/classes/DS.Transform.html

How to load hasMany in Ember?

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

How to fetch embedded objects in one request with ember-data and couchdb-adapter

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)