はじめに
- 他に便利な使い方があれば教えてもらえると幸いです
- 間違っている箇所があれば教えてもらえると幸いです
レシピ
条件付き attribute
条件でキーの有無が変わるときは下記のように書けます。
class UserSerializer < ApplicationSerializer
attribute :posts_count, if: -> { object.posts.present? } do
object.posts.count
end
end
シリアライザ内で使えるパラメーターを渡す
シリアライザにキー、バリューを渡すことで
class UsersController < Api::ApplicationController
def show
render json: current_user,
serializer: UserSerializer,
hoge: 'test'
end
end
or
ActiveModelSerializers::SerializableResource.new(
user,
serializer: UserSerializer,
adapter: :attributes,
hoge: 'test'
).serializable_hash
シリアライザ内において instance_options からその値を参照することができます。
class UserSerializer < ApplicationSerializer
attribute :test_val do
instance_options[:hoge]
end
end
個人的ベストプラクティス
シリアライザは明示的に指定した方が良さそう
シリアライザを明示的に指定なくてもモデルのクラス名から推測して UserSerializer
を使ってくれますが、
class UsersController < Api::ApplicationController
def show
render json: current_user
end
end
- UserSerializer を変更したときの影響範囲を調べるときにシリアライザ名で grep しにくい
- 一目でどのシリアライザを使っているかわかりにくい
ことからシリアライザを明示的に指定した方が良さそうです。
class UsersController < Api::ApplicationController
def show
render json: current_user,
serializer: UserSerializer
end
end
attribute とメソッドを2つ使うよりは attribute のみでキーを出力した方が良さそう
attribute とメソッド定義を2つ書くより
class UserSerializer < ApplicationSerializer
attribute :name_hoge
def name_hoge
"#{object.name}_hoge"
end
end
attribute ブロックのみを使った方がシンプルになります。
class UserSerializer < ApplicationSerializer
attribute :name_hoge do
"#{object.name}_hoge"
end
end
proc のため return は使えないことに注意してください
メソッドの処理そのままでメソッド名と異なるキー名を使いたいときは attribute を使った方が良さそう
attribute ブロックを書くより
attribute :is_admin do
object.admin?
end
attribute メソッドの key オプションを使った方がシンプルに書けます。
class UserSerializer < ApplicationSerializer
attribute :admin?, key: :is_admin
end
リソースのハッシュ出力では adapter: :attributes オプションを使ってルートキーを出力させない方が良さそう
serializable_hash[:post]
のようにキー名を指定する書き方をするとシリアライザの名前空間が変わったり、type メソッドに渡す値が変わるとキー名が変わってしまいデグレが起こる可能性があるので、ルートキーが必要ない場合は adapter: :attributes
を使用してキー名を記載しない方が良いと思います。
単一リソース
ActiveModelSerializers::SerializableResource#serializable_hash を使います。
serializer オプションにシリアライザを指定します。
class UserSerializer < ApplicationSerializer
attribute :single_post do
ActiveModelSerializers::SerializableResource.new(
object.single_post,
serializer: PostSerializer,
adapter: :attributes
).serializable_hash
end
end
複数リソース
each_serializer オプションにシリアライザを指定します。
class UserSerializer < ApplicationSerializer
attribute :posts do
ActiveModelSerializers::SerializableResource.new(
object.posts,
each_serializer: PostSerializer,
adapter: :attributes
).serializable_hash
end
end
アソシエーションをシリアライズするときは has_many, has_one, belongs_to を使うよりも手動でシリアライズした方がパラメーターを渡すことができるので良さそう
アソシエーションも返却するときは has_many などのメソッドを使えばシンプルに書くことができますが、
class UserSerializer < ApplicationSerializer
has_many :posts, serializer: PostSerializer
end
パラメーターを渡すことはできません
class UserSerializer < ApplicationSerializer
# hoge_param は PostSerializer に渡らない
has_many :posts, serializer: PostSerializer, hoge_param: 'hoge'
end
アソシエーションのシリアライザにパラメーターを渡したいときは手動でシリアライザを展開しなければなりません
class UserSerializer < ApplicationSerializer
attribute :posts do
ActiveModelSerializers::SerializableResource.new(
object.posts,
each_serializer: PostSerializer,
hoge_param:'hoge',
adapter: :attributes
).serializable_hash
end
end
ルートキーにメタ情報を追加したいときは meta オプションを使う方が良さそう
キー名にこだわりがなくルートキーと同じ階層にメタ情報を追加したいときは meta を使うと楽です。
class UsersController < Api::ApplicationController
def show
render json: current_user,
serializer: UserSerializer
meta: {time: Time.zone.now}
end
end
meta を使わない場合は hash を自前で作ることになるのでコードが煩雑になると思われます。
class UsersController < Api::ApplicationController
def show
hash = { user: ActiveModelSerializers::SerializableResource.new(current_user,
serializer: UserSerializer, adapter: :attributes).serializable_hash,
meta: {time: Time.zone.now}
}
render json: hash
end
end
ディレクトリの切り方はコントローラーに準じた方が良さそう
使いたい user serializer がコントローラーのメソッドごとに違う場合は
class UsersController < Api::ApplicationController
def index
...
end
def show
...
end
end
serializers
配下にフラットにシリアライザファイルを作成するよりも
app/serializers/index_user_serializer.rb
app/serializers/show_user_serializer.rb
返却されるオブジェクトはAPIのエンドポイントに依存しているので、コントローラーのクラス名の名前空間、メソッドに応じてディレクトリを切った方がわかりやすいと思っています。
app/serializers/users/index/user_serializer.rb
app/serializers/users/show/user_serializer.rb
汎用的ではなく1つのエンドポイントでしか使われないシリアライザはシリアライザ内にクラス定義した方が良さそう
1つのエンドポイントでしか使われない特殊なシリアライザならばファイルに切り出して、コードを参照するときに別ファイルを見にいくよりも
app/serializers/special_post_serializer.rb
シリアライザクラス内にシリアライザを定義した方がコードを追いやすいかもしれません。
class UserSerializer < ApplicationSerializer
attribute :special_post do
ActiveModelSerializers::SerializableResource.new(
object.posts,
serializer: UserSerializer::SpecialPostSerializer,
adapter: :attributes
).serializable_hash
end
class SpecialPostSerializer < ApplicationSerializer
attribute :special_post_key do
'value'
end
end
end
名前空間が変わらないので基本的には type を指定した方が良さそう
type メソッドを使うことでルートキーの名前を固定することができます。
class UserSerializer < ApplicationSerializer
type 'user'
attribute :name
end
ActiveModelSerializers::SerializableResource.new(
user,
serializer: UserSerializer,
).serializable_hash
=> {user: {...}}
type を指定しないとシリアライザの名前空間によってルートキーの名前が変わってしまいます。
class V2::UserSerializer < ApplicationSerializer
attribute :name
end
ActiveModelSerializers::SerializableResource.new(
user,
serializer: V2::UserSerializer,
).serializable_hash
=> {'v2/user' => {...}}
名前空間に関わらず一意のリソースの名前を使うことの方が多いと思うので type は指定した方が良いと思われます。
# ルートキーが変わるとこのようなコードがデグレを起こす可能性がある
user_res = ActiveModelSerializers::SerializableResource.new(...).serializable_hash
user_res[:user][:name]
シリアライザ内でデータの出どころがわかりにくいので scope は使わない方が良さそう
scope を使用すると指定した情報をそのままシリアライザに持っていくことができますが、下記の例の場合 current_post がどこから来たのかがわかりにくいため
class UsersController < Api::ApplicationController
def show
render json: current_user,
serializer: UserSerializer,
scope: current_user.current_post,
scope_name: :current_post
end
end
class UserSerializer < ApplicationSerializer
attribute :post_name do
current_post.name
end
end
通常のパラメーターで渡した方がわかりやすいと思っています。
class UsersController < Api::ApplicationController
def show
render json: current_user,
serializer: UserSerializer,
current_post: current_user.current_post
end
end
class UserSerializer < ApplicationSerializer
attribute :post_name do
instance_options[:current_post].name
end
end
has_many のアソシエーションで order を使うときはそれ専用のアソシエーションをモデルで定義した方が良さそう
下記記事を参照してください。
has_manyで紐づくモデルをSerializerの中でorderするとSQLキャッシュのN+1が起きる問題をなんとかしたい