请注意,此问题适用于Ember Data pre-1.0 beta,通过URL加载关系的机制在1.0 beta后发生了显着变化!
我前一段时间问了一个更长的问题,但自从那时候图书馆发生了变化,我会问一个更简单的版本:
您如何使用DS.Adapter.findHasMany
?我正在构建一个适配器,我希望能够在关系属性的get
上加载关系的内容,而这个看起来像一样。但是,看看Ember数据代码,我看不出如何调用这个函数(如果需要,我可以在评论中解释)。
我的后端没有一种简单的方法可以在我发送的JSON中的属性键中包含一个id数组 - 我正在使用的序列化器不允许我在任何好的地方挂钩以改变它,并且它也是计算上昂贵的。
曾几何时,Ember Data首页显示了这样做“延迟加载”的一个例子......这是可能的,还是路径图中列出的“处理部分加载的记录”,而不是还没完成。?
我正在使用API修订版11,即1月15日的主分支。
好的,以下内容大部分都有效。首先,我根据测试用例的实现在我的适配器中创建了以下findHasMany
方法:
findHasMany: function(store, record, relationship, details) {
var type = relationship.type;
var root = this.rootForType(type);
var url = (typeof(details) == 'string' || details instanceof String) ? details : this.buildURL(root);
this.ajax(url, "GET", {
success: function(json) {
var serializer = this.get('serializer');
var pluralRoot = serializer.pluralize(root);
var hashes = json[pluralRoot]; //FIXME: Should call some serializer method to get this?
store.loadMany(type, hashes);
// add ids to record...
var ids = [];
var len = hashes.length;
for(var i = 0; i < len; i++){
ids.push(serializer.extractId(type, hashes[i]));
}
store.loadHasMany(record, relationship.key, ids);
}
});
}
上面的先决条件是您必须在序列化程序中使用运行良好的extractId
方法,但在大多数情况下RESTAdapter
内置的方法可能会这样做。
这个有效,但有一个重大问题,我在这种延迟加载方法的任何尝试中都没有真正解决过:如果原始记录是从服务器重新加载的,一切都进入底池。显示这一点的最简单的用例是,如果您加载单个记录,然后检索hasMany
,然后再加载所有父记录。例如:
var p = App.Post.find(1);
var comments = p.get('comments');
// ...later...
App.Post.find();
在仅上述代码的情况下,当Ember Data重新实现记录时,它会识别记录中已存在值(posts/1
),尝试重新填充它,并遵循不同的代码路径,它将JSON哈希中的URL字符串视为单字符ID数组。具体来说,它将值从JSON传递给Ember.EnumerableUtils.map
,这可以理解地将字符串的字符枚举为数组成员。
<击>
因此,我尝试通过“修补”DS.Model.hasManyDidChange
来解决这个问题,如下所示:
// Need this function for transplanted hasManyDidChange function...
var map = Ember.EnumerableUtils.map;
DS.Model.reopen({
});
(^没关系,这是一个非常糟糕的主意。)
当从服务器重新加载父模型时,我发现我必须做(至少)一件事来解决上面提到的问题。将URL拆分为单个字符的代码路径位于DS.Model.reloadHasManys
中。所以,我用以下代码覆盖了这个方法:
DS.Model.reopen({
reloadHasManys: function() {
var relationships = get(this.constructor, 'relationshipsByName');
this.updateRecordArraysLater();
relationships.forEach(function(name, relationship) {
if (relationship.kind === 'hasMany') {
// BEGIN FIX FOR OPAQUE HASMANY DATA
var cachedValue = this.cacheFor(relationship.key);
var idsOrReferencesOrOpaque = this._data.hasMany[relationship.key] || [];
if(cachedValue && !Ember.isArray(idsOrReferencesOrOpaque)){
var adapter = this.store.adapterForType(relationship.type);
var reloadBehavior = relationship.options.reloadBehavior;
relationship.name = relationship.name || relationship.key; // workaround bug in DS.Model.clearHasMany()?
if (adapter && adapter.findHasMany) {
switch (reloadBehavior) {
case 'ignore':
//FIXME: Should probably replace this._data with references/ids, currently has a string!
break;
case 'force':
case 'reset':
default:
this.clearHasMany(relationship);
cachedValue.set('isLoaded', false);
if (reloadBehavior == 'force' || Ember.meta(this).watching[relationship.key]) {
// reload the data now...
adapter.findHasMany(this.store, this, relationship, idsOrReferencesOrOpaque);
} else {
// force getter code to rerun next time the property is accessed...
delete Ember.meta(this).cache[relationship.key];
}
break;
}
} else if (idsOrReferencesOrOpaque !== undefined) {
Ember.assert("You tried to load many records but you have no adapter (for " + type + ")", adapter);
Ember.assert("You tried to load many records but your adapter does not implement `findHasMany`", adapter.findHasMany);
}
} else {
this.hasManyDidChange(relationship.key);
}
//- this.hasManyDidChange(relationship.key);
// END FIX FOR OPAQUE HASMANY DATA
}
}, this);
}
});
通过这种添加,使用基于URL的hasManys 几乎可用,还有两个主要问题:
首先,反belongsTo
关系无法正常工作 - 您必须将它们全部删除。这似乎是使用ArrayProxies完成RecordArrays的方式的一个问题,但它很复杂。当父记录被重新加载时,两个关系都被处理为“删除”,所以当循环遍历数组时,belongsTo解除关联代码同时从数组中删除项目,然后循环因为尝试访问而变得怪异一个不再存在的索引。我还没想出这个,这很难。
其次,它通常效率低下 - 我最终经常从服务器重新加载hasMany ......但至少我可以通过在服务器端发送一些缓存头来解决这个问题。
任何人都试图在这个问题中使用这些解决方案,我建议你将上面的代码添加到你的应用程序中,它可能会让你最终到达某个地方。但是,我认为这确实需要在Ember Data中修复才能正常工作。
我希望最终得到更好的支持。一方面,他们明确指出的JSONAPI方向说这种事情是规范的一部分。但另一方面,Ember Data 0.13(或rev 12?)更改了默认的序列化格式,因此如果您想这样做,您的URL必须位于名为*_ids
的JSON属性中... child_object_ids
...如果在这种情况下你甚至不是你发送的ID!这似乎表明,在用例列表中不使用ID数组并不高。任何Ember Data开发者都会这样说:请支持这个功能!
欢迎进一步思考!
答案 0 :(得分:3)
有效载荷需要包含“别的东西”而不是数组。
而不是一组id对于RESTAdapter,返回的JSON就是:
{blog: {id: 1, comments: [1, 2, 3]}
如果您想手动/不同地处理关联,您可以返回类似的JSON:
{blog: {id: 1, comments: "/posts/1/comments"}
然后由您的适配器来获取指定URL中的数据。
答案 1 :(得分:1)
我很高兴找到这篇文章,帮助了我。这是我的版本,基于当前的ember-data和您的代码。
findHasMany: function(store, record, relationship, details) {
var adapter = this;
var serializer = this.get('serializer');
var type = relationship.type;
var root = this.rootForType(type);
var url = (typeof(details) == 'string' || details instanceof String) ? details : this.buildURL(root);
return this.ajax(url, "GET", {}).then(function(json) {
adapter.didFindMany(store, type, json);
var list = $.map(json[relationship.key], function(o){ return serializer.extractId(type, o);});
store.loadHasMany(record, relationship.key, list);
}).then(null, $.rejectionHandler);
},
对于重新加载问题,我根据我在另一个地方找到的代码,在序列化器内部进行了覆盖:
materializeHasMany: function(name, record, hash, relationship) {
var type = record.constructor,
key = this._keyForHasMany(type, relationship.key),
cache = record.cacheFor('data');
if(cache) {
var hasMany = cache.hasMany[relationship.key];
if (typeof(hasMany) == 'object' || hasMany instanceof Object) {
record.materializeHasMany(name, hasMany);
return;
}
}
var value = this.extractHasMany(type, hash, key);
record.materializeHasMany(name, value);
}
我还在努力搞清楚分页,因为我正在使用的一些系列需要它。
答案 2 :(得分:0)
我更接近于使用修订版13并基于sfossen的findHasMany实现。
对于拥有hasMany关系'blogPosts'的Ember模特'作者',我的其余api看起来像'/ api / authors /:author_id / blog_posts'。当查询id为11的作者的其余api时,blog_posts字段为'/ authors / 11 / blog_posts'。
我现在看到服务器返回相关的博客帖子,但是Ember仍然会抛出一个模糊的错误,它在呈现页面时无法从未定义的模型对象中读取“id”。所以我还没到那里,但至少从其他服务中正确地请求了相关数据。
我的完整适配器:
App.Adapter = DS.RESTAdapter.extend({
url: 'http://localhost:3000',
namespace: 'api',
serializer: DS.RESTSerializer.extend({
keyForHasMany: function(type, name) {
return Ember.String.underscore(name);
},
extractHasMany: function(record, json, relationship) {
var relationShip = relationship + '_path';
return { url : json[relationShip] }
}
}),
findHasMany: function(store, record, relationship, details) {
var type = relationship.type;
var root = this.rootForType(type);
var url = this.url + '/' + this.namespace + details.url;
var serializer = this.get('serializer');
return this.ajax(url, "GET", {}).then(
function(json) {
var relationship_key = Ember.String.underscore(relationship.key);
store.loadMany(type, json[relationship_key]);
var list = $.map(json[relationship_key], function(o){
return serializer.extractId(type, o);}
);
store.loadHasMany(record, relationship.key, list);
}).then(null, $.rejectionHandler);
}
});
答案 3 :(得分:0)
这是我的解决方案,但它是在Ember-data 0.14上,所以即使我们仍在这个代码基础上,世界仍然继续前进:
findHasMany: function(store, record, relationship, details) {
if(relationship.key !== 'activities') {
return;
}
var type = relationship.type,
root = this.rootForType(type),
url = this.url + details.url,
self = this;
this.ajax(url, "GET", {
data: {page: 1}
}).then(function(json) {
var data = record.get('data'),
ids = [],
references = json[relationship.key];
ids = references.map(function(ref){
return ref.id;
});
data[relationship.key] = ids;
record.set('data', data);
self.didFindMany(store, type, json);
record.suspendRelationshipObservers(function() {
record.hasManyDidChange(relationship.key);
});
}).then(null, DS.rejectionHandler);
},
我发现使用为我工作的ID替换数据。