active_model_serializers をデータベースと連動しないモデルで使うには

こんにちは、「天下一品はカロリーが低い」と教えてもらったきたけーです(一週間に三杯たべました)。

RailsJSONをレスポンスとして返すときに有用な active_model_serializersデータベースと連動しないモデル(ActiveRecord::Baseを継承していないモデル)でも使いたかったので、そのときのメモ。

とりあえずシリアライザを用意してみる

今回は(あまり良い例ではないですが)適当に以下のようなEntryモデルで解説します。

# app/models/entry.rb

class Entry
  include ActiveModel::Model
  attr_accessor :title, :content
end

まず、ActiveRecord::Baseを継承したモデルで使う時と同様にシリアライザのクラスを用意します。

# app/serializers/entry_serializer.rb
class EntrySerializer < ActiveModel::Serializer
  attributes :title, :content
end

適当なアクションで以下のように書きます。

def show
  @entry = Entry.new
  @entry.title = "hi"
  @entry.content = "ほげほげ"
  render json: @entry
end

アクセスしてみると

{"title":"こんにちは","content":"ほげほげ"}

おや... 単純に@entryオブジェクトのto_jsonメソッドが呼ばれた結果に。意図していた結果は

{"entry":{"title":"こんにちは","content":"ほげほげ"}}

だったので何かがうまくいっていないようです

必要なモジュールのincludeとメソッドの定義

ドキュメントをみてみると

Objects that respond to read_attribute_for_serialization (including ActiveModel and   
ActiveRecord objects) are supported

とあるのでread_attribute_for_serializationメソッドが定義されていないといけないようです。 read_attribute_for_serializationメソッドはActiveModel::Serializationモジュールに定義されているので、これをincludeします。

# app/models/entry.rb

class Entry
  include ActiveModel::Model
  include ActiveModel::Serialization
  attr_accessor :title, :content
end

これでどうだ! ...まだ、うまく動きません。
active_model_serializers のコードを読むことにします。
レンダリングスタック群の_render_option_jsonメソッドをオーバーライドしているようです(https://github.com/rails-api/active_model_serializers/blob/0-8-stable/lib/action_controller/serialization.rb#L43)。
メソッド内でActiveModel::Serializer.build_jsonを読んでいるので追ってみると、 モデルのオブジェクトのactive_model_serializerメソッドから使うシリアライザを取得しているようです(https://github.com/rails-api/active_model_serializers/blob/0-8-stable/lib/active_model/serializer.rb#L264)。

Entryクラスにactive_model_serializerメソッドを生やします。

# app/models/entry.rb

class Entry
  include ActiveModel::Model
  include ActiveModel::Serialization
  attr_accessor :title, :content

  # 0.9系では定義する必要はない
  def active_model_serializer
    ::EntrySerializer
  end
end

...うまくいきました!

ちなみにActiveRecord::Baseを継承しているモデルで、active_model_serializerメソッドを生やす必要がないのは、to_jsonメソッドをオーバーライドしているからなのでした(https://github.com/rails-api/active_model_serializers/blob/0-8-stable/lib/active_record/serializer_override.rb#L6)。

0.8系 と 0.9系 での違い

現在、Ruby gemsに公開されているのは0.8系ですが、Githubをみると分かるように0.9系がmasterブランチで開発が進められています。

0.9系では、実装が大幅に変わっていて、serialize_forメソッドがシリアライザのクラスを探してくれるので(https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer.rb#L47)、active_model_serializerメソッドを生やす必要はありません。

まとめ

データベースと連動しないモデルで active_model_serializers を使うには

  • モデルのクラスでActiveModel::Serializationをincludeする
  • 0.8系では、モデルにシリアライザクラスを返すactive_model_serializerメソッドを生やす(開発版の0.9系では不要)

という手順を踏む必要があるのでした。