Active_model_serializers: How to include nested associations?

Created on 25 Jun 2015  路  32Comments  路  Source: rails-api/active_model_serializers

Hey, let's say I have a Post model which has_many :comments. And I use it like this in the post index controller: render json: post, include: ['comments']. That works fine and includes the comments, but let's say the Comment model belongs_to :user now. Writing render json: post, include: ['comments', 'user'] doesn't "sideload" the data, is there a solution to this?

Feature Question 0.10.x

Most helpful comment

sure here's what I did, my example: a body has one hand which has many fingers. This should serialize a collection of bodies including hands and fingers.

class BodySerializer < ActiveModel::Serializer
    attributes(:name)

    has_one :hand

    url :body
end
class HandSerializer < ActiveModel::Serializer
    attributes(:wrist)

    belongs_to :body
    has_many :fingers

    url [:body, :hand]
end
  class FingerSerializer < ActiveModel::Serializer
    attributes(:knuckle)

    belongs_to :hand

    url [:body, :hand, :fingers]
  end
  class V1::BodyController < ApplicationController
    def index
      bodies = Body.all
      render json: bodies, include: 'hand,fingers'
    end
  end

This should give serialized bodies with both hands and fingers, is this what you had in mind @piotrpalek?

All 32 comments

Hi @piotrpalek , I ran into this problem just as you posted this. I've forked and committed a failing test that shows the behaviour I was trying to produce

Does this look like the same problem to you?

317004058a610c28ac15cbeed459ceb2c9323da6

Hi @piotrpalek, @doooks. I have the same issue right now.

And I think this is design decision and if adapter will render has_many relations recursively we'll get loop. Post -> Comments -> Post -> Comments...

My problem is with AMS 0.10-rc2 and the json-api adapter (just checking if we're talking about the same issue) and I don't know how to sideload nested relationships with that setup (or if it's possible at all).
@doooks My issue is similiar but a bit different, in my example the comment wouldn't have an author (just the post), and I would try to sideload the post's author while serializing the comment (with sideloaded post relationship).

:+1: having the same issue

do any maintainers have an opinion on this? is this feature going to be accepted if we build it?

Fwiw I ended up switching to the JSON API standard. I think its handling of associated records is a great alternative to nesting json. What are your reservations to the json API spec?

@shicholas I'd never heard of it until recently, sure its great but I would rather have json that represents the model a bit more intuitively.

@shicholas Are you able to "sideload" a nested relationship using JSON API? What I mean is:
Blog -> belongsTo -> Author
Comment -> belongsTo -> Blog

And now what I want to do is load all comments and sideload all the corresponding blogs but also sideload all relevant blog authors, is that possible?

yup, I can do as many chains as possible with JSON API as long as the associations are declared in the serializer classes

To include the associations you got to declare them in the controller via include: ['something'] right? Can you give an example how to include the nested associations?

sure here's what I did, my example: a body has one hand which has many fingers. This should serialize a collection of bodies including hands and fingers.

class BodySerializer < ActiveModel::Serializer
    attributes(:name)

    has_one :hand

    url :body
end
class HandSerializer < ActiveModel::Serializer
    attributes(:wrist)

    belongs_to :body
    has_many :fingers

    url [:body, :hand]
end
  class FingerSerializer < ActiveModel::Serializer
    attributes(:knuckle)

    belongs_to :hand

    url [:body, :hand, :fingers]
  end
  class V1::BodyController < ApplicationController
    def index
      bodies = Body.all
      render json: bodies, include: 'hand,fingers'
    end
  end

This should give serialized bodies with both hands and fingers, is this what you had in mind @piotrpalek?

Hey ppl, first of all thank you all for helping each other :smile:
First things first, nested associations will be a feature on 0.10.x, a lot of ppl have being requesting it. :tada:

It started back at #835, there was a PR (#952) I reviewed it a while ago, but it's broken, I'll probably pick it up in some days.
Let me know if some of you want to pick it first of work on a new implementation, I would like it to be adapter agnostic (I know JSON API is a case apart), if possible, well just let me know and we can talk it trough, otherwise I'll work on it asap, but I still need to finish a new feature that we are working on. :smile:

btw @piotrpalek, you should give a try to JSON-API, awesome convention, and we really support and advise it.

@shicholas thanks for the example I will try it later :)
@joaomdmoura yeah I will give it a try thx :)

Any updates on this?

@vyrak Well this worked for me (from schicholas example): include: ['hand.fingers'] to include the nested fingers association.

@piotrpalek That's only when using the JsonAPI adapter, right?

@yjukaku yeah right, I think it's only jsonapi adapter (didn't try any other)

Taking into consideration there is a issue and some work in progress on a PR related to it, I'm closing this one in favor of others.

I know this has been closed, but could anyone point me to the most recent/appropriate issue for including nested associations?

I would love to see this feature included sooner rather than later!

@shicholas and @piotrpalek the approach that worked for two of your in creating nested association using json-api with include is not working for me. I am using 0.10.0.rc2 and I tried making nested association with include but it is not working.

   class UserSerializer < ActiveModel::Serializer
        attributes :id, :email
        has_one :account
   end

   class AccountSerializer < ActiveModel::Serializer
        attributes :id, :name, :domain
        has_many :segments
   end

   class SegmentSerializer < ActiveModel::Serializer
        attributes :id, :name,  :active
        has_many :base_segments
        has_one :account
    end

    class  Api::UsersController < ApplicationController
        def index
            @users = User.all
            render json: @users, include:  ['account', 'account.segments'] 
        end
    end

I also tried render json: @users, include: ['account', 'segments']
Is there I anything I am missing to get this working. Thanks for your help.

@mankind, first of all, excellent name :).

Your serializer definitions look good to me. What type of error are you getting?

FWIW, I am using a concern for JSON API rendering because I am trying to use both .8 and .10 in the same app. My controller concern looks like:

module JsonApiHelpers
  extend ActiveSupport::Concern

  private

  def render_serialized_array(model_array)
    serializer_instance = serializer_for(model_array).new(
      model_array,
      each_serializer: serializer_for(model_array)
    )

    render json: adapter(serializer_instance)
  end

  def render_serialized_model(model)
    serializer_instance = serializer_for(model).new(model)

    render json: adapter(serializer_instance), status: :ok
  end

  def serializer_for(model)
    @_serializer ||= ActiveModel::Serializer.serializer_for(model)
  end

  def included; [] end # rubocop:disable Style/SingleLineMethods

  def adapter_opts
    { adapter: 'json_api', include: included }
  end

  def adapter(serializer_instance)
    ActiveModel::Serializer::Adapter.create(
      serializer_instance,
      adapter_opts
    )
  end
end

which I learned from https://github.com/rails-api/active_model_serializers/issues/1002. Then in my controller I include the concern, override included and call render_serialized_array for collections or render_serialized_model for a single resource.

maybe that helps?!

@shicholas thanks for the quick response. It is finally working now. Thanks for your code because it helped me. Cheers ):

How can i implement this on Post and Put requests? I mean, instead of passing an id, i would like to pass the entire model relation to my POST (user : {name : "sagits", category : {id : 10}}. Is this possible? Thanks in advance.

@sagits It is not possible as it is not part of the JSON API spec AFAIK.

@sagits Could you create a new issue please, including your AMS version, Rails version, and some code showing what you're trying to do and what you expect?

Sure @bf4, here it is 1540.

For anyone reading this who is using the :json adapter, and having issues with rendering associations, one of the documented methods, for whatever reason, does not work. You must define your includes as include: ['foo', 'bar']

https://github.com/rails-api/active_model_serializers/commit/51af5a4b76e64c4e29cac227779524a7dc34e1ff#commitcomment-21425180

I have not spent the time yet to find the offending change that broke this, but hopefully I might save some hair puling. This would effect anyone inheriting an old project running a bundle update on an unlocked project.

In case anyone like me was using a serializer manually like this: MySerializer.new(record) and could not figure out how to pass include: it's done like this: MySerializer.new(record).to_h(include: '**') because passing this parameter to the constructor does not work.

@fazo96 this might help? https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/adapters.md#include-option

fyki, include is an adapter option

Yes, we use it like that most of the time. However sometimes we need to include a serialized record inside a bigger hash structure that does not come from another active model serializer.

In those cases the include option has to be passed when calling to_hash or one of its aliases. I only figured this out by looking at the source code and could not find any documentation on this.

@fazo96 i see. i think its also documented on how to serialize a resource outside of a controller: docs/howto/outside_controller_use.md#serializing-a-resource

so you can pass every option to the ActiveModelSerializers::SerializableResource initializer that you would pass in a render call.

for your case, it would be like:

ActiveModelSerializers::SerializableResource.new(record, include: '**').as_json
Was this page helpful?
0 / 5 - 0 ratings