belongsTo仅设置在hasMany的第一个和最后一个成员上

时间:2013-04-29 12:35:47

标签: ember-data

我的适配器使用findHasMany加载hasMany关系的子记录。 我的findHasMany适配器方法直接基于test case for findHasMany。它根据需要检索hasMany的内容,并最终执行以下两个操作:

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

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

真正奇怪的行为是:它似乎在loadHasMany(或某些后续异步过程)中的某个地方只有第一个和最后一个子记录获得其反belongsTo属性集,即使所有子记录都添加到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的子类,我认为我的适配器或序列化程序中的任何内容都不会导致这种行为。

以前有人见过这样的事吗?虽然有人可能知道为什么会发生这种情况,但这很奇怪。

附加

使用findHasMany只允许我在访问属性时加载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);
        }
    });
}

1 个答案:

答案 0 :(得分: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 Data master。

的原因

DS.Store.loadHasMany调用DS.Model.hasManyDidChange,它会检索所有子记录的引用,然后set将hasMany的content引用到引用数组。这将启动一系列观察者。最终调用DS.ManyArray.arrayContentDidChange,其中第一行是this._super.apply(this, arguments);,调用超类方法Ember.Array.arrayContentDidChange Ember.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 - &gt; DS.RelationshipChange.getByReference - &gt; record最后一个方法希望引用对象具有record属性。但是,DS.Store.materializeRecord属性仅在第一个和最后一个引用对象上设置,原因如上所述。因此,除了第一个和最后一个记录之外的所有记录都无法建立关系,因为它无法访问子记录对象!

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

可能相关

我提到的“观察者链”采取了一些奇怪的道路。发起事件是在DS.ManyArray上设置Ember.ArrayProxy属性,扩展content - 因此arrangedContent属性具有从属属性arrangedContent。重要的是,content上的观察者在执行Ember.propertyDidChange上的观察员之前执行(请参阅Ember.ArrayProxy.arrangedContentArrayDidChange)。但是,Ember.Array.arrayContentDidChange的默认实现只调用DS.ManyArrayEmber.ManyArray.arrayContentDidChange实现! 关键是,这看起来像某些代码以非预期的顺序执行的配方。也就是说,我认为record可能比预期更早执行。如果是这种情况,那么期望content属性已经存在于所有引用上的上述代码可能已经合理地期望这一点,因为直接在DS.Store.materializeRecord属性上的观察者之一可能会调用{{ 1}}在每个引用上。但我没有深入挖掘,以确定这是否属实。