prograils
prograils

Reputation: 2376

ActiveModel Serializers: has_many with condition at run-time?

I use rails (5.0.1) and active_model_serializers (0.10.2). I would like to somehow conditionally serialize the has_many associations:

class Question < ApplicationRecord
    has_many :responses, :inverse_of => :question
end

class Response < ApplicationRecord
    belongs_to :question, :inverse_of => :responses
end

class QuestionSerializer < ActiveModel::Serializer
  attributes :id, :title, :created_at, :updated_at
  has_many :responses
end

class ResponseSerializer < ActiveModel::Serializer
  attributes :id, :title
end

I use jsonapi and querying http://localhost:3000/api/questions/1 I get this response:

Response-1:

{
  "data": {
    "id": "1",
    "type": "questions",
    "attributes": {
      "title": "First",
      "created-at": "2017-02-14T09:49:20.148Z",
      "updated-at": "2017-02-14T13:55:37.365Z"
    },
    "relationships": {
      "responses": {
        "data": [
          {
            "id": "1",
            "type": "responses"
          }
        ]
      }
    }
  }
}

If I remove has_many :responses from QuestionSerializer I get this:

Response-2:

{
  "data": {
    "id": "1",
    "type": "questions",
    "attributes": {
      "title": "First",
      "created-at": "2017-02-14T09:49:20.148Z",
      "updated-at": "2017-02-14T13:55:37.365Z"
    }
  }
}

How do I conditionally get either Response-1 or Response-2 at run time? I tried all the recommendations found - neither works with AMS 0.10.2. Currently, the condition works only this way:

class QuestionSerializer < ActiveModel::Serializer
  attributes :id, :title, :created_at, :updated_at
  has_many :responses if true
end

OR:

class QuestionSerializer < ActiveModel::Serializer
  attributes :id, :title, :created_at, :updated_at
  has_many :responses if false
end

In these 2 cases I really get either Response-1 or Response-2. But this is hard-coded and I would like to maybe pass a param into the serializer or do some similar thing.

What should I do?

Upvotes: 7

Views: 9601

Answers (4)

Tim Kozak
Tim Kozak

Reputation: 4182

Here is how you can pass parameters from the parent serializer and show or hide attributes based on these parameters in the child serializer.

Parent serializer:

class LocationSharesSerializer < ActiveModel::Serializer
  attributes :id, :locations, :show_title, :show_address
     
  def locations
    ActiveModelSerializers::SerializableResource.new(object.locations, {
      each_serializer: PublicLocationSerializer,
      params: { 
        show_title: object.show_title
      },
    })
  end

end

Child serializer

class PublicLocationSerializer < ActiveModel::Serializer
  attributes :id, :latitude, :longitude, :title, :directions, :description, :address, :tags, :created_at, :updated_at, :photos

  def title
    object.title if @instance_options[:params][:show_title]
  end

end

Upvotes: 0

Frank Etoundi
Frank Etoundi

Reputation: 356

I struggled a lot with the same issue. Here is working solution I figured.

Initialise with except: [:key_one, :key_two] as arguments.

class QuestionsController
    def index
        @questions = Question.all
        render(json: ActiveModel::ArraySerializer.new(@questions,
                                                      each_serializer: QuestionSerializer,
                                                      root: 'questions',
                                                      except: [:responses])
        )
    end

    def show 
        # you can also pass the :except arguments here
        # render(json: QuestionSerializer.new(@question, except: [:responses]).to_json)
        render(json: QuestionSerializer.new(@question).to_json)
    end
end

https://www.rubydoc.info/gems/active_model_serializers/0.9.3/ActiveModel%2FArraySerializer:initialize

https://www.rubydoc.info/gems/active_model_serializers/0.9.1/ActiveModel%2FSerializer:initialize

Upvotes: 0

prograils
prograils

Reputation: 2376

Thanks to @gkats, I found an answer (AMS 0.10.2):

class QuestionSerializer < ActiveModel::Serializer
  attributes :id, :title, :created_at, :updated_at
  has_many :responses, if: -> { should_render_association }

  def should_render_association
    @instance_options[:show_children]
  end
end

class Api::ResponsesController < Api::ApplicationController
  def show
    render json: @response, show_children: param[:include_children]
  end
end

The problem was all about the syntax: if: in the serializer should be applied to a block rather than to a function.

Upvotes: 5

gkats
gkats

Reputation: 1322

I think you've kind of answered your own question. If you look into the AMS documentation for associations it says that conditionals are supported.

From what I can tell you're just a typo away

class QuestionSerializer < ActiveModel::Serializer
   has_many :responses, if: false
end

The attributes method also supports the if option, as described here.

What's your active_model_serializers version?

EDIT: I have an error in my answer too. I'm using active_model_serializers (0.10.3) and I'm able to do

class QuestionSerializer < ActiveModel::Serializer
   has_many :responses, if: -> { false }
end

The if option works with either methods, procs or strings. I think you can decide at runtime by providing a method as the conditional.

class QuestionSerializer < ActiveModel::Serializer
  attr_writer :should_render_association
  has_many :responses, if: -> { should_render_association }
end
# Usage: 
serializer = QuestionSerializer.new(question)
serializer.should_render_association = false
serializer.to_json
# => no "responses" key

Upvotes: 6

Related Questions