Reputation: 2003
I have a problem regarding Ember data and Mongodb embedded objects. Here's my model :
App.Contact = App.Entity.extend({
name : DS.attr('string'),
firstname : DS.attr('string'),
additional_names : DS.attr('string'),
civility : DS.attr('string'),
birthday : DS.attr('date'),
organization : DS.belongsTo('App.Organization'),
role : DS.attr('string'),
photo_source : DS.attr('string'),
photo_uri : DS.attr('string'),
gravatar_mail : DS.attr('string'),
addresses : DS.hasMany('App.Address', { embedded: true }),
emails : DS.hasMany('App.Email', { embedded: true }),
phones : DS.hasMany('App.Phone', { embedded: true })
});
Now I'm fetching a contact through the API: (GET /app/api/v1/contact/4f86c4774ab63c2417000001/) here's what I get :
{
"additional_names": null,
"addresses": [],
"birthday": null,
"civility": null,
"emails": [
{
"email": "[email protected]",
"label": null,
"resource_uri": "/app/api/v1/contact/4f86c4774ab63c2417000001/emails/0/",
"type": "HOME"
}
],
"firstname": "Alexandre",
"gravatar_mail": null,
"groups": [],
"id": "4f86c4774ab63c2417000001",
"name": "Simoui",
"organization": null,
"phones": [],
"photo_source": null,
"photo_uri": "/static/img/nophoto.png",
"resource_uri": "/app/api/v1/contact/4f86c4774ab63c2417000001/",
"role": null
}
My "root" object has an id but the embedded object "emails" hasn't. Because in mongodb, id is not set on subdocuments, but only on root document.
This way ember-data see that "email" object hasn't id and then it try to get the full object through the API. For example : GET /app/api/v1/email/set// 404 (NOT FOUND)
To be sure it was the wright problem I tried to return Mongodb subdocuments with a fake ID field. Like : (see difference on email object)
{
"additional_names": null,
"addresses": [],
"birthday": null,
"civility": null,
"emails": [
{
"id": 431,
"email": "[email protected]",
"label": null,
"resource_uri": "/app/api/v1/contact/4f86c4774ab63c2417000001/emails/0/",
"type": "HOME"
}
],
"firstname": "Alexandre",
"gravatar_mail": null,
"groups": [],
"id": "4f86c4774ab63c2417000001",
"name": "Simoui",
"organization": null,
"phones": [],
"photo_source": null,
"photo_uri": "/static/img/nophoto.png",
"resource_uri": "/app/api/v1/contact/4f86c4774ab63c2417000001/",
"role": null
}
Then I got no problem everything is fine. So my question is: Is there a way to fix it?
Upvotes: 3
Views: 1182
Reputation: 2576
I have begun experimenting with a workaround. I only use embedded objects without IDs on a read-only basis, so I haven't tested saving, creation, and proper model state management (isDirty, etc.)...but so far this has solved my similar problem.
Unfortunately I needed to modify the ember-data source. See my fork of ember-data.
The extent of my change involved modifying the hasAssociation()
function of DS.hasMany
. What I did was inject fake IDs the first time the computed property that accesses the association returns. I keep a client-side ID counter, injectedIdCounter
in the closure that defines hasAssociation()
as well as a function to fetch an ID and update the counter. I then define a new option, trueEmbedded
which is an embedded sub-document with no ID. Each time get()
is called on the association, the elements are checked for IDs and if the id doesn't exist one is injected. If IDs have been added, then set()
needs to be called so the modified association is stored back on the parent object. This solved my problem at least. Here's my code.
var injectedIdCounter = 1;
var getInjectedId = function() {
return injectedIdCounter++;
};
var hasAssociation = function(type, options) {
options = options || {};
var embedded = options.embedded,
findRecord = embedded ? embeddedFindRecord : referencedFindRecord;
var meta = { type: type, isAssociation: true, options: options, kind: 'hasMany' };
return Ember.computed(function(key, value) {
var data = get(this, 'data'),
store = get(this, 'store'),
ids, id, association;
if (typeof type === 'string') {
type = get(this, type, false) || get(window, type);
}
key = options.key || get(this, 'namingConvention').keyToJSONKey(key);
if (options.trueEmbedded) {
association = get(data, key);
var injectedIdCount = 0;
association.forEach(function(item) {
if (Ember.none(item.id)) {
item.id = getInjectedId();
injectedIdCount++;
}
});
if (injectedIdCount > 0) {
set(data, key, association);
}
ids = embeddedFindRecord(store, type, data, key);
} else {
ids = findRecord(store, type, data, key);
}
association = store.findMany(type, ids || []);
set(association, 'parentRecord', this);
return association;
}).property().cacheable().meta(meta);
};
You would think that an embedded document doesn't need an ID, but the way ember-data first fetches all the IDs of the objects and then the objects themselves, even for an embedded association, means that some messy solution like this is required.
Hopefully this will be fixed in a future release of Ember.
Cheers, Kevin
Upvotes: 3