I need to navigate through my relationships inside a controller and have not figured out how to do it. I have the following models.
// models/asset.js
export default DS.Model.extend({
name: DS.attr('string'),
type: DS.belongsTo('lookup', {inverse: null, async: true}),
children: DS.hasMany('asset', {inverse: 'parent', async: true}),
parent: DS.belongsTo('asset', {inverse: 'children', async: true})
});
// models/lookup.js
export default DS.Model.extend({
value: DS.attr('string'),
children: DS.hasMany('lookup', {inverse: 'parent', async: true}),
parent: DS.belongsTo('lookup', {inverse: 'children', async: true})
});
The type of my current asset is based on the child records of the parent asset.
In an action in the controller I need to get the current asset's parent's type's children. I would like to just use:
return this.store.findAll('lookup', this.get('asset.parent.type.children')).then(function(parentTypeChildren) {});
But life is not so easy.
If I have the asset loaded and I do this.get('asset.parent') do I get the full asset record or just the parent asset ID?
Any hints about this? All help will be greatly appreciated.
Since you have async: true, a this.get('asset.parent') would trigger a server call (if it is not already in the store) and fetch the parent asset (as a set call) and return it as a promise. this.get('asset.data.parent') would return the id of the parent.
So basically you should try something like this (under the assumption that the asset.parent.type is already in your store):
var childrenLookUps = this.get('asset.parent.type.children');
childrenLookUps.then(function(){
// do something with your childrenLookUps
});
Related
I've two models:
ticket:
export default DS.Model.extend({
name: DS.attr('string'),
email: DS.attr('string'),
messages: DS.hasMany('message'),
})
messages:
export default DS.Model.extend({
message: DS.attr('string'),
date: DS.attr('date'),
ticket: DS.belongsTo('ticket', { async: true }),
});
And a router with this model method:
model(params) {
return this.get('store').findRecord('ticket', params.id, {reload: true, include: 'messages'});
},
At the page reload all ok, only a call to tickets/:id is made but sometimes ember make a bunch of calls to messages/:id. Why don't use the included data and try to retrieve from the server?
I've tried async: false on hasMany relation but I've this error:
Error: Assertion Failed: You looked up the 'messages' relationship on a 'ticket' with id 15 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.hasMany({ async: true })`)
Any idea?
This is the GET tickets/:id response:
{"data":
{
"id":"15",
"type":"tickets",
"attributes":{...},
"relationships":{"messages":{"data":[{"id":"1478482584658","type":"messages"},{"id":"1478482588516","type":"messages"},{"id":"1478517720","type":"messages"},{"id":"1478517813","type":"messages"},{"id":"1478517893","type":"messages"},{"id":"1478530030","type":"messages"},{"id":"1478530032","type":"messages"},{"id":"1478533446","type":"messages"}]}}},
"included":[
{"id":"1478482584658","type":"messages","attributes":{...}},{"id":"1478482588516","type":"messages","attributes":{...}},
...
}
}
]
}
Why don't take data from included and make other calls to the server?
What about promise?
While working with relationships it is important to remember that they return promises.
https://guides.emberjs.com/v2.1.0/models/working-with-relationships/#toc_relationships-as-promises
While using the RESTAdapter, I have an Organization model which is to be embedded in the response. It appears that the default implementation of the Ember.RESTAddapter sends the id, using the same model name, but not as an object (this currently 'works'):
POST/PUT api/v1/item/{id}
{
"item" {
id: "1029383829"
...
organization: "26044097612186763401268824297"
}
}
I have consulted the documentation, and found that the mixin DS.EmbeddedRecordsMixin should do this, coupled with declaring embedded: "always" on the attrs or the Serializer:
models/item.js
var Item = DS.Model.extend({
...,
organization: DS.belongsTo("organization", {embedded: "always"})
});
serializers/item.js:
var ItemSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
organisation: {serialize: "id", embedded: "always"}
}
}
);
However, when deserializing records, Ember Data complains, saying that it expected an object, but just got a string:
Assertion Failed: Expected an object as data in a call to push for
app#model:organization: , but was 26044097612186763401268824297
Ultimately, I would prefer a system, likened to sideloading, wherein an additional attribute, post-fixed "_id", describes the corresponding id of an embedded record:
{
"item": {
id: 1,
name: "name",
organization_id: "26044097612186763401268824297"
...
}
}
How can I allow serializing and deserializing embedded id sideloading for my Organization model?
You aren't actually embedding the record, you're just specifying the id, in that case you should mark it as async.
var Item = DS.Model.extend({
...,
organization: DS.belongsTo("organization", {async: true})
});
And remove your embedded records implementation.
I created model Consultation:
import DS from 'ember-data';
export default DS.Model.extend({
title: DS.attr('string'),
records: DS.hasMany('record', { async: true }),
currentUser: DS.belongsTo('user'),
remoteUser: DS.belongsTo('user')
});
And also I created model Record:
import DS from 'ember-data';
export default DS.Model.extend({
record_text: DS.attr('string'),
record_poster_id: DS.attr('string'),
record_consultation_id : DS.attr('number'),
consultation: DS.belongsTo('consultation'),
isMine: DS.attr('boolean')
});
At first all consultations load during opening page. And I don't want to load all records of each consultation during opening page. To do this I added async: true but all records loaded simultaneously sending many requests like /consultations/:id/records. After that consultation and records still non-joined. I have next json response for consultation:
{
"consultations":[
{
"id":140721,
"title":"ExpertId: 41217, clientId: 0",
"links":{
"records":"records"
},
"currentUser":41217,
"remoteUser":159984
},
......
]
}
And for records:
{
"records":[
{
"record_id":681952,
"record_consultation_id":140721,
"record_poster_id":0,
"record_text":"1",
},
........
]
}
I think I need to override default serializer. I tried to create serializer for Record:
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
primaryKey: 'record_id',
keyForRelationship: function(key, kind) {
return 'record_consultation_id';
}
});
But it still doesn't work.
Please advise how to join models using links?
UPDATE:
Template
{{#each item in model.records}}
<div class="table message">
{{item.record_text}}
</div>
{{/each}}
I am doing Async (lazy loading) of data using RESTAdaptor and Ember-Data too.
For my links area, i put in the full request URL as follows:
"links":{
"records":"/consultations/140721/records"
},
And using firebug to look at when the request gets sent off, its only when I request for the async content that the AJAX gets fired off.
model.get('records');
Can you provide your Controllers & Template code so I can see how your accessing the Records?
I'm currently facing a big problems for days. I'm using ember simple-auth plugin which provide me a session object accessible through the code or the templates. That session object store the account information such as username, id and rights.
My models are like this :
App.Right = DS.Model.extend({
label: DS.attr('string', { defaultValue: undefined })
});
App.Right.FIXTURES = [
{
id: 1,
label: 'Admin'
}, {
id: 2,
label: 'Manager'
}, {
id: 3,
label: 'User'
}
];
App.User = DS.Model.extend({
username: DS.attr('string'),
rights: DS.hasMany('right', {async: true})
});
App.User.FIXTURES = [
{
id: 1,
username: "Someone",
rights: [1]
}
];
Then I have (as specified on the simple-auth documentation) this setup :
App.initializer({
name: 'authentication',
initialize: function(container, application) {
Ember.SimpleAuth.Session.reopen({
account: function() {
var userId = this.get('userId');
if (!Ember.isEmpty(userId)) {
return container.lookup('store:main').find('user', userId);
}
}.property('userId')
});
...
}
});
Inside one of my view I'm doing this:
this.get('context.session.account.rights').toArray()
but it gives me an empty array. That piece of code is executed inside an Ember.computed property.
The question is how can I resolve the childrens of account before rendering the view ?
Since async: true this.get('context.session.account.rights') will return a promise object so you will have to use this.get('context.session.account.rights').then(... see: http://emberjs.com/api/classes/Ember.RSVP.Promise.html#method_then
Okay so I finally got it to work. It doesn't solve the original question because the original question was completely stupid. It's just IMPOSSIBLE to resolve relationships synchronously when you use the async: true. Trying to resolve it in advance is NOT the solution because you will still not know when it has actually resolved.
So here is the solution:
$.each(this.get('cellContent.buttonList'), function(i, button) {
button.set('hasAccess', false);
this.get('context.session.account').then(function(res) {
res.get('rights').then(function(result) {
button.set('hasAccess', Utils.hasAccess(result.toArray(), button.rights));
});
});
});
Using the following cellContent.buttonList definition:
buttonList: [
Ember.Object.create({
route: 'order',
label: 'Consult',
rights: 'all'
}), Ember.Object.create({
route: 'order.edit',
label: 'Edit',
rights: [1, 2]
})
]
Explanation
We have to use Ember.Object in order to have access to the set method. Using an Ember object is very handy. It allows us to change the value of properties after the render process making the view to update according to the new value you just set.
Because it updates the view, you don't have to care anymore whether your model has resolved or not.
I hope this will help people as much as it helps me.
Okay, so I'm trying to copy an existing model as a new model instance (so all attributes are the same except for its ID. In my template, I have an action, copy, that passes the model in scope at that place in a list to the controller so it can be copied. My controller code is below. I appear to be able to create a new record, but its ID is set to 'fixture-0', 'fixture-1' etc., and calling .save() on it (see below) results in an error,
Uncaught TypeError: Cannot call method 'serialize' of undefined
As I'm currently stubbing the models for development, I'm using the fixture adapter, as it seems that's got something to do with the problem.
Controller code:
REApp.ApplicationController = Ember.ArrayController.extend({
actions: {
copy: function(current_model){
var self = this;
console.log(current_model);
var new_record = this.store.createRecord('cycle', {
railsId: null,
accountId: current_model._data.accountId,
//lots more attributes here
});
new_record.save();
console.log(new_record);
}
}
});
console.log call from the controller, showing new_record:
a {id: "fixture-0", store: a, container: t, currentState: Object, _changesToSync: Object…}
Note: even if you take out the .save() method call from the controller, this new record is created with the id set to a string. If .save() is not called (and thus no error raised in the controller), then the new record appears in the ember inspector's data panel with its ID set to a string as above.
This is only my third Ember app (and my first with 1.3.0), so open to the idea that I'm just doing it wrong.
Edit:
Here's the model definition. If it's relevant, I've had other errors returning both varOfModelObject.save(); and this.content.save(); from the controller since, so def thinking it's related to the FixtureAdapter.
REApp.Cycle = DS.Model.extend({
//id: DS.attr('number'), not needed with fixtures
railsId: DS.attr('number'),
siteId: DS.attr('number'), //all numbers here are ints unless noted
clientId: DS.attr('number'),
accountId: DS.attr('number'),
startAt: DS.attr('datetime'),
endAt: DS.attr('datetime'),
chargePerEvent: DS.attr('decimal', {defaultValue: 0.0}),
createdAt: DS.attr('datetime'),
updatedAt: DS.attr('datetime'),
scheduleYaml: DS.attr('string'),
allDay: DS.attr('boolean'),
repeat: DS.attr('boolean'),
exceptionDates: DS.attr('string'),
additionalDates: DS.attr('string'),
charge: DS.attr('number'), //decimal in rails
chargePeriod: DS.attr('string'),
oldRegularEventId: DS.attr('number'),
scheduleHuman: DS.attr('string'),
bagLimit: DS.attr('number'),
untilDate: DS.attr('number'),
sendToWebfleet: DS.attr('boolean', {defaultValue: false}),
contractId: DS.attr('number'),
hourlyRate: DS.attr('number'), //decimal in rails
hourlyCharge: DS.attr('number', {defaultValue: 0.0}), //decimal in rails
doubleEvent: DS.attr('number'),
//these are used for the simple view
weekdays: DS.attr('boolean', {defaultValue: null}),
weekends: DS.attr('boolean', {defaultValue: null}),
//data below this needed for associations
clientName: DS.attr('string'),
commentBody: DS.attr('string'),
siteAddress: DS.attr('string'),
carer1Id: DS.attr('number', {defaultValue: null}),
carer1Name: DS.attr('string'),
carer2Id: DS.attr('number', {defaultValue: null}),
carer2Name: DS.attr('string'),
users: DS.hasMany('user', {async: true}),
items: DS.hasMany('item', {async: true})
});
As mentioned in the comments above, when working with fixtures, you can create new objects in memory, but they are distinguished from the hard-coded fixture objects by a fixture- prefix in their object ID. As a result of this, you may find behaviour being a little screwy - perhaps this will be fixed in a newer version of Ember Data, but in the meantime the answer seems to be to get off using fixture data and move to a live data situation as quick as possible in your dev process.
try this and tell me what you get, yes in the console you gonna get an error yet it works, simply override the method createrecord as follows:
REApp.ApplicationAdapter = DS.FixtureAdapter.extend({
mockJSON: function(store, type, record) {
try {
record.id = Math.floor((Math.random() * 100) + 1);
return this._super(store, type, record);
}
catch(err) {
}
},
}
);