I'll be fine

技術ブログ

【Ruby on Rails】Backbone.jsにinclude先の情報を渡す方法と注意点

次のような中間テーブルを挟むhas_manyなDB構造の場合
users table

id email
1 test1@gmail.com
2 test2@gmail.com
3 test3@gmail.com
4 test4@gmail.com
5 test5@gmail.com

users_services table

id user_id service_id role
1 3 2 role1
2 2 1 role2
3 3 3 role2
4 1 1 role2
5 1 2 role1

services table

id name
1 service1
2 service2
3 service3

railsのコントローラで以下のように書いてもinclude先の情報はbackboneに渡されません。
index_controller.rb

def index
  @users = User.find(
    :all,
    :include => :services,
  )
end

index.html.erb

<script type="text/javascript">
  $(function() {
    window.router = new Blog.Routers.UsersRouter({users: <%= @users.to_json.html_safe -%>});
    Backbone.history.start();
  });
</script>

to_json時にincludeしなければなりません。
index_controller.rb

def index
  @users = User.find(
    :all,
    :include => :services,
  ).to_json(
    :include => :services,
  ).html_safe
end

index.html.erb

<script type="text/javascript">
  $(function() {
    window.router = new Blog.Routers.UsersRouter({users: <%= @users -%>});
    Backbone.history.start();
  });
</script>

中間テーブルのデータをとってくるときの注意点

以下のように中間テーブルのデータを取ってこようとすると

User.find(
  :all,
  :include    => :services,
  :conditions => ["services.id = ?", 1],
)).to_json(
  :only    => [:email,:id],
  :include => {:users_services => {:only => [:service_id, :role]}},
)

次のようなjsonがかえってきます。

'[{"email":"test2@gmail.com","id":2,"users_services":[{"role":"role2","service_id":1}]},{"email":"test1@gmail.com","id":1,"users_services":[{"role":"role2","service_id":1},{"role":"role1","service_id":2}]]'

最後を見てもらえればわかる通りconditionsでservice_idが1のものを指定しているのに2のものがくっついてきています。
to_jsonのinclude時のsql文を見てみると

SELECT `users_services`.* FROM `users_services` WHERE `users_services`.`user_id` = 1
SELECT `users_services`.* FROM `users_services` WHERE `users_services`.`user_id` = 2

なぜかuser_idで絞ってます。
この問題を回避するにはto_json時にmethodsを定義します。
index_controller.rb

def index
  @users = User.find(
    :all,
  )).to_json(
    :only    => [:email,:id],
    :methods => :fetch_service_id_one
  )
end

user.rb

def fetch_service_id_one
  users_services.where("service_id = ?", 1).select("service_id, role")
end

sql

SELECT service_id, role FROM `users_services` WHERE `users_services`.`user_id` = 1 AND (service_id = 1)
SELECT service_id, role FROM `users_services` WHERE `users_services`.`user_id` = 2 AND (service_id = 1)

返ってくるjson

'[{"email":"test2@gmail.com","id":2,"fetch_service_id_one":[{"role":"role2","service_id":1}]},{"email":"test1@gmail.com","id":1,"fetch_service_id_one":[{"role":"role2","service_id":1}]]'

service_idが1のものだけ返ってくるようになりました。

もしこうしたほうが楽にできる、ここ間違ってるなどあれば教えてくださいm(_ _)m