Ember data bulk fetch async hasMany relationship records - ember.js

I'd like to know whether it is possible to configure Ember data in a way that async hasMany relationships are loaded in a single request.
Given the following model:
// models/post.js
export default DS.Model.extend({
comments: DS.hasMany()
});
// models/comments.js
export default DS.Model.extend({
...
});
When I reference the property comments somewhere in the template or controller, it will fetch the related comments of the post one-by-one in separate requests. This results in many requests and I'd like to combine those into a single request with a filter on id-property.

There is a switch you have to enable for the desired behaviour called coalesceFindRequests: true that you can set in you application adapter like so:
// adapters/application.js
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
coalesceFindRequests: true,
});
Now Ember will fetch multiple records of known id via ..api/comments?ids[]=1&ids[]=2
I suppose it'll be the same for a JSONAdapter.

Yes, you should use link relationships:
Instead of this:
relationships: {
commend: {
data: [
{ id: '1', type: 'comment' },
{ id: '2', type: 'comment' }
]
}
}
do this:
relationships: {
comments: {
links: {
related: 'http://example.com/post/A/comments'
}
}
}
The nice thing about this is that you can 100% adjust this in your backend.

Related

Can't get relationships with Ember Mirage

I'm currently writing tests for my App written with EmberJS. I'm using Mirage.
I have the two following models:
mirage/models/paperwork.js
export default Model.extend({
customer: belongsTo('customer'),
paperwork_products: hasMany('paperwork-product', { inverse: 'paperwork' }),
mirage/models/paperwork-product.js
export default Model.extend({
paperwork: belongsTo('paperwork', { inverse: 'paperwork_products' }),
});
In my scenario, I'm creating my datas like this:
const paperwork = server.create('paperwork');
const paperworkProduct = server.create('paperwork-product', { paperwork });
paperwork.paperwork_products.add(paperworkProduct);
My route:
export default ApplicationRoute.extend({
model(params) {
return this.store.findRecord('paperwork', params.paperwork_id, { include: 'paperwork_products' }),
},
});
The problem is that I can't access paperwork.paperwork_products in my template. It's undefined (other paperwork attributes are here, but not relationship). I already even tried to put a debugger in my mirage/config.js when routes are declared. My paperwork exists, and his "paperwork_products" too. But I can't get paperwork_products data in my template.
What am I doing wrong ? I think I must change something in my :
this.get('v1/paperworks/:id');
But I don't know what ...
Thanks in advance !
Edit: Here are my real Ember models:
models/paperwork.js
export default DS.Model.extend({
customer: DS.belongsTo('customer'),
paperwork_products: DS.hasMany('paperwork-product', { async: true }),
});
models/paperwork-product.js
export default DS.Model.extend({
paperwork: DS.belongsTo('paperwork'),
});
Yesterday I tried to compare the real JsonApi response from my back, and Mirage response, and I saw that in the relationships hash, my relationship "paperwork_products" was changed to paperwork-products (with Mirage). So there is a problem with relationships with an underscore or models with dash ...
In config.js, I tried to mock JSONAPI Backend, and it works wells. Just replaced "paperwork-products" by "paperwork_products"
Mirage response :
"relationships":{
"customer":{
"data":{
"type":"customers",
"id":"1"
}
},
"paperwork-products":{
"data":[
{
"type":"paperwork-products",
"id":"1"
}
]
}
}
Should be :
"relationships":{
"customer":{
"data":{
"type":"customers",
"id":"1"
}
},
"paperwork_products":{
"data":[
{
"type":"paperwork_products",
"id":"1"
}
]
}
}
My other models with hasMany relationships do not have any problems.
To confirm, do you have Ember Data models setup with the same relationships? Without those, things may. It work very well ...
If you do, could you post those models as well?
Also, as an FYI, Mirage 0.3.0 comes with an auto-sync setup that will read your Ember Data models and create corresponding Mirage models without any work. It's been lovely ...
Edit:
I would suggest you rework your Ember Data model to use camel cased relationships. If you do the following:
models/paperwork.js
export default DS.Model.extend({
customer: DS.belongsTo('customer'),
paperworkProducts: DS.hasMany('paperwork-product', { async: true }),
});
I would expect it to work without issue, as Ember Data automatically translates camelCased relationships to the appropriate JSON-API key
Does that work for you?

ember data not saving foreign key, sent as null

My ember app is not sending my foreign key to the back-end.
I have a table called issues which is has a related table called categories
My model is:
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr('string'),
category_id: DS.belongsTo('category'),
description: DS.attr('string')
});
My route is:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(){
return this.store.findAll('issue');
},
actions: {
create: function(){
var issue = this.store.createRecord('issue');
issue.name = this.get('controller').get('newName');
issue.description = this.get('controller').get('newDescription');
issue.category_id = parseInt(this.get('controller').get('newCategory'));
//debugger;
console.log(issue);
issue.save();
},
...
other actions
...
}
}
});
the console.log from above looks like the category_id is getting set correctly:
category_id: 3
description: "foobar"
name: "test"
However my JSON payload that gets sent to the backend looks like:
{"issue":{"name":"test","description":"foobar","category_id":null}}
I tried stepping through by adding a custom serialiser in app/serializers/application.js
export default DS.RESTSerializer.extend({
...
serialize: function(snapshot,options){
console.debug('options='+options);
debugger;
var json = this._super(snapshot, options);;
return json;
}
...
});
But I got lost in all the super calling super indirection.
The snapshot.record has category_id: 3, but the json coming back from the this._super() call has category_id: null
options has includeID:true
Any clues will be much appreciated ...
Ember : 2.0.2
Ember Data : 2.0.0
Your model definition is wrong, when dealing with relationships you define them just as you would define any other attribute, there is no need to use _id.
export default DS.Model.extend({
name: DS.attr('string'),
category: DS.belongsTo('category'),
description: DS.attr('string')
});
As for the creation you should always use setters/getters when dealing with ember objects:
create: function() {
var issue = this.store.createRecord('issue', {
name: this.get('controller').get('newName'),
description: this.get('controller').get('newDescription'),
category: this.get('controller').get('newCategory') // assuming new category is a DS.Model instance of category
});
issue.save();
}
If you wish to stick to the syntax you have you would use issue.set('name', this.get('controller').get('newName')), from the looks of your code it seems you are going about this in the wrong way.
You should have a this.route('new') nested under your issues route, that way you wouldn't have to use the controller to store information.
You would simply set the model of the new route to:
model: function() {
return this.store.createRecord('issue');
}
Your template would make use of the input helpers like so:
{{input value=model.name}} and your action would just get the currentModel and call .save().

Routing error with ember-data 2.0 and emberjs 2.0.1

Cross-posting from discuss.ember. I am using Ember 2.0.1 with Ember-data 2.0 and default the default RESTSerializer generated by ember-cli. I know this question has been asked to many places before (which none have real answers) but no solutions have been working for me yet.
I have this model hook for a user model :
export default Ember.Route.extend({
model() {
return this.store.findAll('user');
}
});
Router is the following :
Router.map(function() {
this.route('users', { path: '/' }, function() {
this.route('user', { path: '/:user_id' }, function(){
this.route('conversations', { path: '/'}, function(){
this.route('conversation', { path: '/:conversation_id' });
});
});
});
});
For example, going to /conversations/4 transitions to users.user.conversations. My relations are defined in my models. In the user model I have a DS.hasMany('conversation') conversations attribute set with { embedded: 'always' }. Returned JSON looks like this :
{"conversations":[
{
"id":183,
"status":"opened",
"readStatus":"read",
"timeAgoElement":"2015-08-20T16:58:20.000-04:00",
"createdAt":"June 16th, 2015 20:00",
"user":
{
"id":4
}
}
]}
The problem I get is that Ember-data is able to add my data to the store but I get this error :
Passing classes to store methods has been removed. Please pass a dasherized string instead of undefined
I have read these posts : #272 and #261
Is it a problem with the JSON response?
Thank you. I have been using ember-data for quite a bit of time and never encountered this error before switching to ember 2.0.1 and ember-data 2.0.0
EDIT : I am now sure it is related to the embedded conversations because in ember inspector, if I try to see the conversations of a user (and the conversations are loaded into the store), it returns me a promiseArray which isn't resolved.
Try not to push objects to store directly. Possible use-case of .push() :
For example, imagine we want to preload some data into the store when
the application boots for the first time.
Otherwise createRecord and accessing attributes of parent model will load objects to the store automatically.
In your case UserController from backend should return JSON:
{"users" : [ {"id":1,"conversations":[183,184]} ]}
Ember route for conversation may look like:
export default Ember.Route.extend({
model: function(params) {
return this.store.find('conversation', params.conversation_id);
}
}
User model:
export default DS.Model.extend({
conversations: DS.hasMany('conversation', {async: true})
});
You don't have to always completely reload model or add child record to store. For example you can add new conversation to user model:
this.store.createRecord('conversation', {user: model})
.save()
.then(function(conversation) {
model.get('conversations').addObject(conversation);
});
P.S. Try to follow Ember conventions instead of fighting against framework. It will save you a lot of efforts and nervous.
Your conversation route has URL /:user_id/:conversation_id. If you want it to be /:user_id/conversations/:conversation_id, you should change this.route('conversations', { path: '/'}, function(){ to this.route('conversations', function(){ or this.route('conversations', { path: '/conversations'}, function(){

What array name does Ember Data expect for sub directory models?

I recently started learning Ember and using Ember-CLI so I'm not quite well educated about Ember Data and what array names it expects for relationships that are in sub directories in my app.
// models/server.js
import DS from 'ember-data';
export default DS.Model.extend({
serverHistory: DS.hasMany("history/server", { async: true })
});
// models/history/server.js
import DS from 'ember-data';
export default DS.Model.extend({
server: DS.belongsTo("server", { async: true })
});
I've tried returning these names from my API
server_historys_ids
server_histories_ids
history_server_ids
history_servers_ids
But I don't see an XHR request for Server history in my application. The servers itself are fetched fine.
Update
I changed my relationship name and the API is returning history ids but I'm still not getting an history json request even though I'm trying to each in the template. The game relationship data is accessible in the template and a request is successfully made.
// models/server.js
import DS from 'ember-data';
export default DS.Model.extend({
// attr's here.. not relevant
// Relationships
game: DS.belongsTo("game", { async: true }), // works
serverHistories: DS.hasMany("history/server", { async: true }) // doesn't make a request like game does.
});
I also have an adapter/history/server.js but it's only telling what namespace to use - "api".
Update 2
I think the problem may be in the way I'm calling the data to the model.
// routes/server/view/index.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
var parentModel = this.modelFor("server.view");
return this.store.query("server", { server_address: parentModel.server_address });
// return this.store.find("server", 1);
}
});
How come when I use find with an id it updates the template data and when I use query with parameters it doesn't?
Update 3
So I got my find and query problem sorted out, here's the way I got it to work: https://stackoverflow.com/a/31831667/1814027
The relationship problem still persists. I see no serverHistory data in my Ember toolbar nor a request being made to the API for it.
I beleive serverHistory is anti-conventional name for hasMany and serverHistories should be instead.
export default DS.Model.extend({
serverHistories: DS.hasMany("history/server", { async: true })
});
Then in case of ActiveModelAdapter expected server payload is:
{"server": {"id": 1, "server_history_ids": [1,2,3]}}
It doesn't depend on the fact that serverHistory is namespaced model, it depends on relation name only.
For example for model:
// models/server.js
import DS from 'ember-data';
export default DS.Model.extend({
bars: DS.hasMany("history/server", { async: true })
});
expected payload is:
{"server": {"id": 1, "bar_ids": [1,2,3]}}
Update
Working ember-cli example: https://github.com/artych/so_ember_data_subdir
Artych's answer helped me on the right path but Ember didn't want to recognise server_history_ids so I just renamed the hasMany relation to histories and returned histories: [] from my API. Now it works.. don't know why but it works.

How should I filter items of a user?

I am using Ember 1.13.2 and Ember Data 1.13.4. The API conforms to JSON API format (http://jsonapi.org/format).
A user has many items. Doing {{model.items}} in the template will return ALL items of the user.
What if I also need to display ONLY blue items from the user. How should I go about this?
// Route
import Ember from 'ember';
export default Ember.Route.extend({
model(params) {
// Executes: http://localhost:3099/api/v1/users/5
return this.store.findRecord('user', params.user_id);
}
})
// Template
firstName: {{model.firstName}} - works
<br>items: {{model.items}} - works
<br>blue items: {{model.items}} - what do we do about this?
// app/models/user.js
import DS from 'ember-data';
export default DS.Model.extend({
items: DS.hasMany('item', { async: true }),
firstName: DS.attr('string')
});
// app/models/item.js
import DS from 'ember-data';
export default DS.Model.extend({
user: DS.belongsTo('user', { async: true }),
name: DS.attr('string')
});
I misunderstood the original question. It seems as if you want to fetch only the items where the color is blue (and avoid fetching the rest). For this, you'll need to query the server, which requires server-side code. But, once you have the server-side code done, you can do something like this:
blueItems: Ember.computed('items.#each.color', {
get() {
const query = {
user: this.get('id'),
color: 'blue'
};
return this.get('store').find('item', query);
}
})
But again, you'll need your server to support querying for that data. (The JSON API states how you need to return the data, but you'll need to implement the query yourself.)
Old answer that filters the items after fetching for display (just for reference):
I would use a computed property:
blueItems: Ember.computed('items.#each.color', {
get() {
return this.get('items').filter((item) => {
return item.get('color') === 'blue';
});
}
})
Or the shorthand ;)
blueItems: Ember.computed.filterBy('items', 'color', 'blue')
Not every operation has an Ember shorthand which is why I gave the full example first.
Using computed properties with promises is sometimes tricky, but this computed property should update whenever your items array updates.