web-dev-qa-db-ja.com

EmberJSで単一のルートで複数のモデルを使用する方法/ Ember Data?

ドキュメントを読むと、次のようにモデルをルートに割り当てる必要がある(または割り当てる必要がある)ように見えます。

App.PostRoute = Ember.Route.extend({
    model: function() {
        return App.Post.find();
    }
});

特定のルートで複数のオブジェクトを使用する必要がある場合はどうなりますか?つまり、投稿、コメント、ユーザーですか?それらをロードするルートを指示するにはどうすればよいですか?

85
Anonymous

最後の更新は永遠に:これを更新し続けることはできません。したがって、これは非推奨であり、おそらくこのようになります。ここに、より良い、より最新のスレッドがあります EmberJS:複数のモデルを同じルートにロードする方法?

更新:私の元の答えでは、モデル定義でembedded: trueを使用するように言った。それは間違っています。リビジョン12では、Ember-Dataは、外部キーが接尾辞( link )で定義されることを想定しています。単一レコードの場合は_id、コレクションの場合は_idsです。次のようなもの:

{
    id: 1,
    title: 'string',
    body: 'string string string string...',
    author_id: 1,
    comment_ids: [1, 2, 3, 6],
    tag_ids: [3,4]
}

私はフィドルを更新しました。何か変更があった場合、またはこの回答で提供されたコードでさらに問題が見つかった場合は、再度更新します。


関連モデルの回答:

あなたが説明しているシナリオでは、モデル間で 関連付け に依存します embedded: trueを設定) PostモデルにDS.hasMany関連付けを定義し、両方のCommentDS.belongsTo関連付けを定義できることを考慮して、そのルートにUserモデルのみを読み込みますCommentおよびPostモデル。このようなもの:

App.User = DS.Model.extend({
    firstName: DS.attr('string'),
    lastName: DS.attr('string'),
    email: DS.attr('string'),
    posts: DS.hasMany('App.Post'),
    comments: DS.hasMany('App.Comment')
});

App.Post = DS.Model.extend({
    title: DS.attr('string'),
    body: DS.attr('string'),
    author: DS.belongsTo('App.User'),
    comments: DS.hasMany('App.Comment')
});

App.Comment = DS.Model.extend({
    body: DS.attr('string'),
    post: DS.belongsTo('App.Post'),
    author: DS.belongsTo('App.User')
});

この定義により、次のようなものが生成されます。

Associations between models

この定義を使用すると、投稿をfindするたびに、その投稿に関連付けられたコメントのコレクション、コメントの作成者、投稿の作成者であるユーザーにアクセスできます。 すべて埋め込まれているため。ルートはシンプルなままです:

App.PostsPostRoute = Em.Route.extend({
    model: function(params) {
        return App.Post.find(params.post_id);
    }
});

そのため、PostRoute(またはPostsPostRouteを使用している場合はresource)で、テンプレートはコントローラーのcontentPostモデル)にアクセスできます。 、だから私は単にauthorとして著者を参照することができます

<script type="text/x-handlebars" data-template-name="posts/post">
    <h3>{{title}}</h3>
    <div>by {{author.fullName}}</div><hr />
    <div>
        {{body}}
    </div>
    {{partial comments}}
</script>

<script type="text/x-handlebars" data-template-name="_comments">
    <h5>Comments</h5>
    {{#each content.comments}}
    <hr />
    <span>
        {{this.body}}<br />
        <small>by {{this.author.fullName}}</small>
    </span>
    {{/each}}
</script>

fiddle を参照)


非関連モデルの回答:

ただし、説明したシナリオよりもシナリオが少し複雑である場合、および/またはhave特定のルートに異なるモデルを使用(またはクエリ)する場合は、実行することをお勧めしますin Route#setupController 。例えば:

App.PostsPostRoute = Em.Route.extend({
    model: function(params) {
        return App.Post.find(params.post_id);
    },
    // in this sample, "model" is an instance of "Post"
    // coming from the model hook above
    setupController: function(controller, model) {
        controller.set('content', model);
        // the "user_id" parameter can come from a global variable for example
        // or you can implement in another way. This is generally where you
        // setup your controller properties and models, or even other models
        // that can be used in your route's template
        controller.set('user', App.User.find(window.user_id));
    }
});

そして今、Postルートにいるとき、私のテンプレートはuserフックで設定されたようにコントローラーのsetupControllerプロパティにアクセスできます:

<script type="text/x-handlebars" data-template-name="posts/post">
    <h3>{{title}}</h3>
    <div>by {{controller.user.fullName}}</div><hr />
    <div>
        {{body}}
    </div>
    {{partial comments}}
</script>

<script type="text/x-handlebars" data-template-name="_comments">
    <h5>Comments</h5>
    {{#each content.comments}}
    <hr />
    <span>
        {{this.body}}<br />
        <small>by {{this.author.fullName}}</small>
    </span>
    {{/each}}
</script>

fiddle を参照)

116
MilkyWayJoe

Em.Objectを使用して複数のモデルをカプセル化することは、modelフック内のすべてのデータを取得するための良い方法です。しかし、ビューのレンダリング後にすべてのデータが準備されていることを保証することはできません。

別の選択肢は、Em.RSVP.hashを使用することです。いくつかの約束を組み合わせて、新しい約束を返します。すべての約束が解決された後に解決された場合、新しい約束。そして、setupControllerは、promiseが解決または拒否されるまで呼び出されません。

App.PostRoute = Em.Route.extend({
  model: function(params) {
    return Em.RSVP.hash({
      post:     // promise to get post
      comments: // promise to get comments,
      user:     // promise to get user
    });
  },

  setupController: function(controller, model) {
    // You can use model.post to get post, etc
    // Since the model is a plain object you can just use setProperties
    controller.setProperties(model);
  }
});

このようにして、ビューのレンダリングの前にすべてのモデルを取得します。また、Em.Objectを使用してもこの利点はありません。

もう1つの利点は、promiseとnon-promiseを組み合わせることができることです。このような:

Em.RSVP.hash({
  post: // non-promise object
  user: // promise object
});

Em.RSVPの詳細については、こちらをご覧ください: https://github.com/tildeio/rsvp.js


ただし、ルートに動的セグメントがある場合は、Em.ObjectまたはEm.RSVPソリューションを使用しないでください

主な問題はlink-toです。モデルでlink-toによって生成されたリンクをクリックしてURLを変更すると、モデルはそのルートに直接渡されます。この場合、modelフックは呼び出されず、setupControllerでモデルlink-toを取得します。

例は次のとおりです。

ルートコード:

App.Router.map(function() {
  this.route('/post/:post_id');
});

App.PostRoute = Em.Route.extend({
  model: function(params) {
    return Em.RSVP.hash({
      post: App.Post.find(params.post_id),
      user: // use whatever to get user object
    });
  },

  setupController: function(controller, model) {
    // Guess what the model is in this case?
    console.log(model);
  }
});

そしてlink-toコード、投稿はモデルです:

{{#link-to "post" post}}Some post{{/link-to}}

ここで面白くなってきます。 url /post/1を使用してページにアクセスすると、modelフックが呼び出され、setupControllerがpromiseの解決時にプレーンオブジェクトを取得します。

ただし、link-toリンクをクリックしてページにアクセスすると、postモデルがPostRouteに渡され、ルートはmodelフックを無視します。この場合、setupControllerpostモデルを取得しますが、もちろんユーザーを取得することはできません。

そのため、ダイナミックセグメントのあるルートでは使用しないでください。

49
darkbaby123

しばらく_Em.RSVP.hash_を使用していましたが、私が遭遇した問題は、レンダリングする前にすべてのモデルがロードされるまでビューを待機させたくないということでした。しかし、 Novelys の人々のおかげで Ember.PromiseProxyMixin

3つの異なる視覚セクションがあるビューがあるとします。これらの各セクションは、独自のモデルに裏打ちされている必要があります。ビューの上部にある「スプラッシュ」コンテンツを支えるモデルは小さく、すぐにロードされるため、通常どおりロードできます。

ルートを作成_main-page.js_:

_import Ember from 'ember';

export default Ember.Route.extend({
    model: function() {
        return this.store.find('main-stuff');
    }
});
_

次に、対応するHandlebarsテンプレート_main-page.hbs_を作成できます。

_<h1>My awesome page!</h1>
<ul>
{{#each thing in model}}
    <li>{{thing.name}} is really cool.</li>
{{/each}}
</ul>
<section>
    <h1>Reasons I Love Cheese</h1>
</section>
<section>
    <h1>Reasons I Hate Cheese</h1>
</section>
_

それで、あなたのテンプレートで、チーズとの愛/憎しみの関係について別々のセクションを持ち、それぞれが(何らかの理由で)独自のモデルに裏付けられているとしましょう。各モデルには多くのレコードがあり、それぞれの理由に関連する詳細が記録されていますが、一番上のコンテンツをすばやくレンダリングしたいと考えています。これが_{{render}}_ヘルパーの出番です。テンプレートを次のように更新できます。

_<h1>My awesome page!</h1>
<ul>
{{#each thing in model}}
    <li>{{thing.name}} is really cool.</li>
{{/each}}
</ul>
<section>
    <h1>Reasons I Love Cheese</h1>
    {{render 'love-cheese'}}
</section>
<section>
    <h1>Reasons I Hate Cheese</h1>
    {{render 'hate-cheese'}}
</section>
_

次に、それぞれのコントローラーとテンプレートを作成する必要があります。この例では実質的に同一であるため、使用します。

_love-cheese.js_というコントローラーを作成します。

_import Ember from 'ember';

export default Ember.ObjectController.extend(Ember.PromiseProxyMixin, {
    init: function() {
        this._super();
        var promise = this.store.find('love-cheese');
        if (promise) {
            return this.set('promise', promise);
        }
    }
});
_

ここでPromiseProxyMixinを使用していることに気付くでしょう。これにより、コントローラーが約束を認識します。コントローラーが初期化されると、PromiseはEmber Data。を介して_love-cheese_モデルをロードする必要があることを示します。このプロパティはコントローラーのpromiseプロパティ。

次に、_love-cheese.hbs_というテンプレートを作成します。

_{{#if isPending}}
  <p>Loading...</p>
{{else}}
  {{#each item in promise._result }}
    <p>{{item.reason}}</p>
  {{/each}}
{{/if}}
_

テンプレートでは、約束の状態に応じて異なるコンテンツをレンダリングできます。ページが最初に読み込まれると、[I Love Cheese]セクションに_Loading..._と表示されます。 promiseがロードされると、モデルの各レコードに関連付けられたすべての理由がレンダリングされます。

各セクションは独立して読み込まれ、メインコンテンツのレンダリングをすぐにブロックしません。

これは単純な例ですが、他のみんなが私と同じように役に立つと思います。

コンテンツの多くの行に対して同様のことをしたい場合は、上記のNovelysの例がさらに関連していることがあります。そうでなかったら、上記はあなたのためにうまく働くはずです。

13

これはベストプラクティスではなく、単純なアプローチではないかもしれませんが、1つの中央ルートで複数のモデルを使用できるようにする方法を概念的に示しています。

App.PostRoute = Ember.Route.extend({
  model: function() {
    var multimodel = Ember.Object.create(
      {
        posts: App.Post.find(),
        comments: App.Comments.find(),
        whatever: App.WhatEver.find()
      });
    return multiModel;
  },
  setupController: function(controller, model) {
    // now you have here model.posts, model.comments, etc.
    // as promises, so you can do stuff like
    controller.set('contentA', model.posts);
    controller.set('contentB', model.comments);
    // or ...
    this.controllerFor('whatEver').set('content', model.whatever);
  }
});

それが役に立てば幸い

8
intuitivepixel

他のすべての優れた回答のおかげで、私はここで最高のソリューションをシンプルで再利用可能なインターフェースに組み合わせたミックスインを作成しました。指定したモデルに対してafterModelEmber.RSVP.hashを実行し、setupControllerのコントローラーにプロパティを注入します。標準のmodelフックと干渉しないため、それを通常どおり定義します。

使用例:

App.PostRoute = Ember.Route.extend(App.AdditionalRouteModelsMixin, {

  // define your model hook normally
  model: function(params) {
    return this.store.find('post', params.post_id);
  },

  // now define your other models as a hash of property names to inject onto the controller
  additionalModels: function() {
    return {
      users: this.store.find('user'), 
      comments: this.store.find('comment')
    }
  }
});

Mixinは次のとおりです。

App.AdditionalRouteModelsMixin = Ember.Mixin.create({

  // the main hook: override to return a hash of models to set on the controller
  additionalModels: function(model, transition, queryParams) {},

  // returns a promise that will resolve once all additional models have resolved
  initializeAdditionalModels: function(model, transition, queryParams) {
    var models, promise;
    models = this.additionalModels(model, transition, queryParams);
    if (models) {
      promise = Ember.RSVP.hash(models);
      this.set('_additionalModelsPromise', promise);
      return promise;
    }
  },

  // copies the resolved properties onto the controller
  setupControllerAdditionalModels: function(controller) {
    var modelsPromise;
    modelsPromise = this.get('_additionalModelsPromise');
    if (modelsPromise) {
      modelsPromise.then(function(hash) {
        controller.setProperties(hash);
      });
    }
  },

  // hook to resolve the additional models -- blocks until resolved
  afterModel: function(model, transition, queryParams) {
    return this.initializeAdditionalModels(model, transition, queryParams);
  },

  // hook to copy the models onto the controller
  setupController: function(controller, model) {
    this._super(controller, model);
    this.setupControllerAdditionalModels(controller);
  }
});
4

https://stackoverflow.com/a/16466427/263757 は関連モデルに適しています。ただし、Ember CLIおよびEmber Dataの最近のバージョンでは、無関係なモデルに対してより簡単なアプローチがあります。

import Ember from 'ember';
import DS from 'ember-data';

export default Ember.Route.extend({
  setupController: function(controller, model) {
    this._super(controller,model);
    var model2 = DS.PromiseArray.create({
      promise: this.store.find('model2')
    });
    model2.then(function() {
      controller.set('model2', model2)
    });
  }
});

model2のオブジェクトのプロパティのみを取得する場合は、 DS.PromiseArray の代わりに DS.PromiseObject を使用します。

import Ember from 'ember';
import DS from 'ember-data';

export default Ember.Route.extend({
  setupController: function(controller, model) {
    this._super(controller,model);
    var model2 = DS.PromiseObject.create({
      promise: this.store.find('model2')
    });
    model2.then(function() {
      controller.set('model2', model2.get('value'))
    });
  }
});
2
AWM

MilkyWayJoeの答えに加えて、ありがとうございます。

this.store.find('post',1) 

返す

{
    id: 1,
    title: 'string',
    body: 'string string string string...',
    author_id: 1,
    comment_ids: [1, 2, 3, 6],
    tag_ids: [3,4]
};

著者は

{
    id: 1,
    firstName: 'Joe',
    lastName: 'Way',
    email: '[email protected]',
    points: 6181,
    post_ids: [1,2,3,...,n],
    comment_ids: [1,2,3,...,n],
}

コメント

{
    id:1,
    author_id:1,
    body:'some words and stuff...',
    post_id:1,
}

...私は、完全な関係が確立されるように、リンクバックが重要であると信じています。それが誰かを助けることを願っています。

1
philn5d

beforeModelまたはafterModelフックは、動的セグメントを使用しているためにmodelが呼び出されない場合でも常に呼び出されるため、使用できます。

非同期ルーティング docs:

モデルフックは、一時停止時の移行の多くのユースケースをカバーしていますが、場合によっては、関連するフックbeforeModelおよびafterModelのヘルプが必要になります。これの最も一般的な理由は、{{link-to}}またはtransitionTo(URLの変更による遷移ではなく)を介して動的URLセグメントを含むルートに遷移する場合、ルートのモデルが「への移行は既に指定されています(例:{{#link-to 'article' article}}またはthis.transitionTo( 'article'、article))。この場合、モデルフックは呼び出されません。これらの場合、ルータが遷移を実行するためにルートのすべてのモデルを収集している間に、beforeModelまたはafterModelフックを使用してロジックを格納する必要があります。

themesSiteControllerプロパティがあるとします。次のようなものがあります。

themes: null,
afterModel: function(site, transition) {
  return this.store.find('themes').then(function(result) {
    this.set('themes', result.content);
  }.bind(this));
},
setupController: function(controller, model) {
  controller.set('model', model);
  controller.set('themes', this.get('themes'));
}
0
Ryan D