更新日:
【Rails】 jbuilderの使い方辞典〜メソッドの文法と使い方
jbuilderとは、RailsのGemfileにデフォルトで含まれている「JSON形式のデータを簡単に作成する事が出来るgemのこと」です。
app/views
以下に〇〇.json.jbuilderのファイルを作成して、jbuilderのオブジェクトのjsonを使うと、JSON形式の文字列のデータを作成する事が出来ます。
例えば、app/views/users/index.json.jbuilder
にUserモデルの全てのインスタンスが格納された@users
を使って、下記の様に記述します。
1
2
# @users = User.all
json.array! @users, :id, :nickname, :age
そして、http://localhost:3000/users.jsonにアクセスするだけで、Userモデルの全てのインスタンスが、配列に格納されたJSON形式の文字列のデータで返却する事が出来ます。
上記のjson.array!
の様に、json
オブジェクトに対して様々なメソッドを使う事で複雑なJSON形式の文字列のデータを簡単に構築する事が出来ます。
JSONについて詳細は、「ゼロから始めるJSON入門」を参考にしてください。
jbuilderの基本的な使い方
この章では、jbuilder
の基本的な使い方について解説していきます。
json.キー名
json.キー名 "値"
とする事で名前(キー名): 値
がペアとなったJSON形式の文字列が返却されます。
1
2
json.キー名 "値"
# {"キー名": "値"}
例えば、index.json.jbuilder
を以下のように記述して、http://localhost:3000/users.jsonにアクセスすると、次の画像のように{"name":"ピカ子"}
が返却されます。
1
2
json.name "ピカ子"
# {"name": "ピカ子"}
複数のデータを返す場合
名前(キー名)/値
がペアとなった複数のプロパティを返す場合は、以下のように記述します。
1
2
3
4
json.キー名1 "値1"
json.キー名2 "値2"
# {"キー名1": "値1", "キー名2" : "値2"}
1
2
3
4
json.name "ピカ子"
json.age 18
# {"name": "ピカ子", "age": 18}
入れ子構造にする場合
入れ子構造にして属性をまとめたい場合は、以下のように記述します。
1
2
3
4
5
json.親のキー名 do
json.子のキー名 "値"
end
# {"親のキー名": {"子のキー名": "値"}}
例えば、以下のように記述する事で、1人のユーザー情報をまとめる事が出来ます。
1
2
3
4
5
6
json.user do
json.name "ピカ子"
json.age 18
end
# {"user": {"name": "ピカ子", "age": 18}}
また、複数の入れ子構造のデータを返却する場合は、以下のように記述します。
1
2
3
4
5
6
7
8
9
10
11
json.user do
json.name "ピカ子"
json.age 18
end
json.post do
json.title "jbuilderとは?"
json.author "田中 ピカ太郎"
end
# {"user": {"name": "ピカ子", "age" :18}, "post" :{"title" :"jbuilderとは?", "author" :"田中 ピカ太郎"}}
注意点
複数のデータを扱う場合にキー名が重複していると、以下のように最後に記述したデータのみがJSON形式の文字列に変換されます。
1
2
3
4
5
6
7
json.user do
json.name "ピカ子"
json.name "ピカオ"
json.name "ピカ" # このデータだけがJSON文字列になる
end
# {"user": {"name": "ピカ"}}
json.set!
json.set!
は、以下のように記述する事で名前(キー名)/値
がペアとなったJSON形式の文字列を返却します。
1
2
3
4
json.set! "キー名", "値"
# json.キー名 "値" でも同じデータを返却出来る
# {"キー名" :"値"}
これは、先ほどのjson.キー名
を使った場合と同一の結果を得られます。
1
2
3
4
json.set! "name", "ピカ子"
# json.name "ピカ子" と同一の結果になる
# {"name": "ピカ子"}
キー名をシンボルで指定する
第一引数のキー名
には、以下のように文字列の代わりにシンボルで指定する事が出来ます。
1
2
3
4
json.set! "キー名", "値" # キー名が文字列の場合
json.set! :キー名, "値" # キー名がシンボルの場合
# {"キー名" :"値"} # どちらも同一の結果になる
以下のように、先ほどのコードのキー名
を文字列
からシンボル
に変更しても同一の結果を得られます。
1
2
3
json.set! :name, "ピカ子" # キー名がシンボルの場合
# {"name": "ピカ子"}
入れ子構造にする場合
json.キー名
と同様にjson.set!
で入れ子構造にする場合は、以下のように記述します。
1
2
3
4
5
6
json.set! :親のキー名 do
json.子のキー名 "値"
# json.set! 子のキー名 "値" でも良い
end
# {"親のキー名": {"子のキー名": "値"}}
json.子のキー名
の箇所は、json.set!
にしても同一の結果を得られます。
1
2
3
4
5
6
7
8
json.set! :user do
json.name "ピカ子"
# json.set! :name, "ピカ子" でも良い
json.age 18
# json.set! :age, 18" でも良い
end
# {"user": {"name": "ピカ子", "age": 18}}
また、複数の入れ子構造のデータを返却する場合は、以下のように記述します。
1
2
3
4
5
6
7
8
9
10
11
12
13
json.set! :user do
json.name "ピカ子"
# json.set! :name, "ピカ子" でも良い
json.age 18
# json.set! :age, 18" でも良い
end
json.set! :post do
json.title "jbuilderとは?"
json.author "田中 ピカ太郎"
end
# {"user": {"name": "ピカ子", "age" :18}, "post" :{"title" :"jbuilderとは?", "author" :"田中 ピカ太郎"}}
json.キー名との違いとは?
これまで解説したjson.set!
の箇所は、json.キー名
を使った場合と同一の結果を得られていましたが、json.set!
は、動的にキー名を定義する事が出来ます。
json.set!
- 動的にキー名を定義することが出来るjson.キー名
- 動的にキー名を定義することが出来ない
例えば、インスタンス変数とeachメソッドを使って、以下のように記述することで動的にキー名
を定義する事が出来ます。
1
2
3
4
5
6
7
# @user = { id: 1, name: "ピカ", age: 18 }
@user.each do |key, value|
json.set! key, value
end
# {"id":1,"name":"ピカ","age":18}
これをjson.キー名
で以下のように記述しても、キー名を動的に定義することが出来ず、期待するJSONは返却されません。
1
2
3
4
5
6
7
# @user = { id: 1, name: "ピカ", age: 18 }
@user.each do |key, value|
json.key value
end
# {"key":18}
このように動的にキー名を定義したい場合は、json.set!
を使うようにしましょう。
インスタンス変数のデータを使う場合
この章では、インスタンス変数のデータを使う場合に便利なメソッドを解説します。
解説には、以下のインスタンス変数を使います。
1
2
3
4
5
6
7
8
9
class UsersController < ApplicationController
def index
@users = User.all
end
def show
@user = User.find(params[:id])
end
end
テーブルの中身は以下の通りです。
基本的な使い方
インスタンス変数のデータをjbuilder
で使うには、以下のように記述します。
1
2
3
json.nickname @user.nickname
# {"nickname":"ピカ子"}
http://localhost:3000/users/1.jsonにアクセスすると、指定したインスタンス変数のデータ(usersテーブルのid=1のnickname)がJSON文字列で返却されます。
複数のデータを返却する場合
インスタンス変数の複数のデータを返却するには、以下のように記述します。
1
2
3
4
5
json.id @user.id
json.nickname @user.nickname
json.age @user.age
# {"id":1,"nickname":"ピカ子","age":18}
http://localhost:3000/users/1.jsonにアクセスすると、複数のデータ(usersテーブルのid=1)がJSON文字列で返却されます。
入れ子構造にする場合
入れ子構造にして属性をまとめたい場合は、以下のように記述します。
1
2
3
json.user @user, :id, :nickname, :age
# {"user": {"id": 1, "nickname": "ピカ子", "age": 18}}
@user
の後に、JSON文字列にしたいデータのプロパティ名を指定します。
インスタンス変数の要素が複数の場合
インスタンス変数の要素が複数の場合は、以下の様に記述します。
1
2
3
4
5
6
7
8
9
10
# @users = User.all
json.users @users do |user|
json.id user.id
json.nickname user.nickname
json.age user.age
end
# {"users":[{"id":1,"nickname":"ピカ子","age":18},{"id":3,"nickname":"ピカオ","age":14},{"id":4,"nickname":"ひよこ","age":22},{"id":5,"nickname":"ぴよこ","age":18},{"id":7,"nickname":"ぴよっち","age":22}]}
JSON形式の文字列にするプロパティが、他の箇所でも共通している場合は、後半で解説している部分テンプレートを使うことによって、汎用性のある設計にすることが出来ます。
json.extract!
json.extract!
は、以下のように記述する事で第一引数に指定したインスタンス変数のデータをJSON形式の文字列で返却します。
1
2
3
json.extract! @user, :id, :nickname, :age
# {"id":1,"nickname":"ピカ子","age":18}
先ほどのjson.キー名
では、インスタンス変数を取り出すために以下のように1行1行記述しキー名と値をそれぞれ指定しなければなりませんでしたが、json.extract!
では簡潔に1行で記述することが出来ます。
1
2
3
4
5
json.id @user.id
json.nickname @user.nickname
json.age @user.age
# {"id": 1, "nickname": "ピカ子", "age" :18}
入れ子構造にする場合
入れ子構造にして属性をまとめたい場合は、以下のように記述します。
1
2
3
4
5
6
json.user do
# json.set! :user do でも良い
json.extract! @user, :id, :nickname, :age
end
# {"user": {"id" : 1, "nickname": "ピカ子", "age": 18}}
json.merge!
json.merge!
は、以下のように指定したハッシュや配列をJSON文字列のデータに変換して追加します。
1
2
3
4
5
6
7
8
9
hash = { id: 3 }
json.user do
json.merge! hash # ハッシュをJSON文字列へ
json.name "ピカ子"
json.age 18
end
# {"user": {"id": 3, "name": "ピカ子", "age" :18}}
attributesを使った場合
attributes
メソッドは、以下のようにモデルのインスタンスの中身をハッシュにして返します。
1
2
3
4
5
6
7
8
[1] pry(#<UsersController>)> @user.attributes
=> {"id"=>1,
"nickname"=>"ピカ子",
"age"=>18,
"created_at"=>Tue, 10 Mar 2020 02:39:43 UTC +00:00,
"updated_at"=>Tue, 10 Mar 2020 03:00:52 UTC +00:00,
"avatar"=>"120185.png"}
この@user.attributes
でUserモデルの中身をハッシュに変換して、以下のようにjson.merge!
に渡す事で、JSON文字列に変換する事ができます。
1
2
3
json.merge! @user.attributes
# {"id":1,"nickname":"ピカ子","age":18,"created_at":"2020-03-10T02:39:43.000Z","updated_at":"2020-03-10T03:00:52.000Z","avatar":"120185.png"}
json.array!
json.array!
は、インスタンス変数のデータが複数ある場合に利用します。
usersコントローラのindexアクションで定義した以下の@users
を使って解説します。
1
2
3
4
5
class UsersController < ApplicationController
def index
@users = User.all
end
end
json.array!
は、以下のように記述することで、Userモデルの全てのインスタンスが、配列に格納されたJSON形式の文字列のデータで返却する事が出来ます。
1
2
3
json.array! @users, :id, :nickname, :age
# => [{"id": 1, "nickname": "ピカ子", "age": 18}, {"id": 3, "nickname": "ピカオ", "age": 14},{"id":4,"nickname":"ひよこ","age":22}, {"id":5,"nickname":"ひよこ","age":18}, {"id":7,"nickname":"ぴよっち","age":22}]
http://localhost:3000/users.jsonにアクセスすると、以下のように配列に格納されたJSON文字列が返却されます。
入れ子構造にする場合
json.array!
は、よく入れ子構造にして属性をまとめて使われることが多いです。
以下のように記述すると、入れ子構造にしてJSONデータを配列に格納することが出来ます。
1
2
3
4
5
6
json.users do
# json.set :user do でも良い
json.array! @users, :id, :nickname, :age
end
# {"users": [{"id": 1, "nickname": "ピカ子", "age" :18}, {"id": 3, "nickname": "ピカオ", "age": 14}, {"id": 4, "nickname": "ひよこ", "age": 22}, {"id" :5, "nickname": "ひよこ", "age": 18},{"id": 7, "nickname": "ぴよっち", "age": 22}]}
http://localhost:3000/users.jsonにアクセスすると、以下のようにusers
でまとめられて、配列に格納されたJSON文字列が返却されます。
他のデータを追加したい場合
先ほどの入れ子構造にしたJSON文字列は、users
がキーとなりUserモデルのインスタンスの全てのデータを配列で管理していましたが、usersの配列のバリューの中ではなく、usersと同じ階層にデータを追加したい場合は、以下のようにブロックの外に追加します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
json.users do
json.array! @users, :id, :nickname, :age
end
json.status "created" # ブロックの外に追加
# {
# "users":[
# {
# "id":1,
# "nickname":"ピカ子",
# "age":18
# },
# {
# "id":3,
# "nickname":"ピカオ",
# "age":14
# },
# {
# "id":4,
# "nickname":"ひよこ",
# "age":22
# },
# {
# "id":5,
# "nickname":"ひよこ",
# "age":18
# },
# {
# "id":7,
# "nickname":"ぴよっち",
# "age":22
# }],
#
# "status":"created" # usersと同じ階層
# }
以下のように、users
キーの配列に格納されたモデルのインスタンスのデータ以外に"status": "created"
のプロパティが1つ追加されます。
"status":"created"
を追加した事によって、このusersのバリューは保存された状態と表現する事ができました。
部分テンプレートを使う
jbuilderは、部分テンプレートを使って役割を分けることが出来ます。
例えば、json.array!
で解説した以下のコードは、「プロパティを指定してJSON文字列に変換」と「データを配列へ格納」の2つの役割を持ちます。
1
2
3
json.array! @users, :id, :nickname, :age
# => [{"id": 1, "nickname": "ピカ子", "age": 18}, {"id": 3, "nickname": "ピカオ", "age": 14},{"id":4,"nickname":"ひよこ","age":22}, {"id":5,"nickname":"ひよこ","age":18}, {"id":7,"nickname":"ぴよっち","age":22}]
この「プロパティを指定してJSON文字列に変換」の役割を部分テンプレート側に持たせることで、他の箇所でも利用する事が出来る汎用的なコードになります。
部分テンプレートで役割を分けてみよう
部分テンプレートでは、渡されたデータから必要な情報(各ユーザーのid, nickname, age
)だけをJSON文字列に変換出来るようにします。
まずは、app/views/users/
以下に_user.json.jbuilder
の部分テンプレートを配置します。
必要な情報は各ユーザーのid,nickname,age
だけなので、_user.json.jbuilder
は、以下のように記述します。
1
json.extract! user, :id, :nickname, :age
上記のuser
は、この部分テンプレートだけで使えるローカル変数です。
このローカル変数には、index.json.jbuilder
を以下のように記述する事で、@users
の要素を1つ1つ取り出して渡すことが出来ます。
1
json.array! @users, partial: "users/user", as: :user # 部分テンプレートを呼び出す
ここまでの流れは、以下の通りです。
そして、部分テンプレートに渡して返ってきたデータをjson.array!
で配列に格納して、以下のデータを返却します。
_user.json.jbuilder
で作成した部分テンプレートは、以下のshow.json.jbuilder
でも呼び出す事が出来ます。この場合、@user
の1つの要素を部分テンプレートに渡します。
1
2
3
json.partial! "users/user", user: @user
# {"id": 1, "nickname": "ピカ子", "age": 18},
今回は、配列を使う必要がないのでjson.partial!
で部分テンプレートを呼び出しました。
JSON文字列にするプロパティが共通する場合は、共通している箇所を部分テンプレートに切り分ける事で使い回しの出来る汎用性のある設計になります。
インスタンス変数の共有
jbuilder
でインスタンス変数
を使う場合は、インスタンス変数を共有する為にjbuilderのファイル名を「アクション名.json.jbuilder」にする必要があります。
例えば、usersコントローラのindexアクション
には、以下のように@users
のインスタンス変数が定義されています。
1
2
3
4
5
class UsersController < ApplicationController
def index
@users = User.all # このインスタンス変数をjbuilderで使う
end
end
indexアクション
で定義した@users
は、app/views/users
配下にindex.json.jbuilder
という名前のファイルを作成する事でこのインスタンスを共有することが出来ます。
1
2
3
json.array! @users, :id, :nickname, :age
# [{"id":1,"nickname":"ピカ子","age":18},{"id":3,"nickname":"ピカオ","age":14},{"id":4,"nickname":"ひよこ","age":22},{"id":5,"nickname":"ひよこ","age":18},{"id":7,"nickname":"ぴよっち","age":22}]
ファイル名が違うアクション名の場合
ファイル名がインスタンス変数
を定義したアクション名
と違う場合は、インスタンス変数は共有されません。
usersコントローラにshowアクション
を以下のように追加して確認します。
1
2
3
4
5
6
7
8
class UsersController < ApplicationController
def index
@users = User.all # このインスタンス変数をjbuilderで使う
end
def show # 追加
end
end
そして、@users
をindex.json.jbuilder
ではなくshow.json.jbuilder
使った場合は、以下のようにインスタンス変数が共有されていない為、JSON文字列ではなく空の配列が返却されます。
1
2
3
json.array! @users, :id, :nickname, :age
# []
このように、index
アクションで定義したインスタンス変数は、indexアクション名がついたファイル(index.json.jbuilder
)でのみ共有されます。
もし、showアクションで定義したインスタンス変数を使う場合は、show.json.jbuilder
のファイルを作成する事で共有することが出来ます。
インスタンス変数ではなく変数の場合
コントローラで定義した変数のデータを使うには、インスタンス変数でなければ共有されないので注意してください。
例えば、以下のようにindexアクション
のインスタンス変数(@users
)を変数(user
)に変更します。
1
2
3
4
5
6
class UsersController < ApplicationController
def index
# @users = User.all
users = User.all
end
end
index.json.jbuilder
も以下のように、インスタンス変数(@users
)を変数(user
)に変更します。
1
2
3
4
# json.array! @users, :id, :nickname, :age
json.array! users, :id, :nickname, :age
# []
そして、http://localhost:3000/users.jsonにアクセスしても、空の配列が返却されます。
このように、コントローラで定義する変数のデータをjbuilder
で使う場合は、インスタンス変数しか共有することが出来ないので注意してください。Ruby on Railsについて、もっとハイレベルな知識を身につけていきたい!という方は、こちらの書籍もおすすめです。
公式ドキュメント:jbuilder
この記事のまとめ
- jbuilderとは、JSON形式のデータを簡単に作成する事が出来るgemのこと
- RailsのGemfileにデフォルトで含まれている
- jsonオブジェクトのメソッドを使う事で、複雑なJSON形式の文字列のデータを簡単に構築する事が出来る