With ember.js, how do I prevent individual queries for each related object - ember.js

I have three models, syndicates, users and activities. Each syndicate has many activities, and each activity has one user. My models look like this:
App.Syndicate = DS.Model.extend
activities: DS.hasMany('activity', async: true)
name: DS.attr 'string'
App.Activity = DS.Model.extend
syndicate: DS.belongsTo('syndicate')
user: DS.belongsTo('user', async: true)
App.User = DS.Model.extend
activities: DS.hasMany('activity', async: true)
I display the activities using
article#lead
h1= model.name
each activity in model.activities
p
= activity.id
p
= activity.user.name
Looking at syndicate/1, I get a list of the activities for that syndicate, with a user name for each activity, but each user is triggering a separate request to the api - very slow and expensive. I want to know if I cant request the users in a single query.
My reading suggested I could simply set 'coalesceFindRequests' to true, but this didn't work for me
DS.RESTAdapter.reopen
namespace: 'api/v1'
coalesceFindRequests: true
App.Store = DS.Store.extend(
serializer: DS.RESTSerializer.extend
primaryKey: (type) ->
'_id';
)
App.ApplicationAdapter = DS.ActiveModelAdapter.extend()
My route
App.SyndicateRoute = Ember.Route.extend
model: (params) ->
console.log params.syndicate_id
#store.find 'syndicate', params.syndicate_id
Any pointers on what I'm doing wrong?. I'm very new to ember.
Thanks :)

You can, side load the related data for the aggregate.
http://emberjs.com/guides/models/the-rest-adapter/

Related

ember-cli-mirage loses relationship when models are sideloaded

I've got following models:
// venue.js
export default Model.extend({
name: DS.attr('string')
});
// web.js
export default Model.extend({
webname: DS.attr('string'),
venue: DS.belongsTo('venue', {async: false})
});
and I use the RESTAdapter. I defined my Mirage configuration as following:
// serializers/application.js
import { RestSerializer } from 'ember-cli-mirage';
export default RestSerializer.extend({
});
// config.js
export default function() {
this.get('/webs', schema => {
let venue = schema.venues.create({name: 'venue name'});
let web = schema.webs.create({
webname: 'web name',
venue: venue
});
return {
web: web,
venue: venue
}
})
}
This model sideloading is a part of our application, so I have to use it. Anyways as you can see the response is fine here, i.e. it correctly identifies the foreign keys etc:
But when I receive the response I can't access the venue from the web - it's null. The ember inspector confirms that:
Does anyone have any idea of how I can preserve the relationship when I obtain the data?
Glad you found include! The idea there is that whether associations are included or not can depend on the request. For example, with JSON:API a lot of apps will use query param includes to specify inclusion from the client-side. Other apps will have their servers send over default server-side includes, and that's what the include key is used for.
Also note that include can be a function, if you want more dynamic behavior. You can check out the docs for some examples.
Last point – your get handlers really should be returning what's already in Mirage's database, rather than creating new resources then returning them. This way your app will behave more similar to how it will in production.
So instead of
this.get('/webs', schema => {
let venue = schema.venues.create({name: 'venue name', id: 100});
let web = schema.webs.create({
webname: 'web name',
venueId: 100
});
return web;
})
try
this.get('/webs', schema => {
return schema.webs.all().models[0]
})
to return the first model (or schema.webs.all() if the endpoint should return a collection). Then, to seed Mirage with your starting data, put your data creation logic in scenarios/default.js using server.create:
// scenarios/default.js
export default function(server) {
let venue = server.create('venue', {name: 'venue name', id: 100});
server.create('web', {
venue,
webname: 'web name'
});
}
Ok, I did it a bit wrong apparently. Here is how one should do the sideloading correctly.
Since my web requires a venue to be sideloaded, I created a new model-specific serializer by calling ember g mirage-serializer web. Then I can specify the relationships which should be loaded alongside with the main entity via the include field:
// serializers/web.js
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
include: ['venue'],
});
Then the config.js can be changed to
// config.js
this.get('/webs', schema => {
let venue = schema.venues.create({name: 'venue name', id: 100});
let web = schema.webs.create({
webname: 'web name',
venueId: 100
});
return web;
})
// OR
this.get('/webs', schema => {
let venue = schema.venues.create({name: 'venue name', id: 100});
let web = schema.webs.create({
webname: 'web name',
venue: venue
});
return web;
})
or one can use fixture files etc.

How do I set up polymorphic relationships using ember-data

So lets say a recipe has several ingredients of differing amounts.
Recipe Model
var Recipe = DS.Model.extend({
name: DS.attr('string'),
ingredients: DS.hasMany('ingredient')
});
Ingredient Model
var Ingredient = DS.Model.extend({
name: DS.attr('string'),
recipes: DS.hasMany('recipe'),
// amount?
});
So the amount of each ingredient would depend on the recipe. However on its own the ingredient will not have an amount.
How would you go about modeling that data? Right now I am using the FixtureAdapter until I finish building the interface.
Using Ember 1.5.1 and Ember-Data 1.0.0-beta.7+canary.b45e23ba.
To answer your first question:
Define the model like so
App.Comment = DS.Model.extend({
message: DS.belongsTo('message', {
polymorphic: true
})
});
And the property needs an additional property propertyType, defining the relationship type
{
"message": 12,
"messageType": "post"
}
https://github.com/emberjs/data/blob/master/TRANSITION.md#polymorphic-relationships
Now to your second question, not sure if polymorphism would be necessary. I might just include a joining record
var RecipeIngredient = DS.Model.extend({
amount: DS.attr(),
ingredient: DS.belongsTo('ingredient')
});
var Recipe = DS.Model.extend({
name: DS.attr('string'),
ingredients: DS.hasMany('recipeIngredient')
});

EmberJS simple ID from related object

is there any way to do some filtering based on the related ID? imagine I have 2 models one is a song model and the other is an album model so obviously the song has album with an id, the json will return the model with
{album: 1}
so if I wanna filter it seems that I can't compare the album id with something like
song.get('album.id') === 1
is there any way to do it so simply?
Thanks
Edit: models
App.Album = DS.Model.extend({
irrelevantstuff: DS.attr(),
songs: DS.hasMany('Song')
})
App.Song = DS.Model.extend({
irrelevantstuff: DS.attr(),
album: DS.belongsTo('Album')
})
I tried both with {async:true} and without, still same problem.
however I noticed that if I run it the first time it gives me undefined to all of the album.id but if I run it the second time I get the ids, now I have everything on the store, so it's not doing an AJAX request either the first or second time.
{"songs": [{"irrelevantstuff":"foo", "album": 1}, {"irrelevantstuff":"bar", "album": 1}]}
{"albums": [{"irrelevantstuff":"album", "songs": [1,2]}]
Relationships should be camelCase
App.Album = DS.Model.extend({
irrelevantstuff: DS.attr(),
songs: DS.hasMany('song', {async: true})
})
App.Song = DS.Model.extend({
irrelevantstuff: DS.attr(),
album: DS.belongsTo('album', {async: true})
})
Since your data for the relationship isn't coming down in the request for the song/album it would be considered async. As such if you want to access it you'll need to use it asynchronous methods, promises.
var songPromise = this.store.find('song', 1);
songPromise.then(function(song){
var albumPromise = song.get('album');
albumPromise.then(function(album){
// the album is available for usage....
if(album.get('id') == 1){
alert('woo hoo');
}
});
});

ember: Model can't extend another model for polymorphic association

I'm trying to get polymorphic associations to work but had no luck so far. Basically I wanted the track to belong to playlist AND album
App.Playlist = App.Trackable.extend
App.Album = App.Trackable.extend
App.Trackable = DS.Model.extend
tracks: DS.hasMany 'track'
App.Track = DS.Model.extend
title: DS.attr 'string'
trackable: DS.belongsTo('trackable',
polymorphic: true)
But then I get the following error
Uncaught TypeError: Cannot call method 'extend' of undefined
I think you have the order wrong. You're trying to extend models that you have not declared yet. Try this:
App.Trackable = DS.Model.extend
tracks: DS.hasMany 'track'
App.Playlist = App.Trackable.extend
App.Album = App.Trackable.extend
App.Track = DS.Model.extend
title: DS.attr 'string'
trackable: DS.belongsTo('trackable',
polymorphic: true)

Ember-data mapping embedded object from JSON

I am struggling with a strange problem. I have a model called Activity with a property defined like this:
owner: DS.belongsTo('App.User', embedded: true)
The User is also a defined model when I'm getting the JSON response like this:
some single properties and
user: { id: etc. }
My all properties map well but the user embedded object from JSON doesn't map to the owner property. However, when I change
owner
to
user
It maps well. But I want to leave the owner because it's a better representation of what I mean. I tried this action:
owner: DS.belongsTo('App.User', key: 'user', embedded: true)
but it didn't help.
First, I recommend using the latest Ember / EmberData, but you will need to handle embedded records manually by enhancing extractSingle in a custom serializer (see example below). Also, you should define relationships like this:
App.Activity = DS.Model.extend({
name: DS.attr('string'),
owner: DS.belongsTo('user')
});
App.User = DS.Model.extend({
name: DS.attr('string'),
activities: DS.hasMany('activity')
});
Next, I recommend using the ActiveModelAdapter if you are using underscores when communicating with the server (i.e. like in EmberData 0.13):
App.ApplicationAdapter = DS.ActiveModelAdapter;
Finally, to use owner for a User, override typeForRoot in a custom serializer.
For example:
App.ApplicationSerializer = DS.ActiveModelSerializer.extend({
typeForRoot: function(root) {
if (root == 'owner' || root == 'owners') { root = 'user'; }
return this._super(root);
},
// based on: https://github.com/emberjs/data/blob/master/TRANSITION.md#embedded-records
extractSingle: function(store, type, payload, id, requestType) {
var owner = payload.activity.owner,
ownerId = owner.id;
payload.owners = [owner];
payload.activity.owner_id = ownerId;
return this._super.apply(this, arguments);
}
});
Example JSBin