Active_model_serializers: JSON:API adapter rendering undesired resource relationship linkages

Created on 25 Feb 2019  Â·  8Comments  Â·  Source: rails-api/active_model_serializers

The following controller and serializer cause all specified associations to be preloaded:

class ProfileSerializer < ApplicationSerializer
  has_many :primary_conditions
  has_many :member_roles
  has_many :owned_sites
  has_many :tags
  has_many :award_nominee_awards
  has_many :award_nominees
  has_many :market_contractors

  has_one :primary_address
  has_one :primary_email_address
  has_one :primary_phone
end

class Api::Platform::Carmen::V1::ProfilesController < Api::Platform::Carmen::V1Controller
  load_and_authorize_resource

  def index
    respond_with(paginate(@profiles), :include => [:owned_sites])
  end

  def show
    respond_with(@profile, :include => "**")
  end
end

The resulting JSON output contains all of the specified in the serializer association while we only need owned_sites on the index action, where we have 100 records per page. This generates hundreds of unnecessary queries to the database slowing response time 10x. How do I skip this preloading? Should I create separate serializers for each controller action? This does not look right to me.

Rails: 5.1.6.1
ActiveModel Serializers 0.10.9

Most helpful comment

FWIW, you may want to look at other gems that do JSON:API

All 8 comments

I think the way you're using preload is different from how activerecord uses the term.

In any case, I'm assuming you're using the JSON:API adapter?

First, AMS doesn't handle any query methods on your models. You have to do that yourself.

Second, :include => [:owned_sites] means to that the owned_sites resource relationships should appear fully serialized in the included top level member. The adapter is still going to serialize the relationship objects with their data and id. If you don't want the relationship objects, you'll need a serializer that doesn't specify them, or I think you can make them conditional. See the docs.

Please let me know how this helps

Hi, yes it's the JSON:API adapter.

I think if: -> (serializer) { serializer.scope.admin? } might work, thank you. What I don't like about this is that I need to define lots of procs for this, in many serializers.

I am also thinking about how this could be solved globally, i.e. with a global config and/or particular serializer option to only load included relationships. That would be an awesome option in my opinion, which will allow configuring different types of relationships within a serializer and then include only required ones for each controller action or use * and **.

What do you think?

@heaven I probably just don't understand exactly what you're saying, but it sounds to me like you want
to define relationships to conditionally serialize without having a conditional anywhere, either via polymorphic in the serializer you use or in the serializer via callable

I still am not positive you're clear on the difference whether a JSON:API resource is 'included' (side loaded) or not. Even if they both execute the same active record query, they're not necessarily the same thing.

I think, and it's been a while since I've used AMS, you can specify something like include_data: false. I figure you've looked at the docs and either not seen it or it's not right for your needs.

Now, JSON:API does provide a way to both have the relationship and not include the data, and that is with links. AMS for no particularly great philosophical reason does prefer showing the linkage data over creating resource links. jsonapi-resources shows links by default, but it also has routing helpers for them.

You could definitely subclass the adapter and customize it to apply the include directive as include_data: false when not included.

Whoops, I tried include_data: false as a parameter which didn't work, that's why I missed it. But I don't seem to have enough context within a block to determine whether to include the data or not.

My goal is to define all associations in a serializer but don't load them all where I don't need to. For example, we need a lot of data on GET /profiles/:id, profile -> address -> country, site -> social_profile, many nested relationships. But on GET /profiles we need very little information, only 5% of the associations defined in the serializer, but they are being still loaded from the database and listed in relationships, while we don't need them and simply wasting resources on this. Defining separate serializers per controller action seems redundant to me. I have to repeat the same code, define the same associations but with a block with include_data false and have to manually specify the custom serializer in a controller.

I think it would be great if serializers respected the :include option more and didn't load relationships that were not explicitly included (have an option for this, not asking for this to be a default behavior).

So when you do render json: @profile, :include => [:sites, :address] only these two loaded from the database and added to both relationships and included, not all you have defined in a serializer.

I think it would be great if serializers respected the :include option more and didn't load relationships that were not explicitly included (have an option for this, not asking for this to be a default behavior).

I understand what you're saying, and I apologize for being unclear, but include in JSON:API means something different from what you want it to do. I believe AMS provides what you need to solve your problem and that these solutions are documented. I believe I've addressed your questions by guiding you where to look.

I'm not sure there's much more I can do.

Here's the problem as I hear it:

  1. You have one serializer with all possible relationships you're using in multiple places
  2. You're averse to having multiple serializers or conditional relationships
  3. You'd like the JSON:API include parameter to whitelist the resource relationships, in addition to side-loading them, which would require customizations to the adapter that would need to reside in your app, not in AMS, and you'd rather that someone add that as a feature to AMS

I wish I could help, but I don't work at your company. I only help maintain the library.

Thank you for the help, indeed the things you mentioned can solve the problem, I am just looking for a more convenient way for this.

You also understood my idea right, except for the number 3 – I don't want someone doing this for us, but if you think such a feature could have a place in the gem I can try preparing a pull request. Let me also know if you have any preferences/recommendations regarding setting names and the way it is implemented.

@heaven here's how I imagine it having a place in the gem

  1. There's some configuration on the serializer relationship that toggles whether or not to render the relationship and/or the relationship linkage data (which is what is requiring the db load)
  2. The serializer knows what relationships are being processed in the include directives
  3. It's easy to use (2) in your ApplicationSerialize to toggle behavior depending on (1)

That way you make the JSON:API implementation better while giving you the power to use it in a possibly idiosyncratic way

FWIW, you may want to look at other gems that do JSON:API

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kapso picture kapso  Â·  4Comments

AlexCppns picture AlexCppns  Â·  5Comments

attenzione picture attenzione  Â·  4Comments

iggant picture iggant  Â·  4Comments

steverob picture steverob  Â·  4Comments