Ruby on Rails でのjson出力

Ruby on Rails での、json 出力について調べたのでまとめます。

目次

実行環境

  • Rails 5.2.3

モデル

scaffold でモデル他作成

User モデルと User モデルが、複数の Itme を持つものとする。
生成のコマンドは以下の通り、json 出力をテストしたいだけなので、scaffold で作成する。

1
2
3
rails generate scaffold user name:string
rails generate scaffold item name:string price:integer user:references
rails db:migrate

各モデルのアソシエーションは以下の通り。

app/models/user.rb
1
2
3
class User < ApplicationRecord
has_many :items
end
app/models/item.rb
1
2
3
class Item < ApplicationRecord
belongs_to :user
end

データの用意

適当なデータを作ります。
今回は seed で作成してみる。
db\seeds.rb を以下のように編集した。

seeds.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#一旦削除 参照先を先に削除するとエラーが発生。順番に注意
Item.delete_all()
User.delete_all()

user1 = User.create(name:"User1")
Item.create(name:"item1",price:100,user:user1)
Item.create(name:"item2",price:200,user:user1)

user2 = User.create(name:"User3")
Item.create(name:"item3",price:300,user:user2)
Item.create(name:"item4",price:400,user:user2)

user3 = User.create(name:"User3")
Item.create(name:"item5",price:500,user:user3)
Item.create(name:"item6",price:600,user:user3)

こちらを作成して、rails db:seedで反映し、rails sでアプリケーションを開始する。
以後は、/users.json にアクセスしたときの動作をもとに確認するものとする。

確認

用意されている json でのレスポンス

scaffold で用意したことである程度のものがそろっている。
User コントローラを開くと以下のようになっています。

app/controllers/users_controller.rb
1
2
3
4
5
6
class UsersController < ApplicationController
def index
@users = User.all
end

 # 以下省略

ブラウザで/users.json にアクセスすると、以下のレスポンスになります。

1
2
3
[
{"id":1,"name":"User1","created_at":"2019-11-05T14:03:54.753Z","updated_at":"2019-11-05T14:03:54.753Z","url":"http://localhost:3000/users/1.json"},{"id":2,"name":"User2","created_at":"2019-11-05T14:03:54.805Z","updated_at":"2019-11-05T14:03:54.805Z","url":"http://localhost:3000/users/2.json"},{"id":3,"name":"User3","created_at":"2019-11-05T14:03:54.831Z","updated_at":"2019-11-05T14:03:54.831Z","url":"http://localhost:3000/users/3.json"}
]

これは、app/views/users/index.json.jbuilder によって処理された結果になっている。
app/views/users/index.json.jbuilder 配下の通り。

app/views/users/index.json.jbuilder
1
json.array! @users, partial: "users/user", as: :user

コントローラで定義した@users が index.json.jbuilder で展開されているのがわかる。
jbuilder でも部分テンプレート partial が使用されている。
この部分テンプレートは app/views/users/_user.json.jbuilder が該当する。

app/views/users/_user.json.jbuilder
1
2
json.extract! user, :id, :name, :created_at, :updated_at
json.url user_url(user, format: :json)

もっとシンプルに json 出力

app/views/users/index.json.jbuilder を適当にリネームする。
以下のように編集する。

app/controllers/users_controller.rb
1
2
3
4
5
6
7
class UsersController < ApplicationController

def index
@users = User.all
render json: @users
end
 # 以下省略

ブラウザで/users.json にアクセスすると、以下のレスポンスになります。

1
2
3
4
5
[
{"id":1,"name":"User1","created_at":"2019-11-05T14:03:54.753Z","updated_at":"2019-11-05T14:03:54.753Z"},
{"id":2,"name":"User2","created_at":"2019-11-05T14:03:54.805Z","updated_at":"2019-11-05T14:03:54.805Z"},
{"id":3,"name":"User3","created_at":"2019-11-05T14:03:54.831Z","updated_at":"2019-11-05T14:03:54.831Z"}
]

特定のカラムだけ json 出力

特定のカラムだけを吐き出したいことがあります。
selest メソッドを使用して、user.id と user.name だけを出力する場合以下のようになります。

app/controllers/users_controller.rb
1
2
3
4
5
6
class UsersController < ApplicationController
def index
@users = User.all
render json: @users.select(:id,:name)
end
 # 以下省略

ブラウザで/users.json にアクセスすると、以下のレスポンスになります。

1
2
3
4
5
[
{"id":1,"name":"User1"},
{"id":2,"name":"User2"},
{"id":3,"name":"User3"}
]

関連モデルを json 出力 join 編

User に関連付けされている Item もまとめて json で出力してみます。
users テーブルと、items テーブルを連結してから出力する場合は以下のようにできます。

app/controllers/users_controller.rb
1
2
3
4
5
6
7
class UsersController < ApplicationController

def index
@users = User.joins(:items).select('users.*,items.*')
render json: @users
end
 # 以下省略

ブラウザで/users.json にアクセスすると、以下のレスポンスになります。

1
2
3
4
5
6
7
[
{"id":1,"name":"item1","created_at":"2019-11-05T14:03:54.786Z","updated_at":"2019-11-05T14:03:54.786Z","price":100,"user_id":1},
{"id":2,"name":"item2","created_at":"2019-11-05T14:03:54.795Z","updated_at":"2019-11-05T14:03:54.795Z","price":200,"user_id":1},
{"id":3,"name":"item3","created_at":"2019-11-05T14:03:54.813Z","updated_at":"2019-11-05T14:03:54.813Z","price":300,"user_id":2},
{"id":4,"name":"item4","created_at":"2019-11-05T14:03:54.822Z","updated_at":"2019-11-05T14:03:54.822Z","price":400,"user_id":2},
{"id":5,"name":"item5","created_at":"2019-11-05T14:03:54.839Z","updated_at":"2019-11-05T14:03:54.839Z","price":500,"user_id":3},
{"id":6,"name":"item6","created_at":"2019-11-05T14:03:54.848Z","updated_at":"2019-11-05T14:03:54.848Z","price":600,"user_id":3}]

出力ができましたが、どうやら User の id と name のカラムが item の id と name で上書きされるようです。
誤算でした。

この対応としてカラムに別名をつければいいようです。
.select('users.*,items.id as items_id,items.name as items_name') のようにすることで回避できました。

app/controllers/users_controller.rb
1
2
3
4
5
6
7
class UsersController < ApplicationController

def index
@users = User.left_outer_joins(:items).select('users.*,items.id as items_id,items.name as items_name')
render json: @users
end
 # 以下省略

ブラウザで/users.json にアクセスすると、以下のレスポンスになります。

1
2
3
4
5
6
7
8
[
{"id":1,"name":"User1","created_at":"2019-11-05T14:03:54.753Z","updated_at":"2019-11-05T14:03:54.753Z","items_id":1,"items_name":"item1"},
{"id":1,"name":"User1","created_at":"2019-11-05T14:03:54.753Z","updated_at":"2019-11-05T14:03:54.753Z","items_id":2,"items_name":"item2"},
{"id":2,"name":"User2","created_at":"2019-11-05T14:03:54.805Z","updated_at":"2019-11-05T14:03:54.805Z","items_id":3,"items_name":"item3"},
{"id":2,"name":"User2","created_at":"2019-11-05T14:03:54.805Z","updated_at":"2019-11-05T14:03:54.805Z","items_id":4,"items_name":"item4"},
{"id":3,"name":"User3","created_at":"2019-11-05T14:03:54.831Z","updated_at":"2019-11-05T14:03:54.831Z","items_id":5,"items_name":"item5"},
{"id":3,"name":"User3","created_at":"2019-11-05T14:03:54.831Z","updated_at":"2019-11-05T14:03:54.831Z","items_id":6,"items_name":"item6"}
]

今度は,User の id と name,Item の id と name を別に出力できました。
テーブルを連結しているので、シンプルに特別構造化されることもない出力になりました。
カラム数が多くなってくると個別に別名をつけるのはやや面倒そうです。

関連モデルを json 出力 jbuilder 編

今度は User に関連付けされている Item もまとめて jbuilder で json を作って出力してみます。

app/controllers/users_controller.rb
1
2
3
4
5
6
7
class UsersController < ApplicationController

def index
@users = User.all
end

 # 以下省略
app/views/users/index.json.jbuilder
1
json.array! @users ,partial: "users/user" ,as: :user
app/views/users/_user.json.jbuilder
1
2
3
4
json.extract! user, :id, :name, :created_at, :updated_at
json.items do
json.array! user.items
end

ブラウザで/users.json にアクセスすると、以下のレスポンスになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[
{
"id":1,"name":"User1","created_at":"2019-11-05T14:03:54.753Z","updated_at":"2019-11-05T14:03:54.753Z",
"items":[
{"id":1,"name":"item1","price":100,"user_id":1,"created_at":"2019-11-05T14:03:54.786Z","updated_at":"2019-11-05T14:03:54.786Z"},
{"id":2,"name":"item2","price":200,"user_id":1,"created_at":"2019-11-05T14:03:54.795Z","updated_at":"2019-11-05T14:03:54.795Z"}
]
},
{
"id":2,"name":"User2","created_at":"2019-11-05T14:03:54.805Z","updated_at":"2019-11-05T14:03:54.805Z",
"items":[
{"id":3,"name":"item3","price":300,"user_id":2,"created_at":"2019-11-05T14:03:54.813Z","updated_at":"2019-11-05T14:03:54.813Z"},
{"id":4,"name":"item4","price":400,"user_id":2,"created_at":"2019-11-05T14:03:54.822Z","updated_at":"2019-11-05T14:03:54.822Z"}
]
},
{
"id":3,"name":"User3","created_at":"2019-11-05T14:03:54.831Z","updated_at":"2019-11-05T14:03:54.831Z",
"items":[
{"id":5,"name":"item5","price":500,"user_id":3,"created_at":"2019-11-05T14:03:54.839Z","updated_at":"2019-11-05T14:03:54.839Z"},
{"id":6,"name":"item6","price":600,"user_id":3,"created_at":"2019-11-05T14:03:54.848Z","updated_at":"2019-11-05T14:03:54.848Z"}
]
}
]

構造化された形の json 形式で出力できました。

関連モデルを json 出力 partial を使わない jbuilder 編

調べていると「partial 遅い」が検索に良く引っかかります。
だそうなので、partial を使わない書き方も確認しておきます。

app/controllers/users_controller.rb
1
2
3
4
5
6
7
class UsersController < ApplicationController

def index
@users = User.all
end

 # 以下省略
app/views/users/index.json.jbuilder
1
2
3
4
5
6
json.array! @users do |user|
json.extract! user, :id, :name, :created_at, :updated_at
json.items do
json.array! user.items
end
end

ブラウザで/users.json にアクセスすると、以下のレスポンスになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[
{
"id":1,"name":"User1","created_at":"2019-11-05T14:03:54.753Z","updated_at":"2019-11-05T14:03:54.753Z",
"items":[
{"id":1,"name":"item1","price":100,"user_id":1,"created_at":"2019-11-05T14:03:54.786Z","updated_at":"2019-11-05T14:03:54.786Z"},
{"id":2,"name":"item2","price":200,"user_id":1,"created_at":"2019-11-05T14:03:54.795Z","updated_at":"2019-11-05T14:03:54.795Z"}
]
},
{
"id":2,"name":"User2","created_at":"2019-11-05T14:03:54.805Z","updated_at":"2019-11-05T14:03:54.805Z",
"items":[
{"id":3,"name":"item3","price":300,"user_id":2,"created_at":"2019-11-05T14:03:54.813Z","updated_at":"2019-11-05T14:03:54.813Z"},
{"id":4,"name":"item4","price":400,"user_id":2,"created_at":"2019-11-05T14:03:54.822Z","updated_at":"2019-11-05T14:03:54.822Z"}
]
},
{
"id":3,"name":"User3","created_at":"2019-11-05T14:03:54.831Z","updated_at":"2019-11-05T14:03:54.831Z",
"items":[
{"id":5,"name":"item5","price":500,"user_id":3,"created_at":"2019-11-05T14:03:54.839Z","updated_at":"2019-11-05T14:03:54.839Z"},
{"id":6,"name":"item6","price":600,"user_id":3,"created_at":"2019-11-05T14:03:54.848Z","updated_at":"2019-11-05T14:03:54.848Z"}
]
}
]

構造化された形の json 形式の出力を partial を使わずに作ることができました。


jbuilder も絡めて、Ruby on Rails での json 出力について確認できました。
Ruby on Rails の学習を進めていますが、改めて初期に書いた記事を見ると、理解が浅いゆえに間違っている部分あるようです。
復習の意味も込めつつ textlint と仲良くしながら追々直していきたいです。

ではでは。