I can't get embedded hasMany to work correctly with ember data.
I have something like this
App.Post = DS.Model.extend({
comments: DS.hasMany('App.Comment')
});
App.Comment = DS.Model.extend({
post: DS.hasMany('App.Post'),
name: attr('string')
});
And my API returns the following for GET /post:
[
{
id: 1
comments: [{name: 'test'}, {name: 'test2'}]
},
...
]
I need to send this with POST /post:
[
{
comments: [{name: 'test'}, {name: 'test2'}]
},
...
]
I want to work with Ember models and have them make the appropriate requests:
var post = App.store.createRecord(App.Post, hash_post_without_comments);
post.get('comments').createRecord(hash_comment);
App.store.commit(); // This should call the POST api
and
var posts = App.store.find(App.Post); // This should call the GET api
When I try something like post: DS.hasMany('App.Post', {embedded: true}), the GET is working but the POST is trying to make a POST for the two records not only the parent one.
EDIT : My Real use case
1- I've just built ember data from master
2- My adapter: RESTAdapter
3- The serializer: JSONSerializer
4- I added
App.MyAdapter.map('App.Join', {
columns: { embedded: 'always' }
});
5- My Models are:
App.Join = DS.Model.extend({
rowCount: DS.attr('number'),
columns: DS.hasMany('App.JoinColumn'),
});
App.JoinColumn = DS.Model.extend({
join: DS.belongsTo('App.Join')
});
6- When:
var a = App.Join.find(1);
a.get('columns').createRecord({});
App.store.commit();
a POST for joincolumn is sent and the parent is not dirty
What am i missing?
On master, the correct API is:
App.Adapter.map('App.Post', {
comments: { embedded: 'always' }
});
The two possible values of embedded are:
load: The child records are embedded when loading, but should be saved as standalone records. In order for this to work, the child records must have an ID.
always: The child records are embedded when loading, and are saved embedded in the same record. This, of course, affects the dirtiness of the records (if the child record changes, the adapter will mark the parent record as dirty).
If you don't have a custom adapter, you can call map directly on DS.RESTAdapter:
DS.RESTAdapter.map('App.Post', {
comments: { embedded: 'always' }
});
I have the exact same problem.
This bug has been reported on the ember data issue tracker.
The following PR adds 2 failing tests showing the problem: https://github.com/emberjs/data/pull/578
It seems that there is no workaround right now.
EDIT:
sebastianseilund opened a PR 2 days ago which fixes your problem.
Have a look at: https://github.com/emberjs/data/pull/629/files
Adding an update to this incase others come across this post and are having a hard time figuring out what works with the current version of ember-data.
As of Ember Data 1.0.0.beta.7, you need to override the appropriate methods on the serializer. Here's an example:
1) Reopen the serializer (credit to this post):
DS.RESTSerializer.reopen({
serializeHasMany: function(record, json, relationship) {
var hasManyRecords, key;
key = relationship.key;
hasManyRecords = Ember.get(record, key);
if (hasManyRecords && relationship.options.embedded === "always") {
json[key] = [];
hasManyRecords.forEach(function(item, index) {
// use includeId: true if you want the id of each model on the hasMany relationship
json[key].push(item.serialize({ includeId: true }));
});
} else {
this._super(record, json, relationship);
}
}
});
2) Add the embedded: 'always' option to the relationship on the model:
App.Post = DS.Model.extend({
comments: DS.hasMany('comment', {
embedded: 'always'
})
});
This is what worked for me (Ember 1.5.1+pre.5349ffcb, Ember Data 1.0.0-beta.7.f87cba88):
App.Post = DS.Model.extend({
comments: DS.hasMany('comment', { embedded: 'always' })
});
App.PostSerializer = DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
comments: { embedded: 'always' }
}
});
Related
I have model campaign, which has nested models stories and domains.
App.Campaign = DS.Model.extend({
name: DS.attr('string'),
stories: DS.hasMany('story'),
domains: DS.hasMany('domain')
});
App.Domain = DS.Model.extend({
domain: DS.attr('string'),
campaigns: DS.hasMany('campaign')
});
App.Story = DS.Model.extend({
title: DS.attr('string'),
campaign: DS.belongsTo('campaign')
});
In controller I create currentCampaign in this way:
this.currentCampaign = this.store.createRecord('campaign');
And add stories and domains in this way:
var campaignDomains = this.get('currentCampaign.domains');
var domainObj = this.store.createRecord('domain', {
"domain": domain
});
campaignDomains.addObject(domainObj);
var campaignStories = this.get('currentCampaign.stories');
var story = this.store.createRecord('story', {
"id": this.generateUUID(), // Generate uniqid
"title": storyTitle,
});
campaignStories.pushObject(story);
After saving (before calling transitionTo method I see duplicating domains and stories on template). When I go to page editing campaign I also see duplicating domains and stories. AFter refresh page - I see normal data (by refresh this data are fetching from server).
POST request JSON (domain id are generating on server side, story id are generating on client side):
{"campaign":
{
"id":"f0777f1a17deadcb",
"name":"name of campaign",
"stories":[{
"id":"488b6b6bf4c988f0",
"title":"story",
"campaign":"f0777f1a17deadcb"
}],
"domains":[{
"domain":"domain.com",
"campaigns":["f0777f1a17deadcb"]}
}]
}
Response on POST request (by adding new campaign):
{"campaign":
{
"id":"f0777f1a17deadcb",
"name":"name of campaign",
"stories":[{
"id":"488b6b6bf4c988f0",
"title":"story",
"campaign":"f0777f1a17deadcb"
}],
"domains":[{
"id":"54",
"domain":"domain.com",
"campaigns":["f0777f1a17deadcb"]}
}]
}
Serializer file
App.CampaignSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
domains: {embedded: 'always'},
stories: {embedded: 'always'}
}
});
Ember 1.8.1
Ember-data 1.0.0-beta.14.1
I think you have run into a issue I reported.
I had to downgrade to ember-data 1.0.0-beta.12
About stories (id of story are generating on client side) I used this.store.push
instead of this.store.createRecord - and this works fine.
About domains as I understood - this is bug ember.js. On github I found several discussions on this bug.
Now I use one hack to avoid duplicating data, I hope in future this bug will be fixed and I remove my hack.
var self = this;
this.get('currentCampaign').save().then(function() {
self.get('currentCampaign.domains').toArray().forEach(function(domain) {
if (domain.get('isNew')) {
domain.deleteRecord();
}
});
self.transitionToRoute('campaigns');
})
I'm having a problem with my sibling hasMany relationships disappearing. Working with Ember data canary.
I have the following data model:
import DS from 'ember-data';
export default DS.Model.extend({
// More here, discarded for brevity...
app: DS.hasMany('app', { async: true }),
paymentMethod: DS.hasMany('paymentMethod', { async: true })
});
When user is updated after deleting a paymentMethod in the following way:
var paymentMethod = this.get('content'),
currentUser = this.session.get('currentUser.content');
currentUser.get('paymentMethod').then(function ( paymentMethods ) {
paymentMethods.removeObject(paymentMethod.get('id'));
paymentMethod.destroyRecord();
currentUser.save();
}, handleError);
or saving in the following way:
var paymentMethod = self.store.createRecord('payment-method', paymentMethodData);
paymentMethod.save().then(function ( PaymentMethod ) {
currentUser.get('paymentMethod').addObject(PaymentMethod);
currentUser.save().then(function ( /* record */ ) {...
The apps array is set to an empty []. It happens the opposite way as well, deleteing or adding an app with a paymentMethod will unset the paymentMethod array.
I have the following serializer in place, but it appears as the relationship is set as an empty array before the record gets to the serializer:
var json = {
_id: user.get('id'),
name: {
first: user.get('firstName'),
last: user.get('lastName'),
company: user.get('companyName')
},
login: {
email: user.get('email'),
password: user.get('password')
},
app: user.get('app').mapProperty('id'),
paymentMethod: user.get('paymentMethod').mapProperty('id'),
time_stamp: user.get('time_stamp')
};
return json;
Sorry for the overload. Hope you can help.
You are naming your hasMany associations in singular, which isn't really following the convention. That being said, you have no 'apps' array. I don't think that should cause you any problems, I am just pointing out because you maybe searching for the wrong thing.
I suppose your backend somehow restricts you to this payload?
//Setup:
Ember: 1.3.2
Handlebars: 1.3.0
jQuery: 2.0.0
-----------------
MongoDB (_id's, embedded data)
I have been attempting to get a self many to many relationship like this:
//Model:
App.Post = DS.Model.extend({
title: DS.attr('string'),
content: DS.attr('string'),
links: DS.hasMany('App.Post'),
});
Links should be embedded as id's for (hopefully) obvious reasons.
After a couple of days digging around I have managed to get the app to serialise and submit the data correctly via RESTAdapter, the code I am using looks like this:
//Controller:
App.PostController = Ember.ObjectController.extend({
actions: {
addRelated: function(related) {
var links = this.content.get('links').pushObject(related);
this.content.save();
}
}
});
//Store:
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.extend({
url: '/admin/api',
serializer: DS.RESTSerializer.extend({
primaryKey: function(type) {
return '_id';
},
addHasMany: function(hash, record, key, relationship) {
if (/_ids$/.test(key)) {
hash[key] = [];
record.get(this.pluralize(key.replace(/_ids$/, ''))).forEach(function(post) {
hash[key].push(post.get('id'));
});
}
return hash;
}
})
});
});
From what I can gather the serializer is expecting data in the form
{post: {...}, links: [{...},{...}]}
But since the link is of type post, I would rather not create an entire App.Links model if possible.
So can I map links to posts? As in
{post: {...}, posts: [{...},{...}]}
I tried adding a deserializeHasMany but it didn't get called when using App.Post.find()
I am guessing I would need to write a custom extract function that takes link_ids and extracts the posts into the record from it?
pI haven't test this but would say:
You should change your model to look like this:
App.Post = DS.Model.extend({
title: DS.attr('string'),
content: DS.attr('string'),
links: DS.hasMany('post'), //changed
});
Your JSON should be in the format:
{"posts": [{ "id":3 ... post item .... "links":[3,10]} { "id":4... post item .... "links":[4,11]}]}
All links must be included in the JSON unless already loaded.
My understanding is that you should not have to override the RESTAdapter and RESTSerializer as this should work out of the box - if it doesn't I'd first check ajax and capitalization.
I'm developing my first EmberJS app after following some tutorials as practice. It simply contains a list of 'tables', 'columns', and 'rows' similar to a database.
Link to the problematic page: http://www.kangarooelectronics.com/fakeDB/#/tables/edit/2
My issue is that when I go to remove a column I get:
Object # has no method 'deleteRecord'
As I understand this is due to the object I'm iterating through having no references to the controller because of the way I am constructing the array that I use to create my list.
Removing tables works fine, which are listed in the following fashion:
{{#each model itemController='TableList'}}
<a {{action removeTable this}}>Delete</a>
{{/each}}
I'm iterating through the columns via:
{{#each column in currentColumns itemController='TablesEdit'}}
<a {{action removeColumn column}}>Drop</a>
{{/each}}
Snippet from FIXTURES object:
FakeDB.Table.FIXTURES = [
{
id: 1,
name: 'Users',
columns: {
1:{'colId':1, 'name':'name'},
2:{'colId':2, 'name':'favorite color'},
3:{'colId':3, 'name':'phone number'}
},
// ...snip... //
I am getting 'currentColumns' via:
FakeDB.Table = DS.Model.extend({
name: DS.attr('string'),
columns: DS.attr('object'),
rows: DS.attr('object'),
currentColumns: function() {
var newColumns = $.map(this.get('columns'), function(k, v) {
return [k];
});
return newColumns;
}.property('columns'),
// ..snip.. //
Here you can see my problem... it's obvious that my 'column' isn't going to have any methods from my controller. I tried something like this:
FakeDB.Adapter = DS.FixtureAdapter.extend();
FakeDB.Adapter.map('FakeDB.Table', {
columns: {embedded: 'load'},
rows: {embedded: 'load'}
});
FakeDB.Columns = DS.Model.extend({
colId: DS.attr('integer'),
name: DS.attr('string')
});
FakeDB.Rows = DS.Model.extend({
colId: DS.attr('integer'),
name: DS.attr('string')
});
But I couldn't get {{#each column in columns}} to work with that.
Any suggestions? I'm going to read the docs again and will post back if I find a solution.
Thanks!
EDIT:
So I think I found another solution, but I'm still running into a little issue.
FakeDB.Table = DS.Model.extend({
name: DS.attr('string'),
columns: FakeDB.Columns.find().filter(function(item, index, self) {
if(item.tableID == 1) { return true; }
})
});
Still not sure what to replace 'item.tableID == 1' with so that I get items with the tableID referencing to the current page...
Columns are structured as...
FakeDB.Columns.FIXTURES = [
{
id: 1,
tableID: 1,
name: 'name'
},
// ...snip... //
But now I get:
assertion failed: Your application does not have a 'Store' property defined. Attempts to call 'find' on model classes will fail. Please provide one as with 'YourAppName.Store = DS.Store.extend()'
I am in fact defining a 'Store' property...
I'm developing my first EmberJS app after following some tutorials as practice. It simply contains a list of 'tables', 'columns', and 'rows' similar to a database.
Most databases do contain a list of tables, rows and columns. Most web applications contain a fixed set of tables with pre-defined columns and a dynamic list of rows. If this is your first ember app i would recommend starting with something that keeps you on the happy path.
I am in fact defining a 'Store' property...
True but ember is complaining because store is not available before ember app is initialized. Anything that accesses the store should be in a framework hook of some kind. It can't be used when defining your objects, which wouldn't make a lot of sense anyway.
Probably what you meant to do was make a computed property called columns like this:
FakeDB.Table = DS.Model.extend({
name: DS.attr('string'),
columns: function() {
FakeDB.Columns.find().filter(function(item, index, self) {
if(item.tableID == 1) { return true; }
})
}.property('')
});
This is driving me nuts. I have a simple data model set up (using Padrino); I'm long past the stage of actually getting any error messages but adding 'App.Repo' models to an 'App.Stack' model just…doesn't work.
App.Store = DS.Store.extend({
revision: 10
adapter: DS.RESTAdapter.create({
bulkCommits: false,
mappings: {
stars: App.Stars,
stacks: App.Stacks
}
})
});
App.Stack = DS.Model.extend({
url: DS.attr('string'),
repos: DS.hasMany('App.Repo')
});
App.Repo = DS.Model.extend({
name: DS.attr('string'),
url: DS.attr('string'),
description: DS.attr('string'),
language: DS.attr('string'),
watchers: DS.attr('number'),
stack: DS.belongsTo('App.Stack'),
stackId: DS.attr('number')
});
var store = App.get('router.store');
newStack = store.createRecord(App.Stack);
console.log(newStack.serialize())
-> Object {url: null} // no mention of a repos array like I was expecting?
newStack.set('url', 'http://google.com');
console.log(newStack.serialize());
-> Object {url: "http://google.com"} // this works though
var repo = App.Repo.find().objectAt(0);
console.log(repo.serialize());
-> Object {name: "floere/james", url: "https://github.com/floere/james", description: "Voice commanded servant for OSX", language: "Ruby", watchers: 97…}
// so this exists too…
repos = newStack.get('repos');
repos.pushObject(repo);
newStack.get('repos.length'); // 1 (repos.toArray() etc etc all work too)
// but then…
console.log(newStack.serialize())
-> Object {url: null}
// and so then I try to save the relationship on the server anyway…
store.commit()
=> {"stack"=>{"url"=>nil}} // in my Ruby server logos
The store is all set up fine talking to my back end (for example submitting a POST to /repo.json sends the correct request); it just doesn't recognise that App.Stack has any relation.
No idea what's going wrong or what to look at for help :(
Also
I tried making the relations in my Ruby console and then accessing them in a view. This is what happens
// in the router
router.get('applicationController').connectOutlet('body', 'stacks', router.get('store').findAll(App.Stack));
// in the view
<script type="text/x-handlebars" data-template-name="stacks">
{{#each stack in controller }}
{{stack.id}} // this works
{{stack.url}} // this works
{{stack.repos.length}} // this returns the correct count
{{#each repo in stack.repos}}
// this loops the right number of times. so there *is* something there. somehow.
{{repo}} // prints out <App.Repo:ember490>
{{repo.id}} // prints out [object Object]
{{/each}}
{{/each}}
On that last note - maybe a clue in the [object Object]?
I'm so lost :(
More Info:
I'm using Padrino with Mongoid, using RABL to give me JSON. As I said, I can query for & template out my Stack & Repo records. Here's a JSON sample for the /stacks.json endpoint
{
"stacks": [
{
"account_id": null,
"id": "50c127ff6f094144ed000001",
"stars": [
{
"description": "Voice commanded servant for OSX",
"id": "50c128996f0941cfe8000001",
"name": "floere/james"
}
]
}
]
}
I think you'll have to add hasMany relationships to your json object manually by looping through the repos array. I'm doing this in my adapter's createRecord method.
createRecord: (store, type, record) ->
data = {}
data[root] = #toData(record, { includeId: true })
repos = []
stack.get("repos").forEach (repo) ->
repos.pushObject repo.serialize()
data[root]["repos"] = repos
...
I've found a way to get embedded related objects in the JSON to load properly. Basically you have to subclass the serializer and then in its initializer you tell it to register a map for the relationship. Here's an example for a model class called Category that has a to-many relationship 'resourceTypes':
App.WOSerializer = DS.Serializer.extend({
init: function(){
this._super();
this.map(App.Category, {
resourceTypes: { embedded: 'load' }
});
}
});
My solution is further explained here.