10

アップデート

この質問は、1.0 ベータ以前の Ember Data に適用されることに注意してください。URL を介して関係をロードするメカニズムは、1.0 ベータ以降で大幅に変更されました!


しばらく前にもっと長い質問をしましたが、その後ライブラリが変更されたため、もっと簡単なバージョンを質問します。

どのように使用します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

これは機能しますが、この遅延読み込みアプローチの試みでまだ実際に回避できていない重大な問題が 1 つあります。元のレコードがサーバーからリロードされると、すべてが無駄になります。これを示す最も単純な使用例は、1 つのレコードをロードしてから を取得しhasMany、後ですべての親レコードをロードする場合です。例えば:

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

上記のコードのみの場合、何が起こるかというと、Ember Data がレコードを再実体化するときに、レコードに既に値があったことを認識し ( posts/1)、それを再入力しようとし、別のコード パスをたどります。 1 文字の ID の配列としての JSON ハッシュの URL 文字列。具体的には、JSON から に値を渡しますEmber.EnumerableUtils.map。これは当然のことながら、文字列の文字を配列メンバーとして列挙します。

したがって、次のように「パッチを当てる」ことでこれを回避しようとしましDS.Model.hasManyDidChangeた。

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

DS.Model.reopen({

});

(^気にしないでください、これは本当に悪い考えでした。)

更新 2

親モデルがサーバーから再ロードされたときに、上記の問題を解決するために (少なくとも) もう 1 つのことをしなければならないことがわかりました。URL が 1 文字に分割されていたコード パスは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 の使用はほとんど使用可能になりますが、2 つの主な問題が残ります。

まず、逆belongsTo関係は正しく機能しません。すべて削除する必要があります。これは、ArrayProxies を使用して RecordArray を実行する方法に問題があるように見えますが、複雑です。親レコードがリロードされると、両方の関係が「削除」のために処理されるため、ループが配列を反復処理している間、 belongsTo 関連付け解除コードが同時に配列から項目を削除し、ループがフリークアウトします。これは、アクセスしようとするためです。もはや存在しないインデックス。私はまだこれを理解していません、そしてそれは難しいです。

2 つ目は、しばしば非効率的です。サーバーから hasMany を頻繁にリロードすることになりますが、少なくとも、サーバー側でいくつかのキャッシュ ヘッダーを送信することで、これを回避できる可能性があります。

この質問のソリューションを使用しようとしている人は、上記のコードをアプリに追加することをお勧めします。最終的にどこかに到達する可能性があります。しかし、これを正しく機能させるには、Ember Data で修正する必要があると思います。

これが最終的によりよくサポートされることを願っています。一方では、彼らが進んでいるJSONAPIの方向性は、この種のことは仕様の一部であると明示的に述べています。しかし一方で、Ember Data 0.13 (または rev 12?) はデフォルトのシリアル化された形式を変更したため、これを行うには、URL が*_ids...と呼ばれる JSON プロパティにある必要があります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

これが私の解決策ですが、それは 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 に答える
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 に答える