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/
Related
So I want to build the following select dropdown in ember, using ember data and the API is going to be using the JSON API spec. Here is a screenshot example
So would I in the services model, state the following
app/model/service.js
category: DS.belongsTo('category'),
subCategory: DS.belongsTo('sub-category')
app/model/category.js
service: DS.hasMany('service'),
subCategory: DS.belongsTo('category')
app/model/category.js
service: DS.hasMany('service'),
category: DS.belongsTo('sub-category')
I feel like I am missing something? Thoughts
I think want you're missing is this:
export DS.Model.extend({
parent: DS.belongsTo('category', { inverse: 'children'}),
children: DS.hasMany('category', { inverse: 'parent' }),
});
this will allow you to have a parent/child relationship with one model. If you want a different model for the sub-category I don't really understand whats your problem is.
My application backend has several resources. A model is exposed for each resource.
The entry point to all other resources is through the User model. What I mean is, given a User we can find BlogPost. Given a BlogPost we can find Comments etc.
In Ember terminology, we could say:
User hasMany BlogPost
BlogPost hasMany Comment
Comment belongsTo BlogPost
By backend exposes REST APIs of the form:
GET /api/v1/users/1
GET /api/v1/users/1/blog_posts/1
GET /api/v1/users/1/blog_posts/1/comments/1
I'm trying to figure out how to use Ember Data to fetch Comment belonging to a certain BlogPost belonging to a certain User.
From what I see, if I define a typical Ember model for Comment:
App.Comment = DS.Model.extend({
...
blogPost: DS.belongsTo('App.BlogPost')
});
and in the CommentRoute I have the following:
var CommentRoute = MessageRoute.extend({
model: function(params) {
this.store.find('comment')
},
The request is sent to:
/api/v1/comments
I don't even know where to begin in order for Ember Data to use urls of the form:
GET /api/v1/users/1/blog_posts/1/comments/1
I've seen several similar questions asked (see links below), but haven't seen a definitive answer to any of them. Most of them are almost a year old when ember-data, perhaps, did not have such functionality (or so it is claimed in some of these threads).
I ask again to confirm whether ember-data has such functionality or not.
Similar Questions:
Ember Data nested Models
Canonical way to load nested resources
Deep nested routes
The best way to handle it is with links. If you don't want to do it like that, it is far from supported, and difficult to hack in (the pipeline just doesn't easily pass the information around). Personally I'd recommend rolling your own adapter in that case (Ember without Ember Data).
App.Foo = DS.Model.extend({
name: DS.attr('string'),
bars : DS.hasMany('bar', {async:true})
});
App.Bar = DS.Model.extend({
foo: DS.belongsTo('foo'),
});
json:
{
id: 1,
name: "bill",
links: {
bars: '/foo/1/bars'
}
}
Example: http://emberjs.jsbin.com/OxIDiVU/971/edit
I have these models:
Gmcontrolpanel.Offer = DS.Model.extend({
name: DS.attr('string'),
date: DS.attr('string'),
duration: DS.attr('number'),
products: DS.hasMany('product', {async: true}),
});
Gmcontrolpanel.Product = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
offer: DS.belongsTo('offer'),
variations: DS.hasMany('variation', {async: true})
});
Gmcontrolpanel.Variation = DS.Model.extend({
description: DS.attr('string'),
quantity: DS.attr('number'),
price: DS.attr('string'),
product: DS.belongsTo('product')
});
I'm trying to build a reusable interface for creating/editing an offer; i've made two separate views for inserting a product and inserting a variation;
the product view has a + and a - button to add or remove products, and the same for variation view;
the model for the route is:
model: function() {
return this.get('store').createRecord('offer');
}
What I want is that, when clicking on save button, all (offer, products and variations) are saved;
First of all: which one is the best way of implementing this? containerViews? collectionViews or {{#each}} loops?
And then, how can I create the child records and bind them to the input fields on the child views? I mean: I can create a new product record every time a productView is inserted and the same for variations, but when saving how can I get all these records and set properly all the relationships fields?
Here is a skeleton example of how to set up the relationship:
var newOffer= store.createRecord('offer', {name:....});
//or you can do newOffer.set('name',...);
var newVariation = store.createRecord('variation', {description:.....});
var newProduct = store.createRecord('product', {name:..., description:.....});
newProduct.get('variations').pushObject(newVariation);
newOffer.get('products').pushObject(newProduct);
But for saving the model and persisting it in db, there is one slight problem. Saves are per model, so even when you have the relationship set up properly when we do save on offer model, it doesnot embed the data associated with hasMany relationed models. So we could do something like this :
Note: I have read about bulk save but haven't tried it yet - you might want to give it a shot but if that didnt work then i would do save on each model from bottom up like
newVariation.save().then(function(){
newProduct.get('variations').pushObject(newVariation);
//since the variation model is already saved, it has id associated with the model
//so ember data now knows that it should set variations as variations:[id of the variation model we just saved] when sending post request for product
newProduct.save().then{ //same pattern as above }
}, function(){//handle failure}
Here the case was simple, we had just one variation and one product but you may have multiple of them. We can do rsvp.all to sync up the promises for saves but it is bit sluggish becuse you have to make separate api calls for each save and since you may have multiple variations and products, the no of ajax calls can be bit insane. One way of getting around this would be that you create your own json structure by looping through the models, and combine the models into single json and make a single post request with jQuery ajax api call, save the content in your db and then make use of pushPayload(
http://emberjs.com/api/data/classes/DS.Store.html#method_pushPayload) to load up all the data back to the store.
This is what i have done in similar situation but there might be more elgant solutions out there, so i would wait on more opinions on this.
As for the view thing, i would think you would need a view for product only, this is what im thinking:
//offer.hbs
Bunch of form elemnts to update name description
+ - buttons to add product
{{#each product}}
{{#view productView product=this}}//this is just a normal view
{{/each}}
// your template associated with productView will be like
+ - buttons to add variations to product
{{#each product.variations}}
Show form elments to update name and description
{{/each}}
How about if we give this a try in setupController
setupController:function(controller, model){
model.reload().then(function(data){controller.set('model', data);});
}
Or, what if you create an action to transition to edit mode and in that action you reload the model first and within its then hook, you do the transitionToRoute with reloaded model data. Something like:
goToEdit: function(model){
model.reload().then(function(data){transitionToRoute('offer.edit', data});
}
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'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
}];