2

私のアダプターは、リレーションシップfindHasManyの子レコードをロードするために使用します。hasMany私のアダプタ メソッドは、 のテスト ケースにfindHasMany直接基づいています。オンデマンドのコンテンツを取得し、最終的に次の 2 つの操作を実行します。findHasManyhasMany

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

(問題がある場合に備えて、の完全なコードをfindHasMany以下に示しますが、そうではないと思います。)

本当に奇妙な動作は次のとおりです。すべての子レコードが側に追加されているにもかかわらず、どこかloadHasMany(または後続の非同期プロセス) のどこかで、最初と最後の子レコードのみが逆のbelongsToプロパティ セットhasManyを取得しているようです。つまり、posts/110 個のコメントがある場合、すべてが読み込まれた後、次のようになります。

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 の配列を計算するとコストがかかるため、私の場合は価値があります)。たとえば、古典的な投稿/コメントのサンプル モデルがあるとします。サーバーは posts/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 Data master に適用されるように設計されています。

理由

DS.Store.loadHasManyを呼び出しDS.Model.hasManyDidChange、すべての子レコードの参照を取得してからset、hasManycontentを参照の配列に渡します。これにより一連のオブザーバーが開始され、最終的に が呼び出されます。DS.ManyArray.arrayContentDidChange最初の行はthis._super.apply(this, arguments);であり、スーパークラス メソッドを呼び出しEmber.Array.arrayContentDidChangeます。このEmber.Arrayメソッドには、配列内の最初と最後のオブジェクトをキャッシュし、objectAtこれら 2 つの配列メンバーのみを呼び出す最適化が含まれています。つまり、最初と最後のレコードを選び出す部分があります。

次に、はメソッド (from ) をDS.RecordArray実装しているため、実装は を呼び出し、次に を呼び出します。この最後の関数は、副作用として渡される参照にプロパティを追加します。objectAtContentEmber.ArrayProxyobjectAtContentDS.Store.recordForReferenceDS.Store.materializeRecordrecord

今、私はバグだと思うものにたどり着きました。ではDS.ManyArray.arrayContentDidChange、スーパークラス メソッドを呼び出した後、すべての新しい参照をループしてDS.RelationshipChangeAdd、所有者と子レコードの参照をカプセル化するインスタンスを作成します。しかし、ループ内の最初の行は次のとおりです。

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

上記の最初と最後のレコードとは異なり、これは を直接呼び出しobjectAtフック含むメソッドをEmber.NativeArrayバイパスします。ArrayProxyobjectAtContentDS.Store.materializeRecordrecord

次に、ループで作成された関係の変更は、直後に (同じ実行ループで) この呼び出しツリーで適用されます: DS.RelationshipChangeAdd.sync-> DS.RelationshipChange.getFirstRecord-> DS.RelationshipChange.getByReference. この最後のメソッドは、参照オブジェクトがrecordプロパティを持つことを想定しています。ただし、record上で説明した理由により、プロパティは最初と最後の参照オブジェクトにのみ設定されます。したがって、最初と最後のレコードを除くすべてのレコードでは、子レコード オブジェクトへのアクセス権がないため、関係を確立できません!

上記の修正は、プロパティが参照に存在しない場合はDS.Store.materializeRecord常に呼び出されます。record関数の最後の行だけが追加されます。一方で、これは当初の意図のように見えますvar store = this.store;: 元の行は、関数で他の方法では使用されない変数を宣言していますが、それは何のためにあるのでしょうか? また、追加された行がないと、関数は常に値を返すとは限りません。これは、そうすることが期待される関数としては少し珍しいことです。一方、これは場合によっては、それが望ましくない場合に大量の実体化につながる可能性があります (ただし、場合によっては、それがないと関係が機能しないようです)。

おそらく関連している

私が言及した「オブザーバーの連鎖」は、少し奇妙な道をたどります。開始イベントは でcontentプロパティを設定していましたDS.ManyArray。これは拡張されています。Ember.ArrayProxyしたがって、contentプロパティには従属プロパティがありますarrangedContent。重要なことに、オブザーバー on は、オブザーバー onがarrangedContent実行される前にcontent実行されます (「 」を参照Ember.propertyDidChange)。ただし、実装するだけのデフォルトの実装はをEmber.ArrayProxy.arrangedContentArrayDidChange呼び出します! 要するに、これは一部のコードが意図しない順序で実行されるレシピのように見えます。つまり、予想よりも早く実行される可能性があると思います。この場合、上記のコードはEmber.Array.arrayContentDidChangeDS.ManyArrayEmber.ManyArray.arrayContentDidChangerecordすべての参照に既に存在するプロパティは、これを合理的に期待していた可能性があります。これは、プロパティを直接監視しているオブザーバーの 1 つが各参照でcontent呼び出す可能性があるためです。DS.Store.materializeRecordしかし、これが本当かどうかを調べるほど深く掘り下げていません。

于 2013-05-01T11:20:45.427 に答える