読者です 読者をやめる 読者になる 読者になる

すがブロ

sugamasaoのhatenablogだよ

Rails のモデル関係と to_json(to_xml)

rails ruby

関係を持ったテーブルと出力方法について

Rails初心者にありがちなメモをするよ。
ちなみに、環境は Ruby1.9.2 と Rails3 です。

テーブル間のリレーションについて

ユーザ情報テーブルと、ユーザが持ってる所持品のテーブル的なものがあるとして。
面倒なのでスゲー適当だけど以下のような定義で作った。

rails g scaffold user name:string item_id:integer
rails g scaffold item name:string

item_id は items テーブルの id が入るってことね。

  • users
  • items
    • name:string

これで、ユーザはitem_idに items テーブルのidを一つもつ感じ。

model に関連をつける

いわゆる has_many とか has_one とか belongs_to のアレ。
今回の件だとこんな感じ*1

vim app/models/user.rb

class User < ActiveRecord::Base
  belongs_to :item
end

これでおk。
こうすることで、user.item で item_id に紐づいた値を探してくれるようになる。

これを json にして出力したいぜ

例えば以下のようなデータがあるとして

  • users
    • id => 1
    • name => aaaa
    • item_id => 1
  • items
    • id => 1
    • name => item1

irbで確認するとこんな感じ。

ruby-1.9.2-head > User.find_by_id(1)
=> #
ruby-1.9.2-head > User.find_by_id(1).item
=> #

うん、予定通りのデータが入ってるね。じゃあ、これを json で出力してみましょう。

ruby-1.9.2-head > User.find_by_id(1).to_json

{
    user: {
        created_at: 2010-09-13T21:18:44Z (string)
        ,id: 1 (number)
        ,item_id: 1 (number)
        ,name: aaaa (string)
        ,updated_at: 2010-09-13T21:18:44Z (string)
    }
}

たしかに json 形式なんだけど、item の値が入っていない。あんまりだ。
ちなみに、json の整形は JSON整形 を使った。

to_json にはオプションがいろいろある

テストデータがアレな気がするけど、オプションについて詳しく載っていてとても良い。

つまり :include しろってことだってばよ

ruby-1.9.2-head > puts User.find_by_id(1).to_json(:include => :item)

{
    user: {
        created_at: 2010-09-13T21:18:44Z (string)
        ,id: 1 (number)
        ,item_id: 1 (number)
        ,name: aaaa (string)
        ,updated_at: 2010-09-13T21:18:44Z (string)
        ,item: {
            created_at: 2010-09-13T21:17:10Z (string)
            ,id: 1 (number)
            ,name: item1 (string)
            ,updated_at: 2010-09-13T21:17:10Z (string)
        }
    }
}

やった、itemのデータも引っ張ってこれた!
ただ、これを出力データとするにはちょっと冗長すぎる。updated_at とか必要かっつーと入らなかったりする。

:only や :except で出力を制限できる

例えばこんな感じだ

User.find(:all)[0].to_json({:include => {:item => {:only => [:id,:name]}}, :only => [:name]})

{
    user: {
        name: aaaa (string)
        ,item: {
            id: 1 (number)
            ,name: item1 (string)
        }
    }
}

これ、only が items にも users にも指定してあって、ちょっと重複した感じがしてイヤなのだけど、上位(この場合 users)で指定した場合、その値が子の要素にも only の設定が伝播してしまう。
今回の場合、両方とも name しかないからあまり実害はないけれど、実際の場合はそんな訳はないので、個別に出力する属性を指定する感じの方が良いと思う。
ちなみに、to_xml でも同様です。

じゃあ、respond_with の場合ってどうなるのよ

こんな感じで実装できた。

class UsersController < ApplicationController

    respond_to :html, :xml, :json

  # GET /users
  # GET /users.xml
  def index
    @users = User.all
    respond_with(@users, {:include => {:item => {:only => [:id,:name]}}, :only => [:name]})
  end
  (略)
end

従来の respond_to の場合はこんな感じだろうか(面倒なので、とりあえず json の場合のみ)。

    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @users }
      format.json  { render :js => @users.to_json({:include => {:item => {:only => [:id,:name]}}, :only => [:name]}) }
    end

こう見ると respond_with の cool 感が際立ってやべぇすな。

*1:意味的にあったるかは微妙な気がする。itemの所属するって変じゃね!?