EmberJS / Ember Dataで1つのルートで複数のモデルを使用するにはどうすればよいですか?


85

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

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

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

回答:


117

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

更新:私の元の回答embedded: trueでは、モデル定義で使用すると言いました。それは正しくありません。リビジョン12では、Ember-Dataは、単一レコードまたはコレクションの外部キーがサフィックス(リンク_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.hasManyCommentDS.belongsToUserCommentPost

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

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

モデル間の関連付け

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

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

したがって、PostRoute(またはPostsPostRouteを使用している場合resource)では、テンプレートはモデルでcontentあるコントローラーにアクセスPostできるため、作成者を次のように参照できます。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>

フィドルを参照)


関連のないモデルで答える:

あなたのシナリオはあなたが説明したものよりも少し複雑で、かつ/または場合は、持っている特定のルートの使用(またはクエリ)異なるモデルに、私はそれを行うことをお勧めします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ルートにいるとき、私のテンプレートはusersetupControllerフックで設定されたコントローラーのプロパティにアクセスできます。

<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>

フィドルを参照)


15
ありがとうそんなにそれを投稿する時間を割いて、私はそれが本当に有用であることが判明しました。
匿名

1
@MilkyWayJoe、本当に良い投稿です!今、私のアプローチは本当にナイーブ:)に見える
intuitivepixel

関連のないモデルの問題は、モデルフックのように約束を受け入れないことですよね?そのための回避策はありますか?
miguelcobain 2014年

1
私があなたの問題を正しく理解していれば、あなたはそれを簡単な方法で適応させることができます。約束が果たされるのを待ってから、モデルをコントローラーの変数として設定します
Fabio

コメントを繰り返し表示する以外に、誰かが新しいコメントをに追加する方法の例を示すことができれば素晴らしいでしょうpost.comments
Damon Aw 2014年

49

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

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

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つの利点は、約束と非約束を組み合わせることができることです。このような:

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

詳細については、こちらを確認してくださいEm.RSVPhttps//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フックが呼び出され、setupControllerpromiseが解決されたときにプレーンオブジェクトを取得します。

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

したがって、動的セグメントのあるルートでは使用しないでください。


2
私の答えは、以前のバージョンのEmber&Ember-Dataに当てはまります。これは本当に良いアプローチ+1です
MilkyWayJoe

11
実はあります。モデル自体ではなくモデルIDをヘルパーにリンクする場合は、モデルフックが常にトリガーされます。
darkbaby123 2013

2
これは、Emberガイド(ベストプラクティスなど)のどこかに文書化する必要があります。これは、多くの人が遭遇すると確信している重要なユースケースです。
yorbro 2014年

1
を使用controller.setProperties(model);している場合は、これらのプロパティをデフォルト値でコントローラーに追加することを忘れないでください。それ以外の場合は例外がスローされますCannot delegate set...
Mateusz Nowak 2014年

13

しばらく使用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');
    }
});

次に、対応するハンドルバーテンプレートを作成できます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>

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

と呼ばれるコントローラーを作成します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がlove-cheeseEmberDataを介してモデルをロードする必要があることを示します。このプロパティは、コントローラーのpromiseプロパティで設定する必要があります。

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

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

テンプレートでは、約束の状態に応じてさまざまなコンテンツをレンダリングできます。ページが最初に読み込まれると、「チーズが大好きな理由」セクションが表示されますLoading...。promiseがロードされると、モデルの各レコードに関連付けられているすべての理由がレンダリングされます。

各セクションは個別に読み込まれ、メインコンテンツのレンダリングがすぐにブロックされることはありません。

これは単純な例ですが、他のすべての人が私と同じように役立つことを願っています。

多くのコンテンツ行に対して同様のことを行う場合は、上記のNovelysの例がさらに適切であることがわかります。そうでない場合は、上記で問題なく動作するはずです。


8

これはベストプラクティスやナイーブなアプローチではないかもしれませんが、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);
  }
});

それが役に立てば幸い


1
このアプローチは問題ありませんが、EmberDataをあまり活用していません。モデルが関連していないいくつかのシナリオでは、私はこれに似たものを手に入れました。
MilkyWayJoe 2013年

4

他のすべての優れた回答のおかげで、ここで最高のソリューションをシンプルで再利用可能なインターフェイスに組み合わせたミックスインを作成しました。指定したモデルに対してEmber.RSVP.hashinafterModelを実行してから、のコントローラーにプロパティを挿入し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')
    }
  }
});

これがミックスインです:

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

2

https://stackoverflow.com/a/16466427/2637573は、関連するモデルに適しています。ただし、最近のバージョンのEmberCLIとEmberDataでは、関連のないモデルに対してより簡単なアプローチがあります。

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

のオブジェクトのプロパティのみを取得する場合はmodel2DS.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'))
    });
  }
});

postDB内の既存のすべてのタグをレンダリングして、編集中の投稿にクリックして追加できるようにする編集ルート/ビューがあります。これらのタグの配列/コレクションを表す変数を定義したいと思います。上記で使用したアプローチはこれに有効ですか?
ジョー

確かに、PromiseArray(「タグ」など)を作成します。次に、テンプレートで、対応するフォームの選択要素にそれを渡します。
AWM

1

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: 'MilkyWayJoe@example.com',
    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,
}

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


0

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

あたりとして非同期ルーティングドキュメント:

モデルフックは、pause-on-promiseトランジションの多くのユースケースをカバーしていますが、関連するフックbeforeModelおよびafterModelの助けが必要になる場合があります。これの最も一般的な理由は、{{link-to}}またはtransitionTo(URLの変更によって引き起こされる移行ではなく)を介して動的URLセグメントを持つルートに移行する場合、ルートのモデルは'への移行はすでに指定されています(例:{{#link-to'article' article}}またはthis.transitionTo( 'article'、article))。この場合、モデルフックは呼び出されません。このような場合、ルーターがルートのすべてのモデルを収集して遷移を実行している間に、beforeModelフックまたはafterModelフックのいずれかを使用してロジックを格納する必要があります。

だから、あなたがthemesあなたSiteControllerにプロパティを持っていると言うと、あなたはこのようなものを持つことができます:

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

1
thispromise内で使用すると、エラーメッセージが表示されると思います。var _this = this戻る前に設定してから、メソッド_this.set(内で実行then(して、目的の結果を得ることができます
Duncan Walker
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.