暇人じゃない

ActiveModelSerializers を 0.9 から 0.10 にアップデートしたメモ

ActiveModelSerializers を 0.9 (0.9.5) から 0.10 (0.10.0) にアップデートした時のメモです。 0.10 で API が大幅に変更されたため、アプリケーション側にも修正を加える必要がありました。

しかし、アップデートが完全に終わったわけではなく、0.10 はアソシエーションの扱いがガラッと変わっているため、 その部分だけ作業中となっています (内容については記事の一番下に書いています)。

ActiveModelSerializers を使用しているアプリケーションで、アップデートを行った時に対応した内容を以下に記載します。

initializer の root キー設定

root キーの設定はデフォルトで false になりました。
以下のように initializer に root キーを含めない設定を書いている場合は削除します。

config/initializers/active_model_serializers.rb:

ActiveModel::Serializer.root = false
ActiveModel::ArraySerializer.root = false

ActiveModel::ArraySerializerActiveModel::Serializer::CollectionSerializer

each_serializer オプションが serializer に変わったため、以下のような書き方になります:

serializer = ActiveModel::Serializer::CollectionSerializer.new(
  posts,
  serializer: PostSerializer,
  # ...
)

serialization_optionsinstance_options

Serializer 中で使用するデータを参照するための変数名が変更になりました。

def foo
  object.bar(instance_options[:baz])
end

データを渡す方法も変更になっています。

0.9.5:

serializer = PostSerializer.new(@post)
post = serializer.as_json(foo: foo, bar: bar)

0.10.0:

serializer = PostSerializer.new(@post, foo: foo, bar: bar)
post = serializer.as_json

メソッドが評価されるタイミング

0.10 では Serializer にデータを渡したタイミングで attribute で定義されたメソッドが評価されるようになりました。以下のように UserSerializer に渡す時にメソッド内の objectnil の時にエラーになってしまいます。この場合 Serializer を呼び出さないような考慮が必要です。

def user
  return nil if object.user.nil?

  serializer = UserSerializer.new(object.user)
  serializer.as_json
end

使用しないキーを除外する filter

キーを含めるかどうかの条件は attributehas_one メソッドの if オプションに書くようになりました。

0.9.5:

attributes :id, :body

has_one :foo, serializer: FooSerializer

def filter(keys)
  keys.delete :foo unless object.foo?
end

0.10.0:

attributes :id
attribute :foo_id, if: -> { object.foo? }

has_one :foo, serializer: FooSerializer, if: -> { object.foo? }

アソシエーションのキー名を指定するオプション名変更

rootkey に変更されました。

0.10.0:

has_one :foo, serializer: FooSerializer, key: :bar

番外: アソシエーション周りの問題

モデルのアソシエーションではなく、Serializer のメソッドをアソシエーションに指定した場合に Serializer が使用されず単純に as_json した結果が出力される、という現象です。

カテゴリとサブカテゴリがあったとして、以下のような書き方をした時に発生します。

has_one :category, serializer: CategorySerializer

def category
  object.root # ancestry gem
end

これは以下のように書くと一応実現できるのですが、冗長な感じになります。

attribute :category, if: -> { object.respond_to?(:root) }

def category
  return nil if object.root.nil?
  CategorySerializer.new(object.root)
end

0.9 で偶然書けていただけで 0.10 の挙動が正しいのかもしれません…
自分でも整理できていないところなので、詳しいことが分かったら更新します。