I'm still learning ember.js and have run into a roadblock with ember data not resolving lookup relationships in models. I have one model 'site' that will be basically a lookup table for every other model to differentiate data based on location.
At this point, I'm doing something wrong or missing a key concept - probably both... (or maybe it's the wee hour!)
Site Model (i.e. the lookup table)
import DS from 'ember-data';
export default DS.Model.extend({
code: DS.attr(),
name: DS.attr(),
});
The site model would have a hasMany relationship to all my other models (will be about 12 when complete)
Associate Model
import DS from 'ember-data';
import { belongsTo } from 'ember-data/relationships';
export default DS.Model.extend({
site: belongsTo('site'),
last: DS.attr(),
first: DS.attr(),
active: DS.attr('boolean'),
fullName: Ember.computed('first', 'last', function() {
return `${this.get('first')} ${this.get('last')}`;
}),
});
The 'associate model' will also be a lookup along with 'site' in some other models.
I'm providing data via the JSON API spec but I'm not including the relationship data because as I understand it, ember data it should be pulling down the site data using the site id attribute.
{
"links": {
"self": "/maint/associates"
},
"data": [
{
"type": "associate",
"id": "1",
"attributes": {
"site": "6",
"last": "Yoder",
"first": "Steven",
"active": "1"
},
"links": {
"self": "/associates/1"
}
}
]
}
In my template file I'm referencing associate.site which gives me an error.
<(unknown mixin):ember431>
If I use associate.code or .name to match the site model, nothing will show in the template. The code from the 'site' table is the data I really want to displayed in the template.
So the obvious questions:
Am I wrong that Ember Data should be resolving this or do I need to
include the relationship in my API response?
I realize that my belongsTo in the 'associate' model only references
site while I want site.code, so how do I make that relationship
known or access the field in my 'associate' model?
I didn't include hasMany relationship in the 'site' model because
there would be many. Do I need to do an inverse relationship in
other models? Examples I've seen don't all show the hasMany
relationships setup.
When I look at the models in ember inspector the site field is not
included in the model. Even if I wasn't getting the correct data
should it still show up?
I like ember so far, just need to understand and get over this roadblock
Update: My backend JSON library would only generate relationship links based on the current spec which would be
"related": "/streams/1/site"
but ember data does call
"related": "/sites/1"
to resolve the relationship
So #Adam Cooper answer is correct if you generate links as he answered or if you can only generate the links based on the current specification.
If you're using the JSONAPIAdapter, which is the default, you want your response to look this:
{
"links": {
"self": "/maint/associates"
},
"data": [{
"type": "associate",
"id": "1",
"attributes": {
"last": "Yoder",
"first": "Steven",
"active": "1"
},
relationships: {
"site": {
"links": {
related: "/sites/6"
}
}
}
}]
}
That will allow Ember Data to look up the site via its relationship. Right now Ember is trying to access the site model which Ember Data can't populate hence the error you're getting. As an aside you could probably do with returning an actual boolean value for active too.
Related
I have a many-to-many relationship defined between my tag and payment models as shown below.
//models/tag.js
import Model, { attr, hasMany } from '#ember-data/model';
export default Model.extend({
name: attr('string'),
backgroundColour: attr('string'),
textColour: attr('string'),
payments: hasMany('payment')
});
// models/payment.js
import Model, { attr, hasMany } from '#ember-data/model';
export default Model.extend({
date: attr('date'),
amount: attr('number'),
paymentId: attr('string'),
tags: hasMany('tag'),
});
By default, when I add tags to payments, the id of the payment is used as the key for the relationship. My aim is for Ember data to use the paymentId property as the key for this relationship instead.
The snippet below shows the structure of the data I'm loading, where a tag references payments by the paymentId property.
// Example tag
{
"id": "25",
"name": "Groceries",
"backgroundColour": "31b04b",
"textColour": "ffffff",
"payments": ["20190121201902210"] // References paymentId rather than id
},
// Example payment
{
"id": "1"
"date": "2019-01-27T22:00:00.000Z",
"amount": 1644.44,
"paymentId": "20190121201902210",
"tags": ["25"]
}
I've tried to customise the payment serializer as below,
// serializers/payment.js
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
keyForRelationship(key, _relationship) {
if (key === 'payments') {
return 'paymentId';
}
},
});
However, when the models are loaded I get this error: Assertion Failed: All elements of a hasMany relationship must be instances of Model, you passed [ "20190121201902210" ].
How can I make Ember data use paymentId rather than id when looking up related payments?
I assume that you are using RESTSerializer. It has a primaryKey option, which should be used if the primary key is not named id in your API payload.
It seems to be a little bit difficult for your example as that record seems to have two primary keys: id and paymentId. If only one of them is used to reference related records I would recommend to simply ignore the other one.
If both are used to reference related records, you are in a bad situation. Maybe you can change the API?
If that's not possible I guess you need to map one ID to the other in a serializer which requires to have the payment record loaded before. That gets tricky as serializers are sync which means the record must be loaded before. I guess you will face a lot of edge cases until such a solution is stable - and breaking it would be quiet easy as it highly depends on timing.
So maybe you should even consider not using Ember Data for these resources at all. It highly depends on the assumption that each resource could be identified by a combination of it's type and ID. But it sounds like for you the same resource could be identified by two different IDs.
I can't seem to workout how to load relationships properly in Ember. The user model only returns some of the time. If I refresh the page there seems to be a 50/50 chance the value will be null, or it will resolve correctly.
I understand in the route I'm returning a promise which is the server object (the belongsTo end of the user relationship), but how do I tell the route to wait for the user model to load before loading the page, or how do I tell ember to update the data on the page when it finally does load the user?
I've tried playing around with RSVP in the afterModel hook but haven't had any luck. There must be a obvious solution to this?
route
model(param) {
return this.store.findRecord('server', param.server_id);
},
server model
export default Model.extend(Validations, {
user: belongsTo('user'),
});
user model
export default Model.extend({
displayName: attr('string'),
servers: hasMany('server'),
});
component
export default Ember.Component.extend({});
component template
<div class="user-panel">
<ul class="user-details">
<li>owner:{{model.user.displayName}}</li>
</ul>
</div>
I've read a similar question here How to load belongsTo/hasMany relationships in route with EmberJS
But as I'm only returning a single object rather than an array I get Array Methods must be provided an Array thrown when trying any of the solutions
Server response
{
"data": {
"type": "server",
"id": "578aba694b08ce2310f36798",
"attributes": {
//removed
},
"relationships": {
"jobs": {
"data": [
{
"type": "jobs",
"id": "578aba694b08ce2310f3679a"
}
]
},
"user": {
"data": {
"type": "user",
"id": "57760677d04e0f11f4d3f7e5"
}
}
}
}
}
This should work out of the box! Not the waiting part, but the auto update.
Checkout this twiddle.
But if you want to wait for the model you can just enforce this in the afterModel hook:
afterModel(model) {
return get(model, 'user');
}
I'm working on an Ember app that consumes an external API and I'm having trouble with my serializer.
My app is ingesting a user account but only cares about the very basics of the user account. The API is publishing much more data, which is causing trouble. Specifically, it is including a relationship to a model which I don't include in my Ember App (because it is domain-inappropriate).
I'm trying to configure my app such that it simply ignores the included and relationship information from the API, but even attempting to delete them from the response in the serializer's normalize hook isn't working. It keeps trying to look up the unwritten model and, obviously, failing. As well, I looked at using DS.EmbeddedRecordsMixin but that seems to still depend on having the related model implemented in my app, which I'm really hoping to avoid!
How do I get JSONAPISerializer to ignore relationships and prevent it from attempting to sideload data from the included object?
To better display my situation, here is an example payload:
{
"jsonapi": {
"version": "1.0"
},
"included": [
{
...references models my app doesn't have
}
],
"data": {
"type": "account",
"relationships": {
"permissions": {
"data": [
...references the included object
]
},
},
"id": "16",
"attributes": {
...this is the information I *do* care about
}
}
}
ETA:
My User model:
// app/models/account.js
export default Model.extend(Validations, {
birthdate: attr('date'),
email: attr('string'),
firstName: attr('string'),
lastName: attr('string'),
profile: belongsTo('profile')
}
I'm using Ember's RESTAdapter and have a pretty standard polymorphic relationship:
// models/order.js
import DS from 'ember-date';
export default DS.Model.extend({
transferableItem: DS.belongsTo('transferable-item', { polymorphic: true })
});
// models/transferable-item.js
import DS from 'ember-date';
export default DS.Model.extend({
order: DS.belongsTo('order')
});
// models/ticket.js
import TransferableItem from './transferable-item';
export default TransferableItem.extend();
My JSON looks like this:
{
"orders": [{
"id": 111,
"transferableItem": 999
"transferableItemType": "Ticket"
}],
"tickets": [{
"id": 999
}]
}
Looking in Ember Inspector, both Orders and Tickets properly load. However, the link between the two of them is broken. I get this error:
You looked up the 'transferableItem' relationship on a 'order' with id
999 but some of the associated records were not loaded. Either make
sure they are all loaded together with the parent record, or specify
that the relationship is async (DS.belongsTo({ async: true }))
According to Ember Inspector, there are no transferable-items loaded, so in a way, this error makes sense. However, since this is a polymorphic relationship, shouldn't it just try to use the associated Ticket, which is in fact loaded?
Kept on digging, and discovered that the syntax needs to change when the record is embedded. The JSON should look like this:
{
"orders": [{
"id": 111,
"transferableItem": {
"id": 999
"type": "ticket"
}
}],
"tickets": [{
"id": 999
}]
}
I'm relatively new to Ember CLI and have just begun to use ember-data to retrieve JSON data from my Laravel Eloquent-powered RESTful API.
I would like to populate a DataTables table with the data returned from the "Accounts" API. Each "Account" also has an embedded "Contact" record available under the "contact" key in the JSON returned from the server. The JSON is correct and an example of an "Account" record coming back from the API is:
{
"accounts": {
"id": 1,
"account_name": "My Account",
"contact": {
"id": 54,
"first_name": "John",
"last_name": "Smith"
}
}
}
In my controller I have called the serialize() method on each "Account" DS.Model in the RecordArray to convert it to a JSON object because this should allow relationships to be serialized too. However, even though I am using the ActiveModelSerializer with the EmbeddedRecordsMixin the relationships in the resulting JSON appear like this:
"contact": {
"id": "54"
}
So only the "id" field is present in the serialized, embedded "Contact" object.
My "account.js" serializer looks like this:
import DS from "ember-data";
export default DS.ActiveModelSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
contact: {embedded: 'always'},
},
});
and my "account.js" model looks like this:
import DS from 'ember-data';
export default DS.Model.extend({
contact: DS.belongsTo('contact', {embedded: 'always'}),
});
I cannot understand what I am doing wrong and I'd be grateful of any help anyone could offer.
Thanks.