why ember-data embedded association always send an ajax request? - ember.js

I'm trying to use Ember-Data and the provided RESTAdapter to load an object like this :
{
"videos":[
{
"id":"5062f3c30959c6c732000005",
"tags":[
{"_id":"5062f3cb0959c6c732000006","name":"hello"},
{"_id":"5062f3cb0959c6c732000007","name":" world"}
]
}
]
}
Here I have a video object that has many tags. The tags attribute is declared as embedded :
Video = DS.Model.extend {
tags: DS.hasMany('Tag', {embedded: true})
}
Tag = DS.Model.extend {
video: DS.belongsTo('Video')
}
When I try to load video with
Video.find()
The adpter always try to send a get request to my server at /tags which naturally fails because my server doesn't give acces to tags directly. Instead, tags are already embedded in the /videos.json.
So what is the meaning of embedded: true in ember-data association ?

I believe vaguely what's happening is that it doesn't trust the embedded tags without id parameters. Try giving Tag an id: DS.attr('string', { key: '_id' }).
By the way, embedded is misspelled in your example -- is it correct in your app?
UPDATE: Perhaps better advice would be to set primaryKey: '_id' inside Tag. (doc)

Related

How to dasherize filter URL sent by Ember query?

I am trying to learn Ember by developing a simple TODO manager application.
I am using ember-data and JSONAPISerializer with a hand rolled REST JSON API backend for this application.
I have the following model which represents a task
app/model/task.js
export default DS.Model.extend({
title: DS.attr ('string'),
description: DS.attr ('string'),
isComplete: DS.attr ('boolean')
});
The corresponding JSON data from backend looks like this
{
"data": [{
"id": "1",
"type": "task",
"attributes": {
"title": "Complete Ember TODO manager application",
"description": "Build a simple Ember application for easily managing tasks",
"is-complete": "false"
}
}]
}
As per the convention, the Ember model uses camel cased names and the JSON API backend uses dasherized names.
A basic feature in this application is to filter tasks by their status, so basically one can see either ongoing or completed or all tasks.
For fetching only the ongoing tasks, I have the following query in the model hook of the corresponding route
app/routes/tasks/ongoing.js
export default Ember.Route.extend({
model () {
return this.get('store').query('task', {
filter: {
isComplete: 'false'
}
});
}
});
So when the query is sent to the backend it goes as
restjsonapi/tasks?filter[isComplete]=false
The problem is that the backend expects "is-completed" and does not understand "isComplete".
Is there a way to dasherize the URL emitted by Ember query?
Edit (possible workaround):
I might have been trying to solve this the wrong way I think. I have changed the JSON data to have underscores instead of dashes and worked around this problem.
When using store.query, Ember then finds the relevant adapter for your model type and calls adapter.query which in turn seems to pass your query to jQuery.ajax as a value for the data attribute. See here:
https://github.com/emberjs/data/blob/v2.7.0/addon/adapters/rest.js#L504
The conversion from Object to Query String is then done by jQuery using encodeURIComponent as you can see there: https://github.com/jquery/jquery/blob/e4fd41f8fa4190fbbb6cb98cf7ace64f6e00685d/src/serialize.js#L68
So you won't be able to change this behaviour by passing an option to Ember.
However, you could try something like:
export default Ember.Route.extend({
model () {
return this.get('store').query('task', {
filter: {
"is-complete": 'false'
}
});
}
});
And it should work.

EmberJS embedded items in payload JSONAPI

Ember : 1.13.3
Ember Data : 1.13.5
jQuery : 1.11.3
I am trying to send a JSON payload using ember-data from my EmberJS client to my server. I want to send the entire object graph to the server on saving the project, as I don't want to send multiple requests. I wouldn't mind sending multiple requests, but I am worried about what happens if one of the requests fails in the middle and the data on the server will not be correct.
I wanted to use JSONAPI (http://jsonapi.org/format/#document-compound-documents) as that is becoming the default adapter in Ember. Also, there is a few C# libraries that handle this format, so I thought it would be quite straightforward. However, after reading the spec, it seems that I cannot embed objects if they do not have an id. EmberJS also does not attach the child objects to the JSON either even though I have specified { async: false, embedded: 'always' }) on the DS.attr.
My question is: If an application is used in such a way that an object graph is created on the client side, how do you use JSONAPI format to send the entire object graph to the server? Do I have to generate ids on the client side to satisfy the JSONAPI standard? Then once they get to the server just ignore them so they get saved with an id generated by the ORM?
Here is my labelGroup model:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
labels: DS.hasMany('label-model', { async: false, embedded: 'always' })
});
Here is my project model:
import DS from 'ember-data';
export default DS.Model.extend(DS.EmbeddedRecordsMixin, {
name: DS.attr('string'),
labelGroups: DS.hasMany('labelGroup', { async: false, embedded: 'always'})
});
Here is the POST that I get after doing a save() on the project:
{
"data":{
"attributes":{"name":"Project"},
"relationships":{
"label-groups":{
"data":[
{"type":"label-groups","id":null},
{"type":"label-groups","id":null},
{"type":"label-groups","id":null},
{"type":"label-groups","id":null},
{"type":"label-groups","id":null},
{"type":"label-groups","id":null},
{"type":"label-groups","id":null}
]
}
},
"type":"label-projects"
}
}
UPDATE: I tried using https://www.npmjs.com/package/ember-cli-uuid to generate client side ids which it has. However the data getting output does not include the extra objects, only a reference to their ids. I expected to see an "included" property as specified here:http://jsonapi.org/format/#document-compound-documents, but it is not there.
{
"data":{
"id":"7b4544ee-91cd-493d-8b10-52040e68c283",
"attributes":{"name":"Project"},
"relationships":{
"label-groups":{
"data":[
{"type":"label-groups","id":"08115273-e82a-4d46-93ea-232ce071fb78"},
{"type":"label-groups","id":"9ca94fe9-8077-411e-98d2-1694c6fecce4"},
{"type":"label-groups","id":"d629f1e8-7962-404d-8034-38229ab21f77"},
{"type":"label-groups","id":"c6bda655-5489-4760-847b-bf02239bb2c5"},
{"type":"label-groups","id":"f6fef249-2d1d-43f0-ba64-24b7ff8b5637"},
{"type":"label-groups","id":"a7db25bf-52c8-477b-83e4-64e7c76b072e"},
{"type":"label-groups","id":"f3b5fbb3-261a-4b3d-b481-b9352f8ce2d6"}
]
}
},
"type":"label-projects"
}
}
Ember-data has no support for what you want at the moment. So ember-data will not save your relationships data in a save payload.
But its possible to do this your own by using a custom adapter and serializer.
I strongly recommend you to checkout the API and then look into the source.
If you call .save() on your Model the createRecord method is called on your adapter.
Here serializeIntoHash on the serializer is called to serialize the Model.
serializeIntoHash calls serialize, where serializeBelongsTo and serializeHasMany is called.
Now you can just override serializeHasMany and modify the hasMany before the line:
json[payloadKey] = hasMany;
Here you have the type and the ids as they are sent by ember-data. You could just .forEach the data on the hasMany and then fetch the store for the data and build your included array.
I hope this helps you to understand the serializer and the adapter so you can modify it to do whatever you want pretty easy. Actually this is the best part about ember-data. The structure of the adapter and the serializer, which allows easy modifications.

Accessing store in another route

I have this model:
App.Game = DS.Model.extend({
name: attr(),
uri: attr()
});
and this route:
App.GamesRoute = Ember.Route.extend({
model: function() {
return this.store.find('game');
}
});
This works fine, calls the backend server, and stores elements in the store (I've checked with Ember inspector). This is the json I return:
{"games":[{"id":"TicTacToe","name":"TicTacToe","uri":"http://localhost:10000/games/TicTacToe"}]}
Now I have this template for 'games' (snipped):
{{#each game in model}}
{{#link-to 'games.matchlist' game.id}}{{game.uri}}{{/link-to}}
This shows the URI for each game. Now in the games.matchlist route what I would like to do is to search in the store by the game_id received param and get the game URI. The reason is that the server doesn't follow RESTAdapter conventions, and I would like to make a custom AJAX query to that URI myself.
This doesn't work:
App.GamesMatchlistRoute = Ember.Route.extend({model: function(params) {
var store = this.store;
var game = store.find('game', params.game_id)
console.log(game);
console.log("URI: " + game.uri);
at this point, game is an object but it's not an instance of my model. It doesn't have a uri attribute. What am I doing wrong? I'm feeling that I'm missing something obvious.
If you want to get records without hitting the server and you know you already have it in the store, use this.store.getById('game', ID).
I'm on my mobile, but you need to create a GameAdapter and customize I believe the fetch function. Checkout the docs for adapters on the ember site and you should have your answer.
Your other option is to fetch the data from your server and use this.store.pushPayload(data).
Docs here: http://emberjs.com/api/data/classes/DS.Store.html#method_pushPayload
And the adapter docs here: http://emberjs.com/guides/models/customizing-adapters/

ember/ember-cli - impossible url fetched while routing

I am new to Ember, but I got a successful "app" with it, then i'm trying to "port" it to ember-cli.
I have a quite empty main page, and a link to the about page: main and about routes are defined.
However I got a 404 "/mains" not found when accessing /… why the hell is he adding an extra "s"?
I've uploaded the project:
https://github.com/Leryan/testember/
https://raw.githubusercontent.com/Leryan/testember/master/2015-03-21-202815_1920x1080_scrot.png
You'll see a picture with the problem: when accessing the website root, ember try to fetch "/mains" …
Thanks
Ember is looking for the all the records of the type 'main' by calling this url.
This is because in the router "main.js" you are using this.store.find method, which pluralizes the model type to retrieve all the records for this model ("/mains"):
var MainRoute = Ember.Route.extend({
model: function() {
return this.store.find('main');
}
});
But it looks like you want to use fixtures instead?
Therefore you have to use the FixtureAdapter for the desired route and define the fixtures for the model. To use the FixtureAdapter you must rename your existing adapter "all.js" to "application.js" or "main.js" depending where you want to use it.
And furthermore you have to use reopenClass to assign any fixtures in your model "main.js":
Main.reopenClass({
FIXTURES : [
{ id: 1, name: "blah" },
{ id: 2, name: "blah2" }
]
});
Here is the ember gudie for the fixture adapter:
http://emberjs.com/guides/models/the-fixture-adapter/

Ember Data Relationships against non standard API

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.