Ember-Data:如何使用`DS.Adapter.findHasMany` [英] Ember-Data: How to use `DS.Adapter.findHasMany`

查看:90
本文介绍了Ember-Data:如何使用`DS.Adapter.findHasMany`的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请注意,此问题适用于Ember Data pre-1.0 beta版,通过URL加载关系的机制已经大大改变了1.0 beta!






我稍后问了一个更长的问题,但是自从图书馆发生变化以后,我会问一个简单的版本:



如何使用 DS.Adapter.findHasMany ?我正在构建一个适配器,我希望能够在关系属性的 get 中加载关系的内容,这个看起来像/做到这一点然而,查看Ember数据代码,我看不到这个函数可以被调用(如果需要,我可以在注释中解释)。



在我发送的JSON中,我的后端包含一个ids属性的数组,我使用的serializer不允许我在任何可以改变的地方挂钩,而且计算上也是昂贵的。 / p>

一次,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)?详细信息: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:应该调用一些serializer方法来获取这个?
store.loadMany(type,hash);

//添加ids来记录...
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 ),尝试重新填充它,并遵循不同的代码路径它将URL JSON散列中的字符串作为单个字符ID 的数组。具体来说,它将值从JSON传递给 Ember.EnumerableUtils.map ,可以将字符串的字符枚举为数组成员。




因此,我试图通过修补解决这个问题 DS.Model.hasManyDidChange ,发生这种情况,如下所示:

  //需要此功能移植hasManyDidChange函数... 
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; // DS.Model.clearHasMany()中的解决方法错误?
if(adapter&& adapter.findHasMany){
switch(reloadBehavior){
case'ignore':
// FIXME:应该用引用/ ids替换this._data,目前有一个字符串!
break;
case'force':
case'reset':
default:
this.clearHasMany(relationship);
cachedValue.set('isLoaded',false);
if(reloadBehavior =='force'|| Ember.meta(this).watching [relationship.key]){
//现在重新加载数据...
adapter.findHasMany( this.store,这个,关系,idsOrReferencesOrOpaque);
} else {
//强制getter代码重新运行下一次访问该属性...
删除Ember.meta(this).cache [relationship.key];
}
break;
}
} else if(idsOrReferencesOrOpaque!== undefined){
Ember.assert(您尝试加载许多记录,但没有适配器(+ type +),适配器);
Ember.assert(您尝试加载许多记录,但您的适配器不实现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 ...例如 child_object_ids ...甚至在这种情况下甚至不发送ID的情况下!这似乎表明,在用例列表中不使用ID数组不是很高。任何Ember数据开发人员都可以阅读本文:请支持此功能!



欢迎进一步了解这一点!

解决方案

而不是一个ids数组,有效载荷需要包含数组的其他东西。



在RESTAdapter的情况下,返回的JSON是这样的:

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

如果要手动/不同地处理关联,可以返回一个JSON代替: p>

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

由您的适配器取得指定网址的数据。



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


UPDATE

Note that this question applies to Ember Data pre-1.0 beta, the mechanism for loading relationships via URL has changed significantly post-1.0 beta!


I asked a much longer question a while back, but since the library has changed since then, I'll ask a much simpler version:

How do you use DS.Adapter.findHasMany? I am building an adapter and I want to be able to load the contents of a relationship on get of the relationship property, and this looks like the way to do it. However, looking at the Ember Data code, I don't see how this function can ever be called (I can explain in comments if needed).

There's not an easy way with my backend to include an array of ids in the property key in the JSON I send--the serializer I'm using doesn't allow me to hook in anywhere good to change that, and it would also be computationally expensive.

Once upon a time, the Ember Data front page showed an example of doing this "lazy loading"...Is this possible, or is this "Handle partially-loaded records" as listed on the Roadmap, and can't yet be done.?

I'm on API revision 11, master branch as of Jan 15.

Update

Okay, the following mostly works. First, I made the following findHasMany method in my adapter, based on the test case's implementation:

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);
        }
    });
}

Prerequisite for above is you have to have a well-working extractId method in your serializer, but the built-in one from RESTAdapter will probably do in most cases.

This works, but has one significant problem that I haven't yet really gotten around in any attempt at this lazy-loading approach: if the original record is reloaded from the server, everything goes to pot. The simplest use case that shows this is if you load a single record, then retrieve the hasMany, then later load all the parent records. For example:

var p = App.Post.find(1);
var comments = p.get('comments');
// ...later...
App.Post.find();

In the case of only the code above, what happens is that when Ember Data re-materializes the record it recognizes that there was already a value on the record (posts/1), tries to re-populate it, and follows a different code path which treats the URL string in the JSON hash as an array of single-character IDs. Specifically, it passes the value from the JSON to Ember.EnumerableUtils.map, which understandably enumerates the string's characters as array members.

Therefore, I tried to work around this by "patching" DS.Model.hasManyDidChange, where this occurs, like so:

// Need this function for transplanted hasManyDidChange function...
var map = Ember.EnumerableUtils.map;

DS.Model.reopen({

});

(^ Never mind, this was a really bad idea.)

Update 2

I found I had to do (at least) one more thing to solve the problem mentioned above, when a parent model is re-loaded from the server. The code path where the URL was getting split into single-characters was in DS.Model.reloadHasManys. So, I overrode this method with the following code:

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);
  }

});

With that addition, using URL-based hasManys is almost usable, with two main remaining problems:

First, inverse belongsTo relationships don't work correctly--you'll have to remove them all. This appears to be a problem with the way RecordArrays are done using ArrayProxies, but it's complicated. When the parent record gets reloaded, both relationships get processed for "removal", so while a loop is iterating over the array, the belongsTo disassociation code removes items from the array at the same time and then the loop freaks out because it tries to access an index that is no longer there. I haven't figured this one out yet, and it's tough.

Second, it's often inefficient--I end up reloading the hasMany from the server too often...but at least maybe I can work around this by sending a few cache headers on the server side.

Anyone trying to use the solutions in this question, I suggest you add the code above to your app, it may get you somewhere finally. But this really needs to get fixed in Ember Data for it to work right, I think.

I'm hoping this gets better supported eventually. On the one hand, the JSONAPI direction they're going explicitly says that this kind of thing is part of the spec. But on the other hand, Ember Data 0.13 (or rev 12?) changed the default serialized format so that if you want to do this, your URL has to be in a JSON property called *_ids... e.g. child_object_ids ... when it's not even IDs you're sending in this case! This seems to suggest that not using an array of IDs is not high on their list of use-cases. Any Ember Data devs reading this: PLEASE SUPPORT THIS FEATURE!

Welcome further thoughts on this!

解决方案

Instead of an array of ids, the payload needs to contain "something else" than an array.

In the case of the RESTAdapter, the returned JSON is like that:

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

If you want to handle manually/differently the association, you can return a JSON like that instead:

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

It's up to your adapter then to fetch the data from the specified URL.

See the associated test: https://github.com/emberjs/data/blob/master/packages/ember-data/tests/integration/has_many_test.js#L112

这篇关于Ember-Data:如何使用`DS.Adapter.findHasMany`的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆