Could someone explain how async works with belongsTo/hasMany relationships? - ember.js

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.

Related

ember-pouch: does not load hasMany relationship when the belongsTo side is polymorphic

I have an ember-data app that uses the ember-pouch adapter for local & remote storage.
I am unable to load hasMany relationships when the belongsTo side is polymorphic. I've played with async: true/false and donstsave: true/false options on the hasMany side, to no avail.
The setup:
post can have many comments.
comment can have many comments.
comment belongs to commentable.
// app/models/post.js
import DS from 'ember-data';
import { Model } from 'ember-pouch';
export default Model.extend({
DS.hasMany('comment', { inverse: 'commentable' });
});
// app/models/comment.js
import DS from 'ember-data';
import { Model } from 'ember-pouch';
export default Model.extend({
DS.belongsTo('commentable', { polymorphic: true });
DS.hasMany('comment', { inverse: 'commentable' });
});
The Problem
Calling post.get('comments') loads nothing. If comments are loaded into the store separately, however, then post is able to correctly render comments:
// In the console (being mindful that `post.get('comments')` returns a promise)
const post = store.findRecord('post', '123');
post.get('comments').get('length'); // => 0
store.findAll('comment');
post.get('comments').get('length'); // => 12
What worked for me during an experiment (although I was heavily modifying ember-pouch within the adapter) was using post.get('comments').content.length but don't ask me why this is so and if it is supposed to be that way ...
EDIT:
It seems the problem is that the data is not loaded at that time. So probably something like post.get('comments').then(function() {this.debug(post.get('comments').length})) will work.

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.

Ember {embedded: 'always' } on model Vs Serializer

I was reading through Ember docs and some examples on working with Embedded Object like JSON in Ember.
I came across the EmbeddedRecordsMixin feature and saw that we can write code like below to tell it is embedded record.
import DS from 'ember-data';
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
author: { embedded: 'always' },
}
});
Qouting the below from Ember page
Note that this use of { embedded: 'always' } is unrelated to the { embedded: 'always' } that is defined as an option on DS.attr as part of defining a model while working with the ActiveModelSerializer. Nevertheless, using { embedded: 'always' } as an option to DS.attr is not a valid way to setup embedded records.
And i have also seen model written like this.
App.Child = DS.Model.extend({
name: DS.attr('string'),
toys: DS.hasMany('toy', {embedded: 'always'}),
});
Where child object has toys object embedded.
Going by the first example, can i write the child serailizer as below?
App.ChildSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
toys: {embedded: 'always'}
}
});
Can someone help me understand the difference between these two {embedded: 'always'} and what to use when?
Thanks
Short answer: yes you can and you should.
Well, as far as I know, ember (especialy ember-data) is build to work perfectly with a Rails backEnd.
Rails have a module called ActiveModelSerializer to serialize resources and their related attributes and relationships. Into this module, you can use an option embedded: 'always' to serialize the whole targeted relationship and not only the ids when your client ask for a ressource.
If you use it Rails side (server), you can handle it Ember side (client) by putting this option into your model if you want your ember-data store to handle it easily. It's just an 'echo' to this ActiveModelSerializer module functionality.
On the other side, if for example you create/update an object with many relationships, there is 2 ways to deal with it. The first is to first save object's relationships and then, on success, save the object itself. The second is to send it at once to your server with the option {embedded: 'always'} into your model's serializer, into the relationship you want to send at the same time (embedded) at the object itself.
Ember probably encourage to use this into the serializer, because putting this into model seems only related to a specific Rails option, and it's not straightforward at all. Moreover, putting this into the serializer fulfill this role, with or without ActiveModelSerializer.
Hope it's clear for you, if not, let me know so I can edit this.

Non-persistent attributes in EmberJS

Does anyone know of a way to specify for an Ember model an attribute which is not persisted?
Basically, we're loading some metadata related to each model and sending that data to Ember via the RESTAdapter within the model. This metadata can be changed in our app, but is done via using an AJAX call. Once the call succeeds, I want to be able to update this value within the model without Ember sticking its nose in this business by changing the model to the uncommitted and doing whatever it does with transactions behind the scenes.
I also have the problem that this metadata, which is not data from the model's database record, is passed by the RESTAdapter back to the server, which doesn't expect these values. I am using a RoR backend, so the server errors out trying to mass-assign protected attributes which aren't meant to be attributes at all. I know I can scrub the data received on the server, but I would prefer the client to be able to distinguish between persistent data and auxiliary data.
So, to the original question: is there any alternative to Ember-Data's DS.attr('...') which will specify a non-persistent attribute?
The other answers to this question work with Ember data versions up to 0.13, and no longer work.
For Ember data 1.0 beta 3 you can do:
App.ApplicationSerializer = DS.RESTSerializer.extend({
serializeAttribute: function(record, json, key, attribute) {
if (attribute.options.transient) {
return;
}
return this._super(record, json, key, attribute);
}
});
Now you can use transient attributes:
App.User = DS.Model.extend({
name: DS.attr('string', {transient: true})
});
These attributes won't be sent to the server when saving records.
When this PR get's merged it will be possible to flag properties as readOnly. But till then there are some workarounds to this, e.g. overriding your addAttributes method in the Adapter and deal with your special properties, here an example how this could look like:
Define your Model by adding the new option readOnly:
App.MyModel = DS.Model.extend({
myMetaProperty: DS.attr('metaProperty', {readOnly: true})
});
and then on the Adapter:
App.Serializer = DS.RESTSerializer.extend({
addAttributes: function(data, record) {
record.eachAttribute(function(name, attribute) {
if (!attribute.options.readOnly) {
this._addAttribute(data, record, name, attribute.type);
}
}, this);
}
});
what this does is to loop over the attributes of your model and when it find's an attribute with the readOnly flag set it skips the property.
I hope this mechanism works for your use case.
Following this answer, to prevent a field from being serialized, override the default serializer for your model:
In app/serializers/person.js:
export default DS.JSONSerializer.extend({
attrs: {
admin: { serialize: false }
}
});
See here for the source PR. This solution works in Ember Data 2, and should work in older versions as well.
Update
This answer is most likely out of date with the current releases of Ember Data. I wouldn't use anything in my answer.
I'm answering this question for reference, and because your comment indicated that the record remains isDirty, but here is my solution for read-only, non-persistent, non-dirty attributes.
Overriding the addAtributes method in your Serializer prevents readOnly attributes from being sent to the server, which is probably exactly what you want, but you need to extend (or reopen) your adapter to override the dirtyRecordsForAttributeChange to prevent the record from becoming dirty:
App.CustomAdapter = DS.RESTAdapter.extend({
dirtyRecordsForAttributeChange: function(dirtySet, record, attrName, newValue, oldValue) {
meta = record.constructor.metaForProperty(attrName);
if (meta && meta.options.readOnly) { return; }
this._super.apply(this, arguments);
};
});
Then you can use readOnly attributes like so:
App.User = DS.Model.extend({
name: DS.attr('string', {readOnly: true})
});
user = App.User.find(1); # => {id: 1, name: 'John Doe'}
user.set('name', 'Jane Doe'); #
user.get('isDirty') # => false
This setup is working for me.

failed to get embedded's object property using ember.js with ember-data

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/