RABLの使い方を丁寧に説明

RABLの使い方を丁寧に説明

Table of Contents

この記事ではrablの書き方を毎回忘れてしまう人や初めて耳にしてrablの使い方を知りたい人向けにrablの導入からrablの書き方まで詳しく説明します。

流れは大きく次の順番で説明していきます。

  • rablとは?
  • rabl環境構築
  • rabl DSLの書き方

rablとは?

  • RABL(Ruby API Builder Language)
  • Rails向けRuby用テンプレートシステム
  • json, bson, xml, plist, msgpackサポート
  • 表示データとその表現を分離させている
  • GitHubはnesquena/rabl
  • RubyGemsはrabl

rabl環境の構築

  1. gem用意
  2. 設定ファイル登録
  3. rablファイル用意

gem用意

Gemfileにgemを追加します。

gem 'rabl'

bundle installを忘れずに。

設定ファイル登録

config/initializersrabl_init.rbを追加します。

内容は最低限下記を記載します。

require 'rabl'

Rabl.configure do |config|

end

まずは設定は特になしで問題ありません。後ほど一部設定項目について説明します。

rablファイル用意

viewsフォルダに例えばindex.rablを用意します。
レスポンスがJSONのControllerであれば直接.rablでも動きます。

rablに慣れる

まずは簡単なrablの書き方から入ってrablに触れて少し慣れましょう。

最初のrabl

まずはrablで最も単純なrabl記法です。

node(:key) { "value" }

結果はこれになります。

{"key":"value"}

Rubyオブジェクトの値を表示する

次は先程のnodeを使ってRubyオブジェクトを表示します。
手元で動かす場合は、ControllerやModelは適宜用意してください。

Model側:

create_table :users do |t|
  t.string :name
  t.date :birth_date
  t.integer :score
  t.timestamps
end

Controller側:

class UsersController < ApplicationController
  def show
    @user = User.first
  end
end

View側:

node(:name) { @user.name }
node(:birth_date) { @user.birth_date }
node(:score) { @user.score }

$ http get "http://localhost:3000/users/1"のレスポンスです。※

{
  "name": "A",
  "birth_date": "2020-05-05",
  "score": 1
}

$ http getはHTTPieといってcurlよりも書き方が直感的なCLIツールです。気になる方は、「クライアントエンジニアにはcurlよりHTTPieがお薦め 」にまとめてあります。

rabl上でRubyコードを書く

rablファイル上ではRubyコードを書くことが可能です。
次のコードはユーザー数が2つなら複数モデルを表示します。

class UsersController < ApplicationController
  def index
    @users = User.all
  end
end

rablコード

if @users.count == 2
  node(:users) do
    @users.map do |user|
      {
        name: user.name,
        birth_date: user.birth_date,
        score: user.score
      }
    end
  end
end

レスポンス

{
  "users": [
    {
      "name": "A",
      "birth_date": "2020-05-05",
      "score": 1
    },
    {
      "name": "B",
      "birth_date": "2020-05-04",
      "score": 3
    }
  ]
}

nodeのブロック内がRubyコードを評価されます。
このブロック内ではrabl記法は評価されません。

nodeだけでは本領発揮

nodeではrablの本領を発揮していません。 rablは通常は表示データはモデルであり、レスポンスはRuby DSLを使ってrablファイルに記述します。

rablのobjectを使う

ActiveRecordのUserを表示する場合は次のrablコードになります。

object @user
attributes :name, :score
attribute :birth_date => 'birth_day'
node(:down_case_name) { |user| user.name.downcase }

これのレスポンスです。

{
  "user": {
    "name": "A",
    "score": 1,
    "birth_day": "2020-05-05",
    "upper_name": "a"
  }
}

objectにモデルを渡すことで表示のためのDSLがそのモデルを情報として使うようになります。

  • attributesの渡してる文字列はJSONとモデルで使われます
    • JSONのメンバー名になる
    • モデルのカラム名として呼ばれる
    • Hash構造を渡せばJSONのメンバー名とモデルのメソッド名を分けれます。
  • nodeも使えます。
    • nodeにモデルが引数として渡されます。

rablはモデルのメソッドを呼んでいる

rablのattributesはカラム値を取得してるのではなく、
ARモデルのメソッドを呼んで結果を取得しています。
なので先程のnode(:down_case_name)はメソッド名としてARモデル側に用意すればattributesとして指定できます。

ARモデル:

class User < ApplicationRecord
  def down_case_name
    name.downcase
  end
end

rabl:

object @user
attributes :name, :score
attribute :birth_date => 'birth_day'
attribute :down_case_name

ルートノードを外すまたは変える

objectを使ったレスポンスではルートノードにuserという括りがされています。
これを違う名前にしたかったり、外したい場合はobjectにオプションを渡します。

無指定だと

object @user # user:

userが入ります

{"user":{"name":"A","score":1,"birth_day":"2020-05-05"}}

Hash形式でシンボル値を渡すと

object @user => :gamer

渡したシンボル名になります。

{"gamer":{"name":"A","score":1,"birth_day":"2020-05-05"}}

※シンボル指定です。文字列だとstringになります。

falseを渡すと

object @user => false

ルートノードはなしになります。

{"name":"A","score":1,"birth_day":"2020-05-05"}

になります。

rablのcollectionを使う

objectがモデル1つだとしたら、collectionは複数形です。

最初に載せたNodeを使った複数Userを表示するrablをcollectionを使うとこうなります。

collection @users
attributes :id, :birth_date, :name

結果はこうなります。

[
  {
    "user": {
      "id": 1,
      "birth_date": "2020-05-05",
      "name": "A"
    }
  },
  {
    "user": {
      "id": 2,
      "birth_date": "2020-05-04",
      "name": "B"
    }
  }
]

オブジェクトルートを変更または消す

モデル毎にuserというオブジェクトルートが入っているのでこれを消すにはcollectionにオプションを渡します。

collection @users, :object_root => false

objectと同じでfalseではなくシンボルを渡せばuserではなく別名に変更できます。

ルートノードをつける

ルートノードをつけたい場合はrootオプションを渡します。

collection @users, :root => 'gamers', :object_root => false

結果はこうなります。

{
  "gamers": [
    {
      "id": 1,
      "birth_date": "2020-05-05",
      "name": "A"
    },
    {
      "id": 2,
      "birth_date": "2020-05-04",
      "name": "B"
    }
  ]
}

rablのchildを使う

objectとcollectionはルートレベルにモデルがマッピングされている場合に使えます。
しかし次のルートレベルにモデル以外のレスポンスが必要なケースがあります。

{
  "count": 2,
  "users": [
    {
      "id": 1,
      "birth_date": "2020-05-05",
      "name": "A"    
    },
    {
      "id": 2,
      "birth_date": "2020-05-04",
      "name": "B"
    }
  ]
}

この場合は、objectfalseにしてnode形式とchildを使います。

object false

node(:count) { @users.count }
child(@users, object_root: false) do
  attributes :id, :birth_date, :name
end

object_rootfalseにしておかないとモデル毎にuserというキーが入ります。

親子関係はchildを使う

Userモデルと住所モデルがあり、Userが住所を複数個持っている関係とした場合

class User < ApplicationRecord
  has_many :addresses, inverse_of: :user
end

class Address < ApplicationRecord
  belongs_to :user, inverse_of: :addresses
end

rablではchildを使います。

collection @users, :object_root => false
attributes :id, :name, :birth_date

child(:addresses, object_root: false) do
  attributes :post_code
end

ルートレベルにオブジェクトが紐付いているため、シンボルを渡すことでそのオブジェクトのメソッドとして評価されます。   次のコードと同じ意味になります。

child(@user.addresses, object_root: false)

rablのglueを使う

glueとはモデルとはリレーションシップのないモデルを繋ぎます。
glueの和訳は「接着剤」または「接着する」するです。
つまりあるデータとあるデータをくっつける場合に使います。

Controller側で異なるUserのアドレスを1件取っておきます。

def show
  @user = User.first
  @other_address = User.second.address.first
end

rabl側ではglueを使って@other_addressの情報を@userの情報と合わせて一つのレスポンスとして返します。

object @user
attributes :name, :score, :birth_date

child(:addresses, object_root: false) do
  attributes :post_code
end

glue @other_addresses do
  attributes :post_code => :other_post_code
end

レスポンスでは無関係なモデルが1つのレスポンスとしてかえってきます。

{
  "user": {
    "name": "A",
    "score": 10,
    "birth_date": "2020-05-05",
    "addresses": [
      {
        "post_code": "1680064"
      }
    ],
    "other_post_code": "5680068"
  }
}

rablのextendsを使う

rablで部分テンプレート(partial)を使うにはextendsを使います。

次のコードはUser単体を表示するrablです。
rablの場所をusers/showとします。

object @user
attributes :name, :score, :birth_date

child(:addresses, object_root: false) do
  attributes :post_code
end

次のコードはUser一覧表示を上のrablをextendsを使って表示するrablです。

collection @users, :object_root => false
extends 'users/show'

extends先のobjectやcollectionは上書きされる

extends先のrablのobjectやcollectionは呼び出し元に渡されたオブジェクトになります。
上記の場合は単体のUserモデルが渡されることになります。

child内のextendsでも同じです。

下記rablファイル(addresses/index)があった場合に

collection @addresses
attributes :post_code

Userモデルからextendsで呼ばれたら、collectionにはUserモデルのaddressesが入ります。

child(:addresses, object_root: false) do
  extends 'addresses/index'
end

呼び出し元でオブジェクトを指定することもできます。

extends "addresses/index", object: @other_addresses

rablでcacheを使う

rablでフラグメントキャッシュを使うにはcacheを使います。
デフォルトでは、Railsのキャッシュシステムを使います。
利用にはキャッシュ用のキーが必要になります。

キャッシュキー

キーは文字列になります。

文字列以外を渡す場合は、文字列へ変換されます。例えば

  • モデルを渡す場合はcache_keyメソッドが定義されて文字列を返す必要があります。
  • 配列の場合は先頭から各要素を文字列変換後にスラッシュ(/)で結合します。
cache 'lists' # lists
cache @user # @user.cache_key
cache ['hoge', @user] # "hoge/#{@user.cache_key}"

キャッシュ期限

オプションでexpires_inを渡すことでキャッシュの期限を設定できます。

cache @user, expires_in: 1.hour

extends先のキャッシュ

cacheコマンドはextends先のrablでも使えます。

自身の呼び出し元のオブジェクトを参照したい場合はroot_objectを使います。

cache ["list", root_object]
attributes :id, :name

ルートノードの表示のデフォルト値を設定する

ルートノードはオブジェクトルートの表示をrabl毎に設定せずに一律デフォルト値を設定する場合は
/config/initializers/rabl_init.rbに次の2つを指定することでデフォルト値の変更ができます。

Rabl.configure do ||config|
  config.include_json_root = false
  config.include_child_root = false
end

他にもrablの設定では

  • 存在しない属性にRuntimeErrorを発生させる(productionはtrue非推奨)
  • nilは空の文字列に変換する
  • nilの場合はレスポンスから除外

などの変更ができます。

# 存在しない属性にRuntimeErrorを発生させる(productionはtrue非推奨)
config.raise_onmissing_attribute = true

# nilは空の文字列に変換する
config.replace_nil_values_with_empty_strings = true

# nilの場合はレスポンスから除外
config.exclude_nil_values = true

詳しくはGitHubのConfigurationにて説明されています。

rablは使いにくい

個人的にはRuby on Railsでrablは採用しないほうがいいです。

  • DSLの書き方が独自なため流動性が低い
  • repositoryが活発的とは言えない
  • ARモデル表示に特化しすぎていてページネーションとかARモデル外の柔軟性が低い
  • 日本語記事が少ない

この記事を書いたのも仕事柄どうしてもrablで書かないといけないケースがあって、そのたびに忘れてしまい生産性が下がるのでまとめた次第です。

このエントリーをはてなブックマークに追加