active_model_serializers をデータベースと連動しないモデルで使うには
こんにちは、「天下一品はカロリーが低い」と教えてもらったきたけーです(一週間に三杯たべました)。
RailsでJSONをレスポンスとして返すときに有用な 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系では不要)
という手順を踏む必要があるのでした。