LoginSignup
50
38

More than 5 years have passed since last update.

【Rails/ActiveModelSerializers】each_serializerで呼び出し側から、Serializerクラスに値を渡したいとき

Last updated at Posted at 2017-11-04

前書き

  • Ruby on Railsで、JSON APIを作る上で、active_model_serializers というgemが便利だったので、記録を残そうと思いました。
  • https://github.com/rails-api/active_model_serializers
  • each_serializerというコレクションを扱うオプションがあるのですが、呼び出し側からserializerクラスに値を渡すことで結構調べたため、忘れずにメモを取ろうと思います。

ActiveModelSerializerって何?

  • JSONを吐き出すときのロジックを書けるクラスを作成してくれるgem。

個人的にいいなと思ったところ

  • JSONに吐き出すときのロジックをモデルに書かなくてもいいところ。
    • モデルの責務が大きくなり、自然とコードの量が増える。
    • いわゆるFat Model対策
  • モデルに定義したアソシエーションを使えること。

Before: ActiveModelSerializers を知る前

  • 1. jbuilderを使う。
    • こちらは、viewディレクトリに入っていくため、腑に落ちないと感じていた。
    • 毎回jsonと書かないといけないのも、めんどくさい。
    • モデルに関連しているクラスのプロパティへのアクセサーを書かないといけない。(下のuser_nickname)
      • Decoratorじゃないから、これはやりたくない。
    • 三項演算子では、表現できないif文などは、書くのが難しくなる。
messages/create.json.jbuilder
if @message.errors.present?
   json.error_message    @message.errors.full_messages.first
 else
   json.user_name        @message.user_nickname
   json.body             @message.body
   json.created_at       @message.created_at.to_s(:default)
   json.image            @message.image
   json.user_id          @message.user.id
end

After: ActiveModelSerializers を知ったあと

messages_serializer.rb
class MessageSerializer < ActiveModel::Serializer
  include ActiveModel::Serialization

  attributes :id, :body, :user_name, :image, :created_at, :updated_at, 

  def user_name
    object.user.present? object.user.name || 'no-name'
  end

end
  • なんということでしょう! まさに、こんなのが欲しかった!
    • 1. 新しく Serializers ディレクトリが作成され、その中にSerializerクラスを定義していく形になる。
    • 2. キーにロジックを加えなくてもいいときは、attributes のリストに入れるだけ。
      • 楽!!! jbuilder みたいに json って書かなくてもいい!!
    • 3. プロパティのアクセサーは、こちらに書いていくので、モデルに書く必要はない。
    • 4. メソッドの名前がキーになり、返り値がそのまま出力されるシンプルなもの。

実際に業務で使ってみて。

users_controller.rb
def show
  @user = User.find(params[:id])
  render json: UserSerialzier.new(@user)
end
user_serializer.rb
class UserSerializer < ActiveModel::Serializer
  include ActiveModel::Serialization

  attributes :facebook_avatar_url, :full_name, :registered_at

  def facebook_avatar_url
    "https://graph.facebook.com/#{object.facebook_id}/picture?type=large"
  end

  def full_name
    "#{object.last_name} #{object.first_name}"
  end

  def registered_at
    object.created_at
  end
end

余談ですが、会社のフロントエンジニアは、全て外国人です。本場の英語を使っているからか、jsonのキーの名前の変更がかなりあったので、Serializer には大変助けられた...。(active_model_serializer を知らなかったら、最悪の場合、データベースのカラム名などを変えていたかもしれない...。)

本題: each_serializerのオプションで、呼び出し側から値を入れたいとき

下は簡易コードです。

一つのオブジェクトをSerializerを使って、JSONを返すときは、Serializerのクラスでinitialize メソッドで optionを引数のなかで、取得することができます。

users_controller.rb
def show
  user = Users.find(params[:id])
  user_status = params[:status]
  render json: UserSerializer.new(user), user_status: user_status
end
user_serializer.rb
class UserSerializer < ActiveModel::Serializer
  include ActiveModel::Serialization

  attributes :user_status

  def initialize(object, **option)
    @user_status = option[:user_status]
  end

  def user_status
    @user_status
  end

end

しかし、コレクションの場合...(each_serializerを使うとき)

users_controller.rb
def index
  @users = User.all
  user_status = params[:user_status]
  render json: @users, each_serializer: UserSerializer, user_status: user_status
end

と書いたときに、エラー発生...。どうやら、each_serialzier オプションを使うときは、オプションで呼び出し側から、Serializer に値を渡せないらしい。では、どうすればいいのか。
いろいろと調べた結果、下の記事を発見!

正解は、こちら

user_serializer.rb
class UserSerializer < ActiveModel::Serializer
  include ActiveModel::Serialization

  attributes :user_status

  def user_status
    scope[:user_status]
  end

end

scope を使うらしい!!知らなかった!
これで、エラー解決!結構、時間かけて探した甲斐がありました!!

まとめ

初めて使ったgemでしたが、こんなに便利なgemがあったんだ!って思うほど、active_model_serializer は便利でした。おかげで、モデルに書くメソッドが少なくなり、責務を分けることができました。 active_model_serializer を使っていて、他にもこんな使い方があるよ〜って方は、ぜひコメントに残していただけると幸いです。

50
38
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
38