Link to a nested emberData object in emberJS - ember.js

I'm using emberData and I have the following model
App.Product = DS.Model.extend({
page_title: DS.attr('string'),
image: DS.attr('string'),
shop: DS.belongsTo('App.Shop', {embedded: true}),
style: (function() {
return "background-image:url('" + this.get("image") + "')";
})
});
The JSON data looks like this:
{
id: 1,
image: 'imageUrl',
shop: {
id: 2,
name: 'shopName'
}
}
In my template I want to link to the a page to display the shop
<img {{bindAttr src="image"}}>
{{#linkTo "shop" shop}}Store{{/linkTo}}
Unfortunately it links to http://localhost:3000/#/shop/undefined

It doesn't make sense to embed the model that it belongsTo. The breaking changes document states that you embed objects in a parent object:
From BREAKING_CHANGES.md
Embedded in Parent
An adapter can save one-to-many relationships by embedding IDs (or
records) in the parent object. In this case, the relationship is not
considered acknowledged until both the old parent and new parent have
acknowledged the change.
In this case, the adapter should keep track of the old parent and new
parent, and acknowledge the relationship change once both have
acknowledged. If one of the two sides does not exist (e.g. the new
parent does not exist because of nulling out the belongs-to
relationship), the adapter should acknowledge the relationship once
the other side has acknowledged.
Your fixture must be:
App.Product.FIXTURES = [{
id: 1,
image: "imageUrl",
shop_id: 2
}];

Related

Save belongsTo change in model not part of Payload

I'm trying to save a change to a parent model on a child model in Ember to my server, but for some reason the REST Payload doesn't contain the belongsTo Relationship
My 2 models are defined like this:
parent.js
import DS from 'ember-data';
export default DS.Model.extend({
parentName: DS.attr('string')
});
child.js
import DS from 'ember-data';
export default DS.Model.extend({
childName: DS.attr('string'),
parent: DS.belongsTo('parent')
});
when I change the belongsTo to a different parent for a childModel by editing and saving an existing record. Somehow my payload doesnt include the parent model.
For example.
I have a child like this:
{
id: 1,
parent: 1,
childName: "Child 1"
}
and in code I do this:
childModel.set('parent', parentModel); // this is a different parentModel, one with id: 2
I would expect the payload to look something like this:
HTTP-PUT: http://server/child/1
{
id: 1,
parent: 2,
childName: "Child 1"
}
however, in reality, the payload is this:
HTTP-PUT: http://server/child/1
{
id: 1,
childName: "Child 1"
}
What goes wrong here? Why is the parent relationship missing from the payload?
Some extra information:
Ember v2.0.1
Ember-data v2.0.0
Relationships must be async: true (which is the default)
I'm using a standard DS.JSONAPISerializer in combination with the DS.RestAdapter
Turns out during the serialization, I only extracted the attributes hash from my JSON Api - JSON, Obviously I need to look for relationships in the relationships hash.

Ember-Data - Recursive relationship

I'm using ember-app-kit with ember-data. I have the following two models created using ember-data. Node has a recursive relationship and Tag has a reference to the Node.
Node = DS.Model.extend({
name: DS.attr('string'),
createdAt: DS.attr('date'),
tags: DS.hasMany('tag', {async: true}),
children: DS.hasMany('node', {async: true, inverse: 'parent'}),
parent: DS.belongsTo('node', {async: true, inverse: 'children'})
});
Tag = DS.Model.extend({
name: DS.attr('string'),
parent: DS.belongsTo('node', {async: true})
});
Whenever I try to reassign a tag to a different parent the changes do not persist.
NodeController = Ember.Controller.extend({
actions: {
update: function(newParentNode) {
var node = model;
node.set('parent', newParentNode);
node.save().then(function(updatedNode){
updatedNode.get('parent');// returns null
}); //doesn't work
}
}
});
I found a similar question but the difference is that Tag also has a relationship to Node. Is it possible to update a parent-child relationship by only updating the child?
Update #1:
When examining the output from ember-data in the console I noticed that nothing about the parent child relationship is communicated on the save. The request payload to the backend is sent as the following:
{ "node": { "name": "node4", "createdAt": null } }
Nothing about the parent/child relationship is transmitted. However if I reassign a tag to a different node the backend receives the following:
{"tag": {"name":"tag123", "parent":"66c8ec8c-3790-45b2-8669-e9581102376d"} }
I tried simplifying my node model by removing the inverses. This allowed me to reassign parents, but ember-data started to send the children relationship on the requests as the means of updating the relationship.
{ "node": {
"name": "node4",
"createdAt": null,
"children": ["66c8ec8c-3790-45b2-8669-e9581102376d"]
}
}
While this gets the job done, it is not ideal. As there could many children and sending the ids of every child will bloat the request payload. Is there no way for ember-data to send the updated info of child with its new parent relationship key? Also this change caused error messages to start being raised within Qunit.
You defined the 'parent' relationship on (subclass of DS.Model), but multiple possible inverse relationships of type (subclass of DS.Model) were found on (subclass of DS.Model)

Create parent and child models in the same commit

I have the following models:
App.Offers = DS.Model.extend({
name: DS.attr('string'),
createdBy: DS.belongsTo('App.Users'),
products: DS.hasMany('App.Products'),
startDate: DS.attr('string'),
endDate: DS.attr('string')
}
App.Products = DS.Model.extend({
description: DS.attr('string'),
img: DS.attr('string'),
offer: DS.belongsTo('App.Offers'),
}
Using these two models I create both an offer (parent) and a product (child):
var offer = App.Offers.createRecord({/*fields*/}),
product = App.Products.createRecord({/*fields*/});
offer.get('products').pushObject(product);
offer.get("store").commit();
When I do this the problem is that the parent needs the id of the child and the child the id of the parent in order to set its FK.
I looked up issues and PRs in the ember-data repo and I found this: https://github.com/emberjs/data/pull/440 . It suggests wrapping createRecord in a waitForParents function which basically creates the parent and then the child. While I have tried their suggestion I still can't create my records. The problem is that even though the request for the parent is made first, it still needs the id of the child (which is not created yet). The parent request is send with the following payload:
{/*other_fields*/,"products":["/api/v1/products//"]}
Note the missing id of the product in the url.
I managed to fix it by following the suggestion in this PR: https://github.com/emberjs/data/issues/437. They basically suggested creating a waitForParents function and wrap it around createRecord. This function checks if there's a belongsTo relationship and defers the code execution for that creation until the parent is created (by adding an observer to the id field).
In my case I had to override addHasMany in the serializer as well.
I hope that helps other people with the same problem.

Ember.js: How to persist Many to Many relationships

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/

Ember-data polymorphic associations

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