🥞
active_model_serializersをRails標準のActiveModel::Serializers::JSONで置きかえる
動機
active_model_serializersは十分にメンテナンスされていないので、乗り換え先を探していた。しかし、あまり良さげな乗り換え先がなかった。
そんな時以下の記事で、Railsが標準で実装しているActiveModel::Serializers::JSON
の存在を知った。なのでこれに乗り換えることにした。
要件
- active_model_serializersと同じくらいの書き心地を目指す
- とはいえ
has_many
とかは妥協する
- とはいえ
BaseSerializer
を実装
1. 各Serializer
が継承する基底クラスを実装する。
class BaseSerializer
include ActiveModel::Serializers::JSON
def initialize(*_args)
# ここでkeyに対応するmethodが実装されていることを求められるので、各インスタンス変数のattr_readerを定義。(もっと簡単に書く方法が知りたい)
# https://github.com/rails/rails/blob/1c2529b9a6ba5a1eff58be0d0373d7d9d401015b/activemodel/lib/active_model/serialization.rb#L172-L176
define_attr_readers
end
private
def define_attr_readers
instance_variable_names.each do |instance_variable_name|
self.class.send(:attr_reader, instance_variable_name) unless self.class.method_defined?(instance_variable_name)
end
end
# @return [Array<String>] all instance variable names
def instance_variable_names
instance_variables.map { |ivar| ivar.to_s.delete_prefix('@') }
end
# override `ActiveModel::Serialization#attribute_names_for_serialization`
# https://github.com/rails/rails/blob/1c2529b9a6ba5a1eff58be0d0373d7d9d401015b/activemodel/lib/active_model/serialization.rb#L152-L154
def attribute_names_for_serialization
instance_variable_names
end
end
<details>
<summary>テストコード</summary>
RSpec.describe BaseSerializer do
let(:klass) do
Class.new(described_class) do
def initialize(record1:, record2:)
@record1 = Record1Serializer.new(record1)
@record2 = Record2Serializer.new(record2)
super
end
class Record1Serializer < BaseSerializer
def initialize(record1)
@str = record1[:str]
@number = record1[:number]
super
end
end
class Record2Serializer < BaseSerializer
def initialize(record2)
@str = record2[:str]
@bool = record2[:bool]
super
end
end
end
end
it do
expect(
klass.new(
record1: { str: 'str1', number: 1 },
record2: { str: 'str2', bool: true }
).as_json
).to eq(
{
'record1' => { 'str' => 'str1', 'number' => 1 },
'record2' => { 'str' => 'str2', 'bool' => true }
}
)
end
end
</details>
ActiveModel::Serializer
をBaseSerializer
で置き換える
2. Before
class UsersController
def show
users = User.find(id)
render json: user
end
end
class UserSerializer < ActiveModel::Serializer
attribute :id
attribute :name
end
After
class UsersController
def show
user = User.find(id)
render json: UserSerializer.new(user)
end
end
class UserSerializer < BaseSerializer
# @param [User] user
def initialize(user)
@id = user.id
@name = user.name
super
end
end
おわり
- そこまで書き心地を損なわずに移行できた
- 依存を一つ減らせたので満足
- 一部エンドポイントでは少しだけ早くなった
Discussion