I have two ember models with a relationship like this
App.Foo = DS.Model.extend
bar: DS.belongsTo("App.Bar", embedded: true)
App.Bar = DS.Model.extend
primaryKey: "blah"
blah: DS.attr "string
If I create and save a new record like this:
foo = App.store.createRecord App.Foo
foo.set "bar", App.Bar.createRecord(blah: "blahblah")
App.store.commit()
I see 2 post requests to the server:
URL: /foos
Payload: {"foo":{"bar":null}}
and
URL: /bars
Payload: {"bar":{"blah":"blahblah"}}
The association is embedded so I would like to see:
URL: /foos
Payload: {"foo":{"bar":{"blah":"blahblah"}}}
Can I achieve this with the ember-data REST adapter or do I need to write my own code to do this?
I am observing the same behavior in my application.
Setting 'embedded' to true only helps you get data as embedded, but while you post it separate requests will be generated.
You have write your in code if you want to achieve it in one request.
This is not a direct answer to your question, but for what it's worth I've found that "fighting" Ember's design by trying to save multiple models at once led me down a terrible path.
It is much easier and more reliable to add the required attributes for a new Bar to Foo and then create and return the new objects on the server, like so:
App.Foo = DS.Model.extend({
blah: DS.attr('string'),
barName: DS.attr('string'),
bar: DS.belongsTo('bar')
})
this.store.createRecord('foo', { blah: "stuff", barName: "foo" }).save()
# => POST /foos
Request:
{ foo: { blah: "stuff", bar_name: "boo" } }
Response:
{ foo: { id: 1, blah: "stuff", bar: { id: 1, name: "boo" } } }
Yes, you end up with an unused attribute on Foo, but you save yourself a lot of code.
Related
Needless to say, new to Ember and just trying to get a proof of concept done. Already had some help with EmberCLI but this a new oddness for me.
/routes/index.js
export default Ember.Route.extend({
model() {
return this.store.findAll('skill');
}});
/models/skill.js
import Model from 'ember-data/model';
export default Model.extend({
name: DS.attr('string'),
desc: DS.attr('string'),
type: DS.attr('string')
});
/adapters/application.js
import DS from "ember-data";
export default DS.JSONAPIAdapter.extend({
namespace: 'v1',
host: 'http://edu-api.app:8000',
});
/serializers/application.js
import DS from "ember-data";
export default DS.JSONAPISerializer.extend({});
/templates/index.hbs
<h2>Skills</h2>
<ul>
{{#each model as |item|}}
<li>
<div>
<li>{{item}} {{item.id}} {{item.type}} {{item.name}} {{item.desc}}</li>
</div>
</li>
{{/each}}
</ul>
It seems that the id attr is available and correct, but yet all the other attrs are not being loaded from the json. If I copy/paste the json and manually set it in the model, it works as expected, so is there some filtering going on when coming from the model store or serializer?
The JSONAPISerializer and JSONAPIAdapter are not to for simple JSON/REST Backends but for a fully JSONAPI compatible Backend.
You say it works when you copy & paste and set it on the model, so probably you mean something like this:
this.store.createRecord('skill', {
id: '1',
name: 'foo',
desc: 'bar',
type: 'baz'
});
This will indeed work for a model creation but is not a JSONAPI compatible response! In JSONAPI you would have something like this (if the request should return multiple entities:
{
data: [{
id: '1',
attributes: {
name: 'foo',
desc: 'bar',
type: 'baz'
}
}]
}
So now you have two options:
Make your API JSONAPI compatible, or
use a different adapter & serializer.
The RESTSerializer/RESTAdapter are a simple default implementation that can handle a structure like this:
{
id: '1',
name: 'foo',
desc: 'bar',
type: 'baz'
}
Also they are highly customizable.
Checkout the official API for documentation.
//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 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' }
}
});
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.
I've been trying my hand at Backbone recently and have a very basic question.
I need to search for different kind of records and the search API returns a JSON response like
{ foo:
[
{ name: 'foo1', url: '/foo1' },
{ name: 'foo2', url: '/foo2' }
],
bar:
[ { name: 'bar1', url: '/bar1' } ],
baz:
[ { name: 'baz1', url: '/baz1' } ]
}
I have a backbone model for Foo, Bar and Baz. A collection which on fetch should hit my server and get me the search results. I attempted something like
window.searchEntities = Backbone.Collection.extend({
url: '/get_search_results'
model: function(attrs, options) {
//Typecast the JSON to Foo, Bar and Baz Models
});
});
However, I do not know how to parse the results returned by server so that my collection holds models Foo, Bar and Baz? Or should I tweak the results returned by server so that its easier to handle this with Backbone?
As I'm seeing your JSON is not returning 3 different Models but 3 different Collection due the 3 of them are containing Arrays.
I think you should start from the beginning, If I understood weel you want to return a bunch of Models of different type, let's say:
[
{
type: "Foo",
name: "foo1",
url: "/foo1"
},
{
type: "Foo",
name: "foo2",
url: "/foo2"
},
{
type: "Bar",
name: "bar1",
url: "/bar1"
},
{
type: "Baz",
name: "baz1",
url: "/baz1"
},
]
I see a Collection there, and also different Models of different types.
Now let's see the SearchCollection, I don't think you can play with the model property like you show in your example, so let's say all the Models has a common Model parent Result:
window.SearchEntities = Backbone.Collection.extend({
url: '/get_search_results'
model: Result
});
From here we can do it simple and don't create sub-classes of Result if there is not a real need for it:
window.Result = Backbone.Model.extend({
initialize: function(){
this.url = this.get( "url" );
}
});
And you're done:
var searchEntities = new window.SearchEntities();
searchEntities.fetch();
// ... wait for the fetch ends
searchEntities.at(0).get( "type" ); //=> "Foo"
Still I don't feel confortable by two reasons:
I don't see clear why you want to play with the Result.url.
Where are the ids of your Models? which are very important for Backbone.