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 に手を入れれば対応させることはできるかもしれませんが、ひとまずそのままいきます。
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 にも書くので、親クラスを作ると便利です。
class ApplicationSerializer < ActiveModel::Serializer
# Compatible with ember-data.
embed :ids, include: true
end
class PostSerializer < ApplicationSerializer
attributes :id, :title, :body
has_many :comments
end
class CommentSerializer < ApplicationSerializer
attributes :id, :post_id, :body
end
embed :ids, include: true
にすると、Post の JSON を返す時、以下のように comments の中身も一緒に含めてくれます。
{
"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
にします。
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 がデフォルトです。
class ApplicationSerializer < ActiveModel::Serializer
# Compatible with ember-data.
embed :ids
end
Ember 側は async: true
にしておきます。こっちの作りが、今後 Ember Data のデフォルトになる可能性が高そうです。
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
子の保存
子を保存しても、クライアント側では自動的に親に追加されないので、自分で追加します。
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