Active_model_serializers: Recommended way to use serializer with has_many through relation

Created on 23 Apr 2016  路  10Comments  路  Source: rails-api/active_model_serializers

I'm facing a case when a need to display information contained in my join table. For example:

# == Schema Information
#
# Table name: quality_inspections
#
#  id, content
#
# =============================================================================

class QualityInspection
  has_many :inspection_inspectors
  has_many :inspector, through: :inspection_inspectors
end
# == Schema Information
#
# Table name: inspection_inspectors
#
#  quality_inspection_id, inspector_id, inspection_date
#
# =============================================================================

class InspectionInspector
  belongs_to :quality_inspection
  belongs_to :user, foreign_key: :inspector_id
end

Then, I'd like to have the following json:

{
  "quality_inspections": [{
    "id": 1,
    "content": "foo",
    "inspectors": [{
      "id": 1, // which is the user's id
      "first_name": "Bar",
      "last_name": "FooFoo",
      "inspection_date": "random date"
    }]
  }]
}

For now, I'm doing the following in my serializer:

module Api::V1::QualityInspections
  class InspectorSerializer < ActiveModel::Serializer
    type :inspector

    attributes :id, :first_name, :last_name, :inspection_date

    def id
      inspector.try(:public_send, __callee__)
    end

    def first_name
      inspector.try(:public_send, __callee__)
    end

    def last_name
      inspector.try(:public_send, __callee__)
    end

    private

    def inspector
      @inspector ||= object.inspector
    end
  end
end

Do you have any better solution ? Or maybe I'm not using the right methods on my Serializer ?

Anyway, I'm really stuck when it came to display information on a join table. Oddly, I'd the same issue when using cerebris/jsonapi-resources

Question Not a bug 0.10.x

Most helpful comment

@beauby Here it's :

module Api::V1::QualityInspections
  class QualityInspectionSerializer < ActiveModel::Serializer
    type :quality_inspection

    attributes :id, :at, :quality_inspection_type, :final_decision

    attribute :inspectors

    def inspectors
      object.inspection_inspectors.map do |ii|
        Api::V1::QualityInspections::InspectorSerializer.new(ii)
      end
    end
  end
end

All 10 comments

Basically you do not need to worry about the "through" part of the "has many through" relationship: ActiveRecord handles that complexity for you, and it endows your models with the same API as a simple "has many" relationship would. Could you post your QualityInspectorSerializer?

@beauby Here it's :

module Api::V1::QualityInspections
  class QualityInspectionSerializer < ActiveModel::Serializer
    type :quality_inspection

    attributes :id, :at, :quality_inspection_type, :final_decision

    attribute :inspectors

    def inspectors
      object.inspection_inspectors.map do |ii|
        Api::V1::QualityInspections::InspectorSerializer.new(ii)
      end
    end
  end
end

Try the following:

module Api::V1::QualityInspections
  class QualityInspectionSerializer < ActiveModel::Serializer
    type :quality_inspection

    attributes :id, :at, :quality_inspection_type, :final_decision

    has_many :inspectors
  end
end

though I suspect you should have has_many :inspectors, through: :inspection_inspectors (with inspectors plural, and not singular) in your QualityInspection class.

Yes, I've done it. But my original concern is about the InspectorSerializer, with the ugly inspector.try(:public_send, __callee__)

The InspectorSerializer should be:

module Api::V1::QualityInspections
  class InspectorSerializer < ActiveModel::Serializer
    type :inspector

    attributes :id, :first_name, :last_name, :inspection_date
  end
end

The thing is that inspection_date is on the join_table and the other field on the user table

Oh, I see. So what you really want to do, is to serialize a set of inspections, rather than a set of inspectors. I'd do the following:

module Api::V1::QualityInspections
  class QualityInspectionSerializer < ActiveModel::Serializer
    type :quality_inspection

    attributes :id, :at, :quality_inspection_type, :final_decision

    has_many :inspection_inspectors, key: 'inspectors'
  end
end

module Api::V1::QualityInspections
  class InspectionInspectorSerializer < ActiveModel::Serializer
    type :inspector

    attributes :id, :inspection_date
    attribute :first_name do
      object.user.first_name
    end
    attribute :last_name do
      object.user.last_name
    end
  end
end

Looks nice, I'll give it a try.

However, I'd hope of a solution who use a serializer (UserSerializer for example).

It depends on the payload you want to achieve. Basically, if you want your API to serve an "inspection" as one object, you have to somewhat merge the user inside the inspection. But you could also have the Inspection have an Inspector, which would be serialized by an UserSerializer.

Closing for now, feel free to keep commenting if you have more questions.

Was this page helpful?
0 / 5 - 0 ratings