This is really confusing me at the moment and it's probably dead simple to fix.
I have two model, related between themselves:
App.User = DS.Model.extend({
name: DS.attr('string'),
zip_code: DS.attr('string'),
address_line: DS.attr('string'),
country: DS.attr('string'),
active: DS.attr('boolean'),
state: DS.attr('string'),
comments: DS.attr('string'),
user_type: DS.belongsTo('user_type', { async: true })
});
App.UserType = DS.Model.extend({
name: DS.attr('string'),
users: DS.hasMany('users', { async: true })
});
I'm trying to the a User's UserType.name:
Simple enough right? Wrong!
// Doesn't work.
user.get('user_type.name');
// Also doesn't work.
user.get('user_type').get('name');
In my console, I can clearly see the object is there and related.
You will probably need to "resolve" the UserType:
user.get('user_type').then(function(userType) {
console.log("User type is:", userType.get('name'));
});
Note that this introduces asynchrony to your method... Depending on what you're trying to do, you'll need to accomodate for that. Normally, when bound to a Handlebars template, this resolving of related types happens automatically, so you should be able to (in a template) use {{user.user_type.name}}
Because you've set the relationship as {async: true}, Ember presumes that the relationship is asynchronous, i.e. it needs to load the UserType model separately. So, at least the first time it's called, user.get('user_type') actually returns a promise which, at the point at which the chained method is called, is not guaranteed to have loaded content.
Even if it's already in the store, (if memory serves) Ember is not going to attempt to set it until the next run loop. Calling .get('name') on that promise immediately will thus give you null.
The user.get('user_type.name') syntax will just resolve internally to the same thing as user.get('user_type').get('name') so that result will be no different.
Steve H.'s response should work. Alternatively, if you're working with a server (not fixtures) and the server response is such that the userType is sideloaded with the user – or if you are in a position to change the response – you should be able to remove the {async: true} conditions, and both of the expressions in your question should work as expected.
Related
I had this code in my ember app:
var FooModel = DS.Model.extend({
bars: DS.hasMany( 'bar', { async: true, inverse: 'foo'} )
});
var BarModel = DS.Model.extend({
foo: DS.belongsTo( 'foo', { async: true, inverse: 'bars'} )
});
Edit: Using
Ember : 1.13.7
Ember Data : 1.13.8
But when I went to render foo.bars, they wouldn't be loaded unless I used the browser back and forward buttons. Reloading the page would cause the foo.bars to disappear again.
When I changed the code to this:
var FooModel = DS.Model.extend({
bars: DS.hasMany( 'bar', { async: true } )
});
var BarModel = DS.Model.extend({
});
Everything works as it should, and I'm just really confused as to why. Especially since I took that original code from another ember app where it was working just fine (although there might have been some adapter/serializer magic going on that I don't know about). Edit: The app where it was working is using
Ember : 1.4.0
Ember Data : 1.0.0-beta.7+canary.b45e23ba
Handlebars : 1.3.0
Edit: Using REST adapter for both
Async in relatioships
{ async: true} is the default value for relationships in Ember since 1.13 which means that in most cases you define the relationship without specifying the async value at all. It means that related records will not be loaded into the store until required. This is almost always the desired way to go since it prevents blocking by returning a promise. Remember that promises require a different way of programming since the process no longer is procedural step by step through the code. You manage promises by chaining a .then(function(param){// handle fulfilled promise here});.
There are cases where { async: false} could be beneficial. For example in a one to one relationship and you wanted both sides of the relationship to be loaded immediately.
Why your code does not work
I don't know for sure but it seems that the code as you have written it and it could be a bug in the code. The same as with async above you also do not actually need to specify the inverse value here either. According to Ember documentation:
Ember Data will do its best to discover which relationships map to one another. Explicit Inverses
Your application as a simple one to many relationship should work fine simply with:
var FooModel = DS.Model.extend({
bars: DS.hasMany( 'bar' )
});
var BarModel = DS.Model.extend({
foo: DS.belongsTo( 'foo' )
});
I have replied a similar question What is an “async relationship”? / {async: true} vs. {async: false}
Async relationships (default)
Accessing the relationship will return a promise.
post.get('comments').then((comments) => {
// now we can work with the comments
});
If the data is not available and the relationship is accessed (for example, from a template or a "consumed" computed property), Ember Data will automatically fetch the resources.
Read more about Relationships as Promises
Sync relationships (from docs)
Ember Data resolves sync relationships with the related resources
available in its local store, hence it is expected these resources
to be loaded before or along-side the primary resource.
BelongsTo
export default DS.Model.extend({
post: DS.belongsTo('post', {
async: false
})
});
In contrast to async relationship, accessing a sync relationship
will always return the record (Model instance) for the existing
local resource, or null. But it will error on access when
a related resource is known to exist and it has not been loaded.
let post = comment.get('post');
HasMany
export default DS.Model.extend({
comments: DS.hasMany('comment', {
async: false
})
});
In contrast to async relationship, accessing a sync relationship
will always return a DS.ManyArray instance
containing the existing local resources. But it will error on access
when any of the known related resources have not been loaded.
post.get('comments').forEach((comment) => {
});
If you are using links with sync relationships, you have to use
ref.reload to fetch the resources.
After seeming to bang my head on a wall for what seems to be a simple problem I thought it best to ask for some help.
I am creating an EmberFire application that allows users to authenticate using the simple login. Once authenticated the user can store particular items for later retrieval.
I have models defined as so:
USER:
export default DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
dateOfBirth: DS.attr('date'),
registeredAt: DS.attr('date'),
compentencies: DS.hasMany('competency', { async: true })
});
COMPETENCY:
export default DS.Model.extend({
title: DS.attr('string'),
endDate: DS.attr('date'),
user: DS.belongsTo('user', { async: true })
});
These are stored in the firebase db in a denormalised for as I would like them to be.
My issue arises on retrieval of persisted data. I am not sure how I am supposed to lock the competencies down to a particular user?
The way the rules cascade in FireBase I am not sure if this is even possible in this form but it does not seem right that I would have to store each competency in an embedded form under each user node.
I know that I could just allow read access to all 'Competency' models with this.store.find('competency') and then filter them on the client but that does not fit the security I would expect.
Any help would be great thanks.
Ryan
To only allow read access to a competency if the competency belongs to the user who is currently logged in you can use $competencyId (you can replace "competencyId" with any valid string). Using $competencyId limits access to a specific node under competencies/ instead of allowing access to the entire tree.
"rules": {
"competencies": {
"$competencyId": {
".read": "data.child('user').val() === auth.uid"
}
}
}
I have a pretty basic setup where I'm trying to format a date in my Controller. The problem is I can't access it in the formattedStart function below, whereas I CAN access it in the summaryRowAction handler. This is baffling me, because console.logging this in both places gives the same result. But for some reason inside of formattedStart, this.get('model.startDate') is undefined.
App.SummaryRowController = Ember.ObjectController.extend({
formattedStart: function(){
console.log(this.get('model.startDate');)
return this.get('model.startDate');
}.property(),
actions: {
summaryRowAction: function(){
console.log(this.get('model.startDate'));
}
}
});
Here is my model and my template (in Jade) for reference:
App.PricingSummary = DS.Model.extend({
startDate: DS.attr(),
endDate: DS.attr(),
days: DS.hasMany('day', {async: true}),
property: DS.belongsTo('property', {async: true})
});
script(type="text/x-handlebars", data-template-name="summaryRow")
.summaries__summary("{{action 'summaryRowAction'}}")
.summaries__summary--item{{formattedStart}} — {{endDate}}
It's because the first (and only) time that the property is evaluated, model is actually null. You need to specify startDate as a dependency in the property so Ember knows to re-evaluate when the data changes. Also, you don't need model.* in an object controller, the properties are automatically delegated to content/model
So:
formattedStart: function(){
console.log(this.get('startDate');)
return this.get('startDate');
}.property('startDate'),
I'm new to ember, and try to understand how it works.
I've defined a store with a fixturesAdapter as adapter (rev 7).
I've defined two models:
App.Tag = DS.Model.extend({
name: DS.attr('string'),
item: DS.belongsTo('App.Item')
});
and:
App.Item = DS.Model.extend({
name: DS.attr('string'),
tags: DS.hasMany(App.Tag, { embedded:true }),
})
I also fill their associated fixtures and at last a controller:
App.itemsController = Ember.ArrayController.create({
content: App.store.findAll(App.Item)
});
I've defined a function inside App.Item model:
tagline: function(){
return this.get('tags').toArray().map(function(tag){
return tag.get('name');
}).join(',');
}.property('tags.#each.isLoaded')
Here is the corresponding jsfiddle: http://jsfiddle.net/K286Q/29/
My questions are:
What am I doing wrong?
Why does it see several tags associated to first item, but is not able to get their name?
You're running up against a few breaking changes in the current version of ember-data.
The first is that, since revision 6 of ember-data, IDs are string-normalized and must be represented as strings in fixtures. Note that the REST adapter will convert numbers/strings, but the fixture adapter doesn't do any conversions. This is a common source of confusion (see the previous question).
The second is that support for embedded data objects has been temporarily removed from ember-data. I'm pretty sure that this feature will be re-introduced in a better way than supporting {embedded: true} in the attributes. IMO, embedding is more of an adapter concern and doesn't really belong with the definition of the model.
I adjusted your fixtures and got your example working here: http://jsfiddle.net/dgeb/zHz4Y/
I have a very common situation, if there is a term for it, well I am not aware of it then.
A record is having fields: id, enabled, text, etc...
and having POST /record/enable to enable or disable record, as it invoke is bigger process on server.
So, once callback from normal POST is received, I want to update record.enabled to true locally, which should not be part of any transaction. and should be updated directly.
How to achieve this?? Or what is better alternative for such requirement?
I think something along these lines should do:
App.PObject = DS.Model.extend({
id: DS.attr('number'),
name: DS.attr('string'),
nestedObject: function() {
if (!this.nestedObj) {
this.nestedObj = Ember.Object.create({
active: false
});
}
return this.nestedObj
}.property()
});
As i have seen so far, Model is dirty only when its attributes, that are defined as DS.attr, change.
EDIT: Realized later that, This solution doesn't work.
Proper term of my problem is: transient field
Transient field of an Object is ignored for serialization/deserialization (like Java ORMs ignores transient fields) http://en.wikipedia.org/wiki/Transient_(computer_programming
I have figured out a solution. Its really simple.
App.PObject = DS.Model.extend({
id: DS.attr('number'),
name: DS.attr('string'),
//will not be part of Ember-data persistence, but will be populated on GET
enabled: null
});
Object (Record) properties which are not attributes are ignored.