I want to use Ember Data but the API I am coding against does not respect all the conventions.
My problem is that the API does not return sideloaded relationships but relationships should be loaded using a REST structure: /model/id/relationship.
I have the following model:
Library = DS.Model.extend
name: DS.attr 'string'
groups: DS.hasMany 'group', {async: true}
groupsCount: DS.attr 'number'
The payload looks like:
{
library: {
groups_count: 44,
name: "demo",
id: "545262a063726d2514390100"
}
}
When I attempt to load groups using library.get('groups') nothing happens. It should make a call to libraries/545262a063726d2514390100/groups
Can I change my RESTAdapter to make this work?
After some research I found a way that works for me:
LibrarySerializer = DS.RESTSerializer.extend
normalize: (type, hash, prop)->
hash.links =
groups: "groups"
#_super(type, hash, prop)
This essentially adds a links object to the response, making the rest adapter follow that path to retrieve the relationship.
Related
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.
I am trying to config an ember app using ember-data to connect and read data from one api.
My model is:
App.Movie = DS.Model.extend
title: DS.attr 'string'
rating_average: DS.attr 'string'
short_plot: DS.attr 'string'
free: DS.attr 'boolean'
My api returns:
{
"pagination": {
"count":xx,
"page": x,
"total_pages": x
},
"movies": [
{
"id": xxxx,
"title": "xxx",
"rating_average": "x",
"short_plot": "xxxx",
"already_seen": x,
....
....
When ember try lo load data it throws:
Assertion failed: Your server returned a hash with the key **pagination** but you have no mapping for it
Ember does not expect the "pagination" keys in the Json. How can I specify that only try to read from the key 'movies' ?
While it is possible to customize ember-data to handle this, consider using ember-model instead. ember-data works best when you have control over your API and can ensure that it follows a set of conventions. If that is not the case you will find yourself fighting to make ember-data work with your API and it was simply not designed for this use case. ember-model was designed for this use case and while it does less for you out-of-box it will be much easier to customize. See ember-model-introduction to learn more.
That said, to make things work with ember-data you'll need to extend the rest adapter and customize it's ajax function.
App.Store = DS.Store.extend({
adapter: 'App.Adapter'
});
App.Adapter = DS.RESTAdapter.extend({
ajax: function(url, type, hash) {
var promise = this._super(url, type, hash);
return promise.then(function(json) {
delete json.pagination;
return json;
});
}
});
In this custom adapter we are calling the rest adapter's ajax function - see source - which returns a promise. The promise's then() function can be used to strip the key pagination from the result. Of course that means the pagination key will be removed from every json response.
Two of the models in my application are projects and users. Projects can have many users assigned to them and vice versa. I'm using Ember Data, so my model looks like this:
App.Project = DS.Model.extend({
name: DS.attr('string'),
created: DS.attr('date'),
users: DS.hasMany('App.User')
});
When creating a project on the server, the API expects to receive the project's name AND an array of IDs corresponding to the project's users. So, basically, something like this:
POST /projects
{
project: {
name: 'My Project',
users: [1, 10, 14]
}
}
However, Ember Data isn't including the array of user IDs when sending a POST or PUT request. By default, it only includes the name attribute. How can I modify Ember Data to include what I need? Is it even worth doing, or should I go the Discourse route and abandon Ember Data for now?
Assuming you are using the latest version and the RESTAdapter/RESTSerializer, you can override the addHasMany method of the serializer.
So, here is an example of how to do this:
App.CustomSerializer = DS.RESTSerializer.extend({
addHasMany: function(hash, record, key, relationship) {
var ids = record.get(relationship.key).map(function(item) {
return item.get('id');
});
hash[relationship.key] = ids;
},
});
App.Store = DS.Store.extend({
adapter: DS.RESTAdapter.extend({
serializer: App.CustomSerializer.create()
})
});
Note that the addHasMany implementation is taken from https://github.com/emberjs/data/blob/master/packages/ember-data/lib/serializers/fixture_serializer.js#L41
I'm building an Ember.js app which allows Many-to-Many relationships between 2 entities, e.g. Post and Tag:
App.Post = DS.Model.extend({
title: DS.attr("string"),
body: DS.attr("string"),
tags: DS.hasMany("App.Tag")
});
App.Tag = DS.Model.extend({
name: DS.attr("string"),
posts: DS.hasMany("App.Post")
});
I'm having difficulty getting Ember to serialize the many-to-many relationship when persisting new records. This is how I'm currently doing it:
// Create the post
post = store.createRecord(App.Post, {title: "Example", body: "Lorem ipsum..."});
// Create the tag
tag = store.createRecord(App.Tag, {name: "my-tag"});
// Add the tag to the post
post.get("tags").addObject(tag);
// Add the post to the tag
tag.get("posts").addObject(post);
// Save
store.commit();
The new records show up in the DOM, and are POSTed to my API, however the serialization doesn't include the relationship between them. For example, the serialization of the post looks like:
title=Example&body=Lorem+ipsum...
I would expect it to also include the tags it has been associated with.
Where am I going wrong?
By default, hasMany relationships are only serialized to an _ids array in your JSON if you configure the relationship as embedded in your serializer. Take a look at this answer for more details.
You can do it with Ember Data 1.0.0 by overriding the serializer:
http://mozmonkey.com/2013/12/serializing-embedded-relationships-ember-data-beta/
Has anybody come up with an answer for polymorphic associations and ember-data?
We would need some way of being able to query the type at the other end of the relationship from what I can tell.
Anybody any thoughts on this?
With the latest ember-data build you can now use polymorphic associations:
You need to configure your Models to make it polymorphic:
/* polymorphic hasMany */
App.User = DS.Model.extend({
messages: DS.hasMany(App.Message, {polymorphic: true})
});
App.Message = DS.Model.extend({
created_at: DS.attr('date'),
user: DS.belongsTo(App.User)
});
App.Post = App.Message.extend({
title: DS.attr('string')
});
/* polymorphic belongsTo */
App.Comment = App.Message.extend({
body: DS.attr('string'),
message: DS.belongsTo(App.Message, {polymorphic: true})
});
You also need to configure alias properties on your RESTAdapter
DS.RESTAdapter.configure('App.Post' {
alias: 'post'
});
DS.RESTAdapter.configure('App.Comment' {
alias: 'comment'
});
The result expected from your server should be like this:
{
user: {
id: 3,
// For a polymorphic hasMany
messages: [
{id: 1, type: "post"},
{id: 1, type: "comment"}
]
},
comment: {
id: 1,
// For a polymorphic belongsTo
message_id: 1,
message_type: "post"
}
}
More information in this github thread
So I have something. It's not finished, or entirely clean, but it works. Basically, I use a mixin to bypass the Ember associations entirely. I'm sure that this could be rolled into the adapter or the store, but for now this works.
Polymorphic models come through the the JSON with an itemId and itemType:
App.Follow = DS.Model.extend
user: DS.belongsTo('App.User')
itemId: DS.attr("number")
itemType: DS.attr("string")
I add a mixin to the models that are associated with it :
App.Hashtag = DS.Model.extend App.Polymorphicable,
follows:(->
name: DS.attr("string")
#polymorphicFilter(App.Follow, "Hashtag")
).property('changeCount') #changeCount gives us something to bind to
followers: (->
#get('follows').map((item)->item.get('user'))
).property('follows')
The mixin implements three methods, one that updates the changeCount, one that returns the model's type and the polymorphicFilter method that filters a model by itemType and id:
App.Polymorphicable = Ember.Mixin.create
changeCount: 1
polymorphicFilter: (model, itemType)->
App.store.filter model,
(data) =>
if data.get('itemId')
#get('id') is data.get('itemId').toString() and data.get('itemType') is itemType
itemType:()->
#constructor.toString().split('.')[1]
updatePolymorphicRelationships:()->
#incrementProperty('changeCount')
The controller layer is protected from all this jankyness, except for having to call updatePolymorphicRelationship to make sure the bindings fire:
App.HashtagController = Ember.ObjectController.extend
follow:()->
App.Follow.createRecord({
user: #get('currentUserController.content')
itemId: #get('id')
itemType: #get('content').itemType()
})
#this provides a way to bind and update. Could be refactored into a didSave()
#callback on the polymorphic model.
#get('content').updatePolymorphicRelationships()
App.store.commit()
That's what I have so far. I'm trying to keep things in the model layer as it's just one step removed from the adapter layer. If it looks like Ember Data is not going to look at polymorphics at all in future, then it would make sense to pull this all up to a higher level, but for now, this works and leaves my controllers (relatively) clean.
Polymorphic associations are now supported in ember data
https://github.com/emberjs/data/commit/e4f7c3707217c6ccc0453deee9ecb34bd65c28b9