すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Rails

【Rails】jbuilderの使い方辞典〜メソッドの文法と使い方がすぐわかる

ぴっかちゃん
ぴっかちゃん

jbuilderとは、RailsのGemfileにデフォルトで含まれている「JSON形式のデータを簡単に作成する事が出来るgemのこと」です。

app/views以下に〇〇.json.jbuilderのファイルを作成して、jbuilderのオブジェクトのjsonを使うと、JSON形式の文字列のデータを作成する事が出来ます。

例えば、app/views/users/index.json.jbuilderにUserモデルの全てのインスタンスが格納された@usersを使って、下記の様に記述します。

app/views/users/index.json.jbuilder | 配列に格納されたJSONデータを作成する
1
2
# @users = User.all
json.array! @users, :id, :nickname, :age

そして、http://localhost:3000/users.jsonにアクセスするだけで、Userモデルの全てのインスタンスが、配列に格納されたJSON形式の文字列のデータで返却する事が出来ます。

jbuilderでjson形式のデータの表示結果

上記のjson.array!の様に、jsonオブジェクトに対して様々なメソッドを使う事で複雑なJSON形式の文字列のデータを簡単に構築する事が出来ます。

JSONについて詳細は、「ゼロから始めるJSON入門」を参考にしてください。

jbuilderの基本的な使い方

この章では、jbuilderの基本的な使い方について解説していきます。

json.キー名

json.キー名 "値"とする事で名前(キー名): 値がペアとなったJSON形式の文字列が返却されます。

〇〇.json.jbuilder | 単数のデータを返す場合
1
2
json.キー名 "値"
# {"キー名": "値"}

例えば、index.json.jbuilderを以下のように記述して、http://localhost:3000/users.jsonにアクセスすると、次の画像のように{"name":"ピカ子"}が返却されます。

app/views/users/index.json.jbuilder | 単数のデータを返す場合
1
2
json.name "ピカ子"
# {"name": "ピカ子"}

単純なキー、バリューの結果

複数のデータを返す場合

名前(キー名)/値がペアとなった複数のプロパティを返す場合は、以下のように記述します。

〇〇.json.jbuilder | 複数のデータを返す場合
1
2
3
4
json.キー名1 "値1"
json.キー名2 "値2"

# {"キー名1": "値1", "キー名2" : "値2"}
app/views/users/index.json.jbuilder | 複数のデータを返す場合
1
2
3
4
json.name "ピカ子"
json.age 18

# {"name": "ピカ子", "age": 18}

入れ子構造にする場合

入れ子構造にして属性をまとめたい場合は、以下のように記述します。

〇〇.json.jbuilder | 入れ子構造にする場合
1
2
3
4
5
json.親のキー名 do
  json.子のキー名 "値"
end

# {"親のキー名": {"子のキー名": "値"}}

例えば、以下のように記述する事で、1人のユーザー情報をまとめる事が出来ます。

app/views/users/index.json.jbuilder | 入れ子構造にする場合
1
2
3
4
5
6
json.user do
  json.name "ピカ子"
  json.age 18
end

# {"user": {"name": "ピカ子", "age": 18}}

また、複数の入れ子構造のデータを返却する場合は、以下のように記述します。

app/views/users/index.json.jbuilder | 複数の入れ子構造のデータを返却する場合
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形式の文字列に変換されます。

app/views/users/index.json.jbuilder | キー名が重複した場合
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形式の文字列を返却します。

〇〇.json.jbuilder | 単数のデータを返す場合
1
2
3
4
json.set! "キー名", "値"
# json.キー名 "値"  でも同じデータを返却出来る

# {"キー名" :"値"} 

これは、先ほどのjson.キー名を使った場合と同一の結果を得られます。

app/views/users/index.json.jbuilder | 単数のデータを返す場合
1
2
3
4
json.set! "name", "ピカ子"
# json.name "ピカ子" と同一の結果になる

# {"name": "ピカ子"} 

キー名をシンボルで指定する

第一引数のキー名には、以下のように文字列の代わりにシンボルで指定する事が出来ます。

〇〇.json.jbuilder | 単数のデータを返す場合
1
2
3
4
json.set! "キー名", "値" # キー名が文字列の場合
json.set! :キー名, "値" # キー名がシンボルの場合

# {"キー名" :"値"} #  どちらも同一の結果になる

以下のように、先ほどのコードのキー名文字列からシンボルに変更しても同一の結果を得られます。

app/views/users/index.json.jbuilder | キー名をシンボルにした場合
1
2
3
json.set! :name, "ピカ子"  # キー名がシンボルの場合

# {"name": "ピカ子"}

入れ子構造にする場合

json.キー名と同様にjson.set!で入れ子構造にする場合は、以下のように記述します。

〇〇.json.jbuilder | 入れ子構造にする場合
1
2
3
4
5
6
json.set! :親のキー名 do
  json.子のキー名 "値"
  # json.set! 子のキー名 "値"  でも良い
end

# {"親のキー名": {"子のキー名": "値"}}

json.子のキー名の箇所は、json.set!にしても同一の結果を得られます。

〇〇.json.jbuilder | 入れ子構造にする場合
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}}

また、複数の入れ子構造のデータを返却する場合は、以下のように記述します。

app/views/users/index.json.jbuilder | 複数の入れ子構造のデータを返却する場合
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メソッドを使って、以下のように記述することで動的にキー名を定義する事が出来ます。

〇〇.json.jbuilder | 動的にキー名を定義する
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は返却されません。

〇〇.json.jbuilder | 動的にキー名を定義出来ない場合
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!を使うようにしましょう。

インスタンス変数のデータを使う場合

この章では、インスタンス変数のデータを使う場合に便利なメソッドを解説します。

解説には、以下のインスタンス変数を使います。

app/controllers/users_controller.rb | 各インスタンス変数を定義
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

テーブルの中身は以下の通りです。

usersテーブルの中身

基本的な使い方

インスタンス変数のデータをjbuilderで使うには、以下のように記述します。

app/views/users/show.json.jbuilder | 単数のデータを返す場合
1
2
3
json.nickname @user.nickname

# {"nickname":"ピカ子"}

http://localhost:3000/users/1.jsonにアクセスすると、指定したインスタンス変数のデータ(usersテーブルのid=1のnickname)がJSON文字列で返却されます。

指定したインスタンス変数のデータを返却する

複数のデータを返却する場合

インスタンス変数の複数のデータを返却するには、以下のように記述します。

app/views/users/show.json.jbuilder | 複数のデータを返す場合
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文字列で返却されます。

複数のデータを返却する場合

入れ子構造にする場合

入れ子構造にして属性をまとめたい場合は、以下のように記述します。

app/views/users/show.json.jbuilder |インスタンス変数を使って入れ子構造にする場合
1
2
3
json.user @user, :id, :nickname, :age

# {"user": {"id": 1, "nickname": "ピカ子", "age": 18}}

@userの後に、JSON文字列にしたいデータのプロパティ名を指定します。

インスタンス変数の要素が複数の場合

インスタンス変数の要素が複数の場合は、以下の様に記述します。

app/views/users/index.json.jbuilder |インスタンス変数の要素が複数の場合
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形式の文字列で返却します。

app/views/users/show.json.jbuilder |インスタンス変数のデータをまとめて返却する場合
1
2
3
json.extract! @user, :id, :nickname, :age

# {"id":1,"nickname":"ピカ子","age":18}

先ほどのjson.キー名では、インスタンス変数を取り出すために以下のように1行1行記述しキー名と値をそれぞれ指定しなければなりませんでしたが、json.extract!では簡潔に1行で記述することが出来ます。

app/views/users/show.json.jbuilder | 複数のデータを返す場合
1
2
3
4
5
json.id @user.id
json.nickname @user.nickname
json.age @user.age

# {"id": 1, "nickname": "ピカ子", "age" :18}

入れ子構造にする場合

入れ子構造にして属性をまとめたい場合は、以下のように記述します。

app/views/users/show.json.jbuilder |インスタンス変数のデータをまとめて返却する場合
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文字列のデータに変換して追加します。

app/views/users/index.json.jbuilder | ハッシュを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メソッドは、以下のようにモデルのインスタンスの中身をハッシュにして返します。

コンソール | インスタンスに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文字列に変換する事ができます。

app/views/users/show.json.jbuilder |インスタンス変数のデータをまとめて返却する場合
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を使って解説します。

app/controllers/users_controller.rb | indexアクションでインスタンス変数を定義
1
2
3
4
5
class UsersController < ApplicationController
  def index
    @users = User.all
  end
end

json.array!は、以下のように記述することで、Userモデルの全てのインスタンスが、配列に格納されたJSON形式の文字列のデータで返却する事が出来ます。

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}]

http://localhost:3000/users.jsonにアクセスすると、以下のように配列に格納されたJSON文字列が返却されます。

配列に格納されてJSON文字列が返却される

入れ子構造にする場合

json.array!は、よく入れ子構造にして属性をまとめて使われることが多いです。

以下のように記述すると、入れ子構造にしてJSONデータを配列に格納することが出来ます。

app/views/users/index.json.jbuilder | 入れ子構造にする場合
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と同じ階層にデータを追加したい場合は、以下のようにブロックの外に追加します。

app/views/users/index.json.jbuilder | 他のデータを追加したい場合
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つの役割を持ちます。

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}]

配列に格納されてJSON文字列が返却される

この「プロパティを指定してJSON文字列に変換」の役割を部分テンプレート側に持たせることで、他の箇所でも利用する事が出来る汎用的なコードになります。

部分テンプレートで役割を分けてみよう

部分テンプレートでは、渡されたデータから必要な情報(各ユーザーのid, nickname, age)だけをJSON文字列に変換出来るようにします。

まずは、app/views/users/以下に_user.json.jbuilderの部分テンプレートを配置します。

部分テンプレートを配置する

必要な情報は各ユーザーのid,nickname,ageだけなので、_user.json.jbuilderは、以下のように記述します。

app/views/users/_user.json.jbuilder | JSON文字列にするプロパティを指定して変換する
1
json.extract! user, :id, :nickname, :age

上記のuserは、この部分テンプレートだけで使えるローカル変数です。

このローカル変数には、index.json.jbuilderを以下のように記述する事で、@usersの要素を1つ1つ取り出して渡すことが出来ます。

app/views/users/index.json.jbuilder | @usersのデータを1つ1つ取り出して、部分テンプレートに渡す
1
json.array! @users, partial: "users/user", as: :user # 部分テンプレートを呼び出す

ここまでの流れは、以下の通りです。

そして、部分テンプレートに渡して返ってきたデータをjson.array!で配列に格納して、以下のデータを返却します。

配列に格納されてJSON文字列が返却される

_user.json.jbuilderで作成した部分テンプレートは、以下のshow.json.jbuilderでも呼び出す事が出来ます。この場合、@userの1つの要素を部分テンプレートに渡します。

app/views/users/show.json.jbuilder | 部分テンプレートを使う
1
2
3
json.partial! "users/user", user: @user

# {"id": 1, "nickname": "ピカ子", "age": 18},

今回は、配列を使う必要がないのでjson.partial!で部分テンプレートを呼び出しました。

JSON文字列にするプロパティが共通する場合は、共通している箇所を部分テンプレートに切り分ける事で使い回しの出来る汎用性のある設計になります。

インスタンス変数の共有

jbuilderインスタンス変数を使う場合は、インスタンス変数を共有する為にjbuilderのファイル名を「アクション名.json.jbuilder」にする必要があります。

例えば、usersコントローラのindexアクションには、以下のように@usersのインスタンス変数が定義されています。

app/controllers/users_controller.rb | indexアクションでインスタンス変数を定義
1
2
3
4
5
class UsersController < ApplicationController
  def index
    @users = User.all # このインスタンス変数をjbuilderで使う
  end
end

indexアクションで定義した@usersは、app/views/users配下にindex.json.jbuilderという名前のファイルを作成する事でこのインスタンスを共有することが出来ます。

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アクションを以下のように追加して確認します。

app/controllers/users_controller.rb | indexアクションでインスタンス変数を定義
1
2
3
4
5
6
7
8
class UsersController < ApplicationController
  def index
    @users = User.all # このインスタンス変数をjbuilderで使う
  end

  def show # 追加
  end
end

そして、@usersindex.json.jbuilderではなくshow.json.jbuilder使った場合は、以下のようにインスタンス変数が共有されていない為、JSON文字列ではなく空の配列が返却されます。

app/views/users/show.json.jbuilder| インスタンス変数のデータを使う
1
2
3
json.array! @users, :id, :nickname, :age

# []

このように、indexアクションで定義したインスタンス変数は、indexアクション名がついたファイル(index.json.jbuilder)でのみ共有されます。

もし、showアクションで定義したインスタンス変数を使う場合は、show.json.jbuilderのファイルを作成する事で共有することが出来ます。

インスタンス変数ではなく変数の場合

コントローラで定義した変数のデータを使うには、インスタンス変数でなければ共有されないので注意してください。

例えば、以下のようにindexアクションのインスタンス変数(@users)を変数(user)に変更します。

app/controllers/users_controller.rb | インスタンス変数(@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)に変更します。

app/views/users/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で使う場合は、インスタンス変数しか共有することが出来ないので注意してください。

公式ドキュメント:jbuilder

この記事のまとめ

  • jbuilderとは、JSON形式のデータを簡単に作成する事が出来るgemのこと
  • RailsのGemfileにデフォルトで含まれている
  • jsonオブジェクトのメソッドを使う事で、複雑なJSON形式の文字列のデータを簡単に構築する事が出来る

9

わかった!