10

更新

请注意,这个问题适用于 Ember Data pre-1.0 beta,通过 URL 加载关系的机制在 1.0 beta 后发生了显着变化!


前段时间我问了一个更长的问题,但由于库从那时起发生了变化,我会问一个更简单的版本:

你怎么用DS.Adapter.findHasMany?我正在构建一个适配器,我希望能够get在关系属性上加载关系的内容,这看起来像是这样做的方式。但是,查看 Ember Data 代码,我看不出如何调用此函数(如果需要,我可以在注释中解释)。

我的后端没有一种简单的方法可以在我发送的 JSON 中的属性键中包含一个 id 数组——我正在使用的序列化程序不允许我挂钩任何可以改变它的地方,而且它也将是计算成本高。

曾几何时,Ember Data 的首页显示了一个执行此“延迟加载”的示例……这可能吗,或者路线图上列出的“处理部分加载的记录”,目前还不能完成.?

截至 1 月 15 日,我正在使用 API 修订版 11,主分支。

更新

好的,以下主要工作。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({

});

(^ 没关系,这是一个非常糟糕的主意。)

更新 2

当从服务器重新加载父模型时,我发现我必须(至少)再做一件事来解决上述问题。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 必须位于名为...的 JSON 属性中*_ids......例如child_object_ids......在这种情况下您要发送的 ID!这似乎表明不使用 ID 数组在他们的用例列表中并不高。任何阅读此内容的 Ember Data 开发人员:请支持此功能!

欢迎对此提出进一步的想法!

4

4 回答 4

3

有效载荷需要包含“其他”而不是数组,而不是一个 id 数组。

对于 RESTAdapter,返回的 JSON 是这样的:

{blog: {id: 1, comments: [1, 2, 3]}

如果你想手动/不同地处理关联,你可以返回一个这样的 JSON:

{blog: {id: 1, comments: "/posts/1/comments"}

然后由您的适配器从指定的 URL 获取数据。

查看相关测试:https ://github.com/emberjs/data/blob/master/packages/ember-data/tests/integration/has_many_test.js#L112

于 2013-04-14T15:57:06.567 回答
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);
}

我仍在努力解决分页问题,​​因为我正在使用的一些收藏品需要它。

于 2013-06-13T02:57:05.277 回答
0

我更接近于让它与第 13 版一起使用,并以 sfossen 的 findHasMany 实现为基础。

对于具有 hasMany 关系“blogPosts”的 Ember 模型“Author”,我的其余 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);
    }
});
于 2013-06-23T14:02:59.587 回答
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 替换数据对我有用。

于 2014-02-12T06:36:55.117 回答