4
4

More than 5 years have passed since last update.

Ember Data を Rails と一緒に使う

Last updated at Posted at 2014-05-02

Ember Data は、Beta なこともあり、まだあまり情報がないので整理しておきます。Guides にもあまり書かれていないので、Discussion Forum や Stackoverflow を参考にしました。

ActiveModelAdapter

RESTAdapter という一見デフォルト風の Adapter がありますが、Rails の snakecase な変数名から JavaScript な camelcase に変換してくれる ActiveModelAdapter が使いやすいと思います。

Rails 側では Active Model Serializer を使って JSON を出力します。

Rails の routing

衝撃的なことに、AcriveModelAdapter は(RESTAdapter も)ネストした URL に対応していません。Adapter に手を入れれば対応させることはできるかもしれませんが、ひとまずそのままいきます。

config/routes.rb
resources :posts, except: [:new, :edit], defaults: { format: 'json' }
resources :comments, except: [:new, :edit], defaults: { format: 'json' }

ActiveModel::Serializers

ActiveModel::Serializers をインストールして使います。

gem 'active_model_serializer'
bundle install
./bin/rails g serializer post
./bin/rails g serializer comment

Serializer を用意しておくだけで、controller で respond_to した際に、自動的に Serializer を使ってくれます。

子の中身も親のレスポンスに含める場合

関連を id の配列として出力するため embed :ids と書きます。どの model にも書くので、親クラスを作ると便利です。

app/serializers/applicaton_serializer.rb
class ApplicationSerializer < ActiveModel::Serializer
  # Compatible with ember-data.
  embed :ids, include: true
end
app/serializers/post_serializer.rb
class PostSerializer < ApplicationSerializer
  attributes :id, :title, :body
  has_many :comments
end
app/serializers/comment_serializer.rb
class CommentSerializer < ApplicationSerializer
  attributes :id, :post_id, :body
end

embed :ids, include: true にすると、Post の JSON を返す時、以下のように comments の中身も一緒に含めてくれます。

/posts.json
{
  "comments": [
    {
      "id": 123,
      "body": "Nice post!"
    }
  ],
  "posts": [
    {
      "id": 345,
      "title": "Hello, World!",
      "body": "I'm so happy to meet you, World!",
      "comment_ids": [123]
    }
  ]
}

この際、Ember 側の model の hasMany を async: false にします。

models/post.js
App.Post = DS.Model.extend({
  title: DS.attr('string'),
  body: DS.attr('string'),
  comments: DS.hasMany('comment', { async: false })
});

子の中身を親のレスポンスに含めない場合

Comment は別の JSON でリクエストさせたい場合は、embed :ids, include: false にして、Post のレスポンスに Comment の中身を含めないようにします。false がデフォルトです。

app/serializers/applicaton_serializer.rb
class ApplicationSerializer < ActiveModel::Serializer
  # Compatible with ember-data.
  embed :ids
end

Ember 側は async: true にしておきます。こっちの作りが、今後 Ember Data のデフォルトになる可能性が高そうです。

models/post.js
App.Post = DS.Model.extend({
  title: DS.attr('string'),
  body: DS.attr('string'),
  comments: DS.hasMany('comment', { async: true })
});

こうしておくと、Post が読み込まれた後、Comment は別途 Post ごとに /comments?ids[]=1&ids[]=2 のような URL で問い合わせられます。実際は URL ecnode されていて /comments?ids%5b%5d=1&ids%5b%5d=2 となっています。

Rails の controller でフィルタリング処理を実装しておかないと、毎回全部のコメントを返してしまい無駄になります。それでも動くようでしたが。

class CommentController < ApplicationController
  respond_to :json

  def index
    if params[:ids].is_a?(Enumerable)
      @comments = Comment.where(id: params[:ids])
    else
      @comments = Comment.all
    end
    respond_with @comments
  end
end

子の保存

子を保存しても、クライアント側では自動的に親に追加されないので、自分で追加します。

controllers/comment_new_controller.js
App.CommentNewController = Ember.ObjectController.extend({
  // PostController を呼び出すのに必要。
  needs: 'post',
  actions: {
    createComment: function() {
      // PostController の model を取得。
      var post = this.get('controllers.post.content');

      var comment = this.store.createRecord('comment', {
        body: this.get('body'),
        post: post
      };

      comment.save().then(function() {
        post.get('comments').pushObject(comment);
      });
    }
  }
});

Rails 側も resources をネストさせていないので、ちょっと変わった感じになります。

class CommentController < ApplicationController
  respond_to :json

  before_action :set_post, only: [:create]

  def create
    @comment = @post.comments.create(comment_params)
    respond_with @comment
  end

  private

  def set_post
    @post = Post.find(comment_params[:post_id])
  end

  def comment_params
    params.require(:comment).permit(:body, :post_id)
  end
end

という感じで、なんとかひととおり動くようになりました。

Ember の他の部分に比べ、Ember Data はまだまだこれからな感じですね。

参考

hasMany の async: true って何?
http://discuss.emberjs.com/t/what-is-an-async-relationship-async-true-vs-async-false/4107/10

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4