How to implement multiple different serializers for same model using ActiveModel::Serializers?
Asked Answered
A

5

47

Let's say you're implementing a REST API in Rails. When serving a collection, you might want to only include a few attributes:

/people

But when serving a single resource, you want to include all the attributes:

/people/1

I don't see how to do that using ActiveModel::Serializers, since the examples all use the pattern of defining one serializer per model (with a standard naming convention) and having AMS automatically use the right one in the controller when you do:

render json: @people

or:

render json: @person
Alonaalone answered 18/9, 2012 at 21:32 Comment(0)
D
4

To avoid mixing view concerns into your models (via serialized variations), use the view to render your JSON for each action, much like we do for HTML.

jbuilder & rabl both fill this data templating need quite nicely.

Update 2013-12-16: The ActiveModelSerializers library does support defining multiple serializers for one model, as @phaedryx answered later, by using custom serializers.

Diskson answered 27/9, 2012 at 4:50 Comment(3)
So, what you're saying is that AMS doesn't yet have the flexibility in it's current design to satisfy this common use case of differently serializing for different API responses? Because that's how it feels, but I didn't want to assume the problem wasn't simply my own ignorance, especially since AMS is being considered for Rails4 core as "the way"...Alonaalone
jbuilder was created by DHH specifically to address this problem in Rails. Creating variations via ActiveModel::Serializers will end up putting public-API (view) concerns in your controllers &/or models.Diskson
Ok. Obviously I need to look at jbuilder and then decide how to proceed, but it was a good answer. Thanks!Alonaalone
F
130

You can have multiple serializers for the same model, e.g.

class SimplePersonSerializer < ActiveModel::Serializer
  attributes :id, :name
end

and

class CompletePersonSerializer < ActiveModel::Serializer
  attributes :id, :name, :phone, :email
end

simple info for people in one controller:

render json: @people, each_serializer: SimplePersonSerializer

complete info for people in another:

render json: @people, each_serializer: CompletePersonSerializer

simple info for a single person:

render json: @person, serializer: SimplePersonSerializer

complete info for a single person:

render json: @person, serializer: CompletePersonSerializer
Figurate answered 25/10, 2012 at 15:48 Comment(7)
How can I have the key as person: {} not complete_person: {}?Virginity
@chopper yes. you just need to append root: :person. Here is a full example render json: @person, serializer: CompletePersonSerializer, root: :personVirginity
You can subclass class CompletePersonSerializer < SimplePersonSerializer and include just the new attributesGrearson
@SeifSallam You can add the root: person to the serializer class it self. It doesn't need be specified everywhere you call render jsonPenman
Phaedryx and @Swards you guys are my heroes!Elvinelvina
Is there any way to specify this for each action automatically at the controller level?Asyndeton
it would be very handy to change this to the answer.Cogan
C
11
class CompletePersonSerializer < ActiveModel::Serializer
  root :person
  attributes :id, :name, :phone, :email
end

or

render json: @people, each_serializer: CompletePersonSerializer, root: :person
Chalkboard answered 17/6, 2013 at 21:33 Comment(1)
as of today, root :person raises an error. each_serializer: CompletePersonSerializer is sufficientRepulsive
D
4

To avoid mixing view concerns into your models (via serialized variations), use the view to render your JSON for each action, much like we do for HTML.

jbuilder & rabl both fill this data templating need quite nicely.

Update 2013-12-16: The ActiveModelSerializers library does support defining multiple serializers for one model, as @phaedryx answered later, by using custom serializers.

Diskson answered 27/9, 2012 at 4:50 Comment(3)
So, what you're saying is that AMS doesn't yet have the flexibility in it's current design to satisfy this common use case of differently serializing for different API responses? Because that's how it feels, but I didn't want to assume the problem wasn't simply my own ignorance, especially since AMS is being considered for Rails4 core as "the way"...Alonaalone
jbuilder was created by DHH specifically to address this problem in Rails. Creating variations via ActiveModel::Serializers will end up putting public-API (view) concerns in your controllers &/or models.Diskson
Ok. Obviously I need to look at jbuilder and then decide how to proceed, but it was a good answer. Thanks!Alonaalone
F
2

Adding to what @phaedryx said, what I do for this is call a method that returns the correct serializer... for your question, I'd use:

class MyController < ApplicationController

  def index
    render json: @people, each_serializer: serializer_method
  end

  private

  def serializer_method
    defined?(@people) ? PeopleSerializer : PersonSerializer
  end

end
Filipino answered 10/10, 2013 at 20:30 Comment(0)
R
0

IMO it's best to have a specific serializer for each controller action. I built this concern to handle it. (which I add to base controller)

module Serializable
  extend ActiveSupport::Concern

   alias each_serializer serializer

   def serializer
     "#{params[:controller].classify}s::#{params[:action].classify}Serializer".constantize
   end
end

That way you can just call

render json: @person, serializer:

in your controller and it will find the correct serializer.

Reynaldoreynard answered 6/10, 2022 at 20:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.