Load but don't send embedded objects - ember.js

Say you have the following model:
App.Item = DS.Model.extend({
owner: DS.belongsTo('App.Person', {embedded: true})
})
This means you can load this embedded association, but it also means that if you want to create a new Item for a person that already exists ember-data will also embed the Person object for every new item.
Is it possible to make it load embedded objects but when creating associations only send the ids? i.e send this instead:
{"item": {"owner_id": 5}}
Edit:
To clarify, I want ember-data to load embedded relations, but if I set {embedded: true}
this code:
App.Item.createRecord({name: 'Something', owner: App.Person.find(1)});
// And a few moments later when App.Person.find(1) has loaded
App.store.commit()
It will send the following json:
{ "item": {"name": "Something", owner: { id: 1, name: "whatever" }}
But what I want is:
{ "item": {"name": "Something", owner_id: 1 }}
Basically if I set embedded = true ember-data will also embed the assocations when you create an object.

If I correctly understand your aim, you shouldn't have to specify { embedded: true }. The default ember-data behavior is to be lazy.
It you are using active_model_serializers (which I strongly recommend to you), you should declare your server-side serializer as follow:
class ItemSerializer < ActiveModel::Serializer
embed :ids, include: false
#...
end

Related

Ember.js how to properly initialize localstorage-adapter? (hasMany with async: true is not persisted)

I'm using ember.js with localstorage-adaper.js.
My problem started with an already answered question: EmberJS - record with hasMany relation fails to load
So having the models:
// Models
App.List = DS.Model.extend({
name: DS.attr('string'),
items: DS.hasMany('item')
});
App.Item = DS.Model.extend({
name: DS.attr('string') ,
list: DS.belongsTo('list')
});
When the #/list/1 template got rendered the the items weren't shown on the page and an assertion failed was thrown in the console:
Assertion failed: You looked up the 'items' relationship on 'App.List:ember236:1' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (DS.attr({ async: true }))
As specified, the solution is to make the hasMany async like this:
App.List = DS.Model.extend({
name: DS.attr('string'),
items: DS.hasMany('item',{async:true})
});
It works great for that scenario!
Next:
I'm loading data from the server and push it into the store when the application first loads like this: (You can find the JSBin for the example here: http://jsbin.com/lejizo/1/)
var data = { //in real life this is fetched through an AJAX request
'list': { id: '1', name: 'The List', items: ['1','2'] },
'items': {
'1': { id: '1', name: 'item 1', list: '1' },
'2': { id: '2', name: 'item 2', list: '1' }
}
};
...
this.store.push('list', data.list).save();
this.store.pushMany('item', data.items).forEach(function (item) {
item.save();
});
After setting the async:true option, I've noticed that the items ids are not persisted anymore into the localstorage. The JSON looks like this:
{"App.List":{"records":{"1":{"id":"1","name":"The List","items":[]}}},"App.Item":{"records":{}}}
Obviously the items are not shown since there is no reference to them.
I might think that I should find another way to populate the locastorage at first! How?
Or there is another workaround for this situation?
Working with aync:true on hasMany relationships beside the foreign keys not being persisted to the localstorae, also causes additional problems inside controllers. You get the Promise, not the actual object, so you always must use list.get('items').then(/*code here*/) (check out this question I've posted Ember local-storage How to use hasMany with async: true in controllers?), which in some scenarios where you use it inside a loop might cause a stackoverflow.
Using localstorage, you'll allways have all the data on the client side. It really makes no sense to work with async:true. The problem is that ember expects an array of vanilla objects for the list.items, instead of just ids. This is described here DS.FixtureAdapter loses fixture data with hasMany async attributes
There is an easy workaround for getting rid of async:true in your hasMany relationships. Please note that you have all needed data in localstorage! Ember doesn't throw the: "Assertion failed: You looked up the 'items' relationship on 'App.List:ember236:1' but some of the associated records were not loaded..." error anymore if he sees the items inside the memory store.
SOLUTION:
Ember.Route.reopen({
beforeModel: function(transition){
this.store.find('list');
this.store.find('items');
}
});
We override the Ember.Route object, so that in the beforeModel hook we call store.find('object'). What this does is forces Ember to load data from localstorage to the "in memory" store! You won't need the async:true anymore and no error will be thrown. Also, at first initialization the foreign keys will be persisted too!
You have to do this everytime (in the Route super class) because you'll never know on which route a refresh will occur. Also, for a store of ~10 models with up to 50 records, it runs in ~50-70ms. If this seems to much in your scenario, make sure you do this call only on the route you want and only for the models it needs.
Also, if you override beforeModel inside your routes, make sure you call
this._super(transition)
Hope this helps!
You defined your data wrong, try this:
var data = { //in real life this is fetched through an AJAX request
'list': { id: '1', name: 'The List', items: [1,2] },
'items': [
{ id: '1', name: 'item 1', list: '1' },
{ id: '2', name: 'item 2', list: '1' }
}];
}
First you do not need a index 1,2,... in front of your items.
Second hasMany relationship should be return an array that is why items is wrap as an array.
So to fix your issue you either have to fix the data from the server, or write a serializer to massage the data.
Hope it helps!

Ember Data 1.0.0: what is expected format for belongsTo relationship

I have the following models:
App.Publication = DS.Model.extend({
title: DS.attr('string'),
bodytext: DS.attr('string'),
author: DS.belongsTo('author')
});
App.Author = DS.Model.extend({
name: DS.attr('string')
});
And the folowing json data:
{
"publications": [
{
id: '1',
title: 'first title',
bodytext: 'first body',
author_id: 100
},
{
id: '2',
title: 'second title',
bodytext: 'second post',
author_id: 200
}
];
}
In Ember Data RC12 this worked (you could specify author_id OR author in the json and the publication would always have the correct author linked).
In Ember Data 1.0.0 this no longer works; author is always null.
In some documents I found that - since I am using "author_id" in the json data (and not simply author) - I need to specify the key in the model; thus:
author: DS.belongsTo('author', { key: 'author_id' })
This however does not work; the author in the publication remains null.
The only solution I see for now is to implement a custom serializer and override the author_id to author (via normailzeId); I cannot change my backend data structure ... thus:
App.MySerializer = DS.RESTSerializer.extend({
//Custom serializer used for all models
normalizeId: function (hash) {
hash.author = hash.author_id;
delete hash.author_id;
return hash;
}
});
Is the above the correct way ?
Ember Data 1.0 no longer does any payload normalization by default. The key configuration for DS.belongsTo has been removed as well so you will have to implement a custom serializer.
normalizeId is an internal serializer function used for converting primary keys to always be available at id. You shouldn't override this.
Instead, you can override the keyForRelationship method which is provided for this purpose.
You could use something like the following:
App.ApplicationSerializer = DS.RESTSerializer.extend({
keyForRelationship: function(rel, kind) {
if (kind === 'belongsTo') {
var underscored = rel.underscore();
return underscored + "_id";
} else {
var singular = rel.singularize();
var underscored = singular.underscore();
return underscored + "_ids";
}
}
});
Note: I've also renamed the serializer to App.ApplicationSerializer so that it will be used as the default serializer for your application.
Finally, if you haven't already found it, please take a look at the transition notes here: https://github.com/emberjs/data/blob/master/TRANSITION.md
If you read through the transition document shortly after the initial 1.0.0.beta.1 release I would recommend taking a look again as there have been a number of additions, particularly regarding serialization.
From the Ember 1.0.0 Transition Guide:
Underscored Keys, _id and _ids
In 0.13, the REST Adapter automatically camelized incoming keys for you. It also expected belongsTo relationships to be listed under name_id and hasMany relationships to be listed under name_ids.
If your application returns json with underscored attributes and _id or _ids for relation, you can extend ActiveModelSerializer and all will work out of the box.
App.ApplicationSerializer = DS.ActiveModelSerializer.extend({});
Note: DS.ActiveModelSerializer is not to be confused with the ActiveModelSerializer gem that is part of Rails API project. A conventional Rails API project with produce underscored output and the DS.ActiveModelSerializer will perform the expected normalization behavior such as camelizing property keys in your JSON.

Ember-data DS.Model.find() doesn't seems to work

I purchased play by play of Ember.js at peepcode.com and followed as the video offered.
So, I set the Model and Serializer and Controllers up in Rails.
When I typed URL like this.
http://localhost:3000/actors/wycats
the JSON response rendered as I expected. (It's probably Ember-data's expecting JSON form..right?)
{
"actor": {
"id": 1,
"blog": "http://www.yehudakatz.com",
"company": "Tilde, Inc.",
"email": "wycats#gmail.com",
"gravatar_id": "428167a3ec72235ba971162924492609",
"location": "San Francisco",
"login": "wycats",
"name": "Yehuda Katz",
"actor_type": "User"
}
}
So I set up the Store and Actor Model in ember.js
GithubScore.Store = DS.Store.extend({
revision: 11,
adapter: "DS.RESTAdapter"
});
GithubScore.Actor = DS.Model.extend({
login: DS.attr('string'),
name: DS.attr('string'),
gravatarId: DS.attr('string'),
location: DS.attr('string'),
blog: DS.attr('string')
});
And I launched my Ember App, No error occurred.
but when I tried to get a model using console
( I already had a model saved in Rails DB with ID of 1 )
GithubScore.Actor.find(1)
It returns a Class, No error occurred, but When I try to get an attribute of it.
it returns only null, although the model's status 'isLoaded'
GithubScore.Actor.find(1).get('isLoaded')
=> true
GithubScore.Actor.find(1).get('blog')
=> null
and I found that when I call GithubScore.Actor.find(1).get('isLoaded') repeatly at first time it returns only false, but when I try to get an attribute 'isLoaded' is changed to true immediately.
GithubScore.Actor.find(1).get('isLoaded')
=> false (for many times)
GithubScore.Actor.find(1).get('blog')
=> null
GithubScore.Actor.find(1).get('isLoaded')
=> true (immediately changed)
and when I try .toJSON() method to model as the video did. It throws an error.
GithubScore.Actor.find(1).toJSON()
=> TypeError: Object <GithubScore.Actor:ember272:1> has no method 'toJSON'
One thing I curious about is that, though GithubScore.Store object is extended from DS.Store.
It doesn't have find(type, id) method which DS.store already has.
I can't find what the problems are. Would you give me some help?
Thank you for reading!
Try instead to display the blog value in a template and access it through the browser. When you execute GithubScore.Actor.find(1).get('blog') it's returning null because Ember is retuning merely an object as a Promise while still in the process of fetching it. When you try instead to display the value in the template, the template is bound to the value and will be updated once it's retrieved.
As for isLoaded = true, apparently it's a bug, i experienced the same issue with RecordArray and it's been reported in previous question on stackoverflow as well from other people.

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)

why ember-data embedded association always send an ajax request?

I'm trying to use Ember-Data and the provided RESTAdapter to load an object like this :
{
"videos":[
{
"id":"5062f3c30959c6c732000005",
"tags":[
{"_id":"5062f3cb0959c6c732000006","name":"hello"},
{"_id":"5062f3cb0959c6c732000007","name":" world"}
]
}
]
}
Here I have a video object that has many tags. The tags attribute is declared as embedded :
Video = DS.Model.extend {
tags: DS.hasMany('Tag', {embedded: true})
}
Tag = DS.Model.extend {
video: DS.belongsTo('Video')
}
When I try to load video with
Video.find()
The adpter always try to send a get request to my server at /tags which naturally fails because my server doesn't give acces to tags directly. Instead, tags are already embedded in the /videos.json.
So what is the meaning of embedded: true in ember-data association ?
I believe vaguely what's happening is that it doesn't trust the embedded tags without id parameters. Try giving Tag an id: DS.attr('string', { key: '_id' }).
By the way, embedded is misspelled in your example -- is it correct in your app?
UPDATE: Perhaps better advice would be to set primaryKey: '_id' inside Tag. (doc)