2

我的适配器用于findHasMany加载hasMany关系的子记录。我的适配器方法findHasMany直接基于. 它取回点播的内容,最终做了以下两个操作:findHasManyhasMany

store.loadMany(type, hashes);
// ...
store.loadHasMany(record, relationship.key, ids);

(如果问题存在,完整的代码findHasMany如下,但我不这么认为。)

真正奇怪的行为是:似乎在某个地方loadHasMany(或在一些后续的异步过程中)只有第一个和最后一个子记录得到它们的逆belongsTo属性 set,即使所有的子记录都被添加到hasMany一边。即,如果posts/1有 10 条评论,这就是我在加载完所有内容后得到的:

var post = App.Posts.find('1');
post.get('comments').objectAt(0).get('post'); // <App.Post:ember123:1>
post.get('comments').objectAt(1).get('post'); // null
post.get('comments').objectAt(2).get('post'); // null
// ...
post.get('comments').objectAt(8).get('post'); // null
post.get('comments').objectAt(9).get('post'); // <App.Post:ember123:1>

我的适配器是 的子类DS.RESTAdapter,我不认为我在我的适配器或序列化程序中重载了任何会导致这种行为的东西。

有没有人见过这样的东西?这很奇怪,尽管有人可能知道为什么会这样。

额外的

UsingfindHasMany让我hasMany仅在访问属性时才加载内容(在我的情况下很有价值,因为计算 ID 数组会很昂贵)。所以说我有经典的帖子/评论示例模型,服务器返回帖子/1:

{
  post: {
    id: 1,
    text: "Linkbait!"
    comments: "/posts/1/comments"
  }
}

然后我的适配器可以/posts/1/comments按需检索,如下所示:

{
  comments: [
    {
      id: 201,
      text: "Nuh uh"
    },
    {
      id: 202,
      text: "Yeah huh"
    },
    {
      id: 203,
      text: "Nazi Germany"
    }
  ]
}

这是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);
    var query = relationship.options.query ? relationship.options.query(record) : {};

    this.ajax(url, "GET", {
        data: query,
        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);
        }
    });
}
4

1 回答 1

1

解决方案

DS.RelationshipChange.getByReference通过将以下代码插入您的应用程序来覆盖该方法:

DS.RelationshipChange.prototype.getByReference = function(reference) {
  var store = this.store;

  // return null or undefined if the original reference was null or undefined
  if (!reference) { return reference; }

  if (reference.record) {
    return reference.record;
  }

  return store.materializeRecord(reference);
};

是的,这是覆盖 Ember Data 中的私有内部方法。是的,任何更新都可能随时中断。我很确定这是 Ember Data 中的一个错误,但我不能 100% 确定这是正确的解决方案。但它确实解决了这个问题,可能还有其他与关系相关的问题。

自 2013 年 4 月 29 日起,此修复程序旨在应用于 Ember 数据主服务器。

原因

DS.Store.loadHasMany调用DS.Model.hasManyDidChange,它检索所有子记录的引用,然后set将 hasMany'scontent指向引用数组。这启动了一个观察者链,最终调用DS.ManyArray.arrayContentDidChange,其中第一行是this._super.apply(this, arguments);调用超类方法Ember.Array.arrayContentDidChangeEmber.Array方法包括缓存数组中的第一个和最后一个对象并objectAt仅调用这两个数组成员的优化。所以有一部分是挑出第一条和最后一条记录。

接下来,由于DS.RecordArray实现了一个objectAtContent方法(来自Ember.ArrayProxy),objectAtContent实现调用DS.Store.recordForReference,而后者又调用DS.Store.materializeRecord. 最后一个函数将record属性添加到作为副作用传入的引用中。

现在我们来看看我认为是一个错误。在DS.ManyArray.arrayContentDidChange中,调用超类方法后,循环遍历所有新引用,并创建一个DS.RelationshipChangeAdd封装了所有者和子记录引用的实例。但循环内的第一行是:

var reference = get(this, 'content').objectAt(i);

与上面对第一条和最后一条记录发生的情况不同,这objectAt直接调用Ember.NativeArray并绕过了ArrayProxy包括objectAtContent钩子在内的方法,这意味着——它在引用对象上DS.Store.materializeRecord添加了record属性——可能从未在某些引用上被调用过。

接下来,在循环中创建的关系更改会立即(在同一个运行循环中)应用到这个调用树:DS.RelationshipChangeAdd.sync-> DS.RelationshipChange.getFirstRecord-> DS.RelationshipChange.getByReference最后一种方法期望引用对象具有record属性。但是,record出于上述原因,该属性仅设置在第一个和最后一个参考对象上。因此,对于除第一条和最后一条记录之外的所有记录,都无法建立关系,因为它无权访问子记录对象!

DS.Store.materializeRecord每当record引用中不存在该属性时,上述修复调用。函数中的最后一行是唯一添加的内容。一方面,它的初衷似乎var store = this.store;是这样的:原文中的那一行声明了一个在函数中没有使用的变量,那么它有什么用呢?此外,如果没有添加的行,该函数并不总是返回一个值,这对于预期这样做的函数来说有点不寻常。另一方面,在某些情况下,这可能会导致大规模物化,而这是不可取的(但是,在某些情况下,如果没有它,关系似乎就无法工作)。

可能相关

我提到的“观察者链”有点奇怪。启动事件是content在 a 上设置属性DS.ManyArray,它扩展Ember.ArrayProxy了 - 因此该content属性具有依赖属性arrangedContent。重要的是,观察者在被arrangedContent执行之前content被执行(参见 参考资料Ember.propertyDidChange)。但是,Ember.ArrayProxy.arrangedContentArrayDidChange简单调用的默认实现Ember.Array.arrayContentDidChange,它DS.ManyArray实现了!关键是,这看起来像是一些代码以意想不到的顺序执行的秘诀。也就是说,我认为Ember.ManyArray.arrayContentDidChange可能会比预期的更早执行。如果是这种情况,上面提到的代码期望record已经存在于所有引用上的属性可能已经合理地期待这一点,因为直接在content属性上的观察者之一可能会调用DS.Store.materializeRecord每个引用。但我还没有深入挖掘,以确定这是否属实。

于 2013-05-01T11:20:45.427 回答