active_model_serializers icon indicating copy to clipboard operation
active_model_serializers copied to clipboard

Rails 5, ActiveRecord has_many for nested Serializers not used?

Open tdegrunt opened this issue 7 years ago • 11 comments

Probably doing something wrong, but ...

Expected behavior vs actual behavior

Given the following models & serialisers:

class Shipment < ApplicationRecord
    has_many :shipment_service_types
end
class ShipmentServiceType < ApplicationRecord
    belongs_to :shipment
    has_many :time_windows
end
class TimeWindow < ApplicationRecord
    belongs_to :shipment_service_type
end

# serializers
class ShipmentSerializer < ActiveModel::Serializer
  attributes :id, :name
  has_many :shipment_service_types, serializer: ShipmentServiceTypeSerializer
end
class ShipmentServiceTypeSerializer < ActiveModel::Serializer
  attributes :name
  has_many :time_windows, serializer: TimeWindowSerializer
end

class TimeWindowSerializer < ActiveModel::Serializer
  attributes :id, :start, :end
end

The output I expect to see is:

{
  "shipment": {
    "id": 1,
    "name": "Test",
    "shipment_service_types": [
      {
        "id": 1,
        "name": "test",
        "time_windows": [
          {
            "id": 1,
            "start": "2000-01-01 09:05:41 UTC",
            "end": "2000-01-01 10:05:41 UTC"
          }
        ]
      }
    ]
  }
}

What I'm getting is:

{
  "shipment": {
    "id": 1,
    "name": "Test",
    "shipment_service_types": [
      {
        "id": 1,
        "name": "test"
      }
    ]
  }
}

Steps to reproduce

(e.g., detailed walkthrough, runnable script, example application)

I've made an example app: https://github.com/tdegrunt/ams_test

Environment

ActiveModelSerializers Version (commit ref if not on tag):

Output of ruby -e "puts RUBY_DESCRIPTION": ruby 2.2.4p230 (2015-12-16 revision 53155) [x86_64-darwin15]

OS Type & Version: macOS 10.12.2

Integrated application and version (e.g., Rails, Grape, etc): Rails 5.0.1

Backtrace

(e.g., provide any applicable backtraces from your application)

Additonal helpful information

(e.g., Gemfile.lock, configurations, PR containing a failing test, git bisect results)

tdegrunt avatar Dec 23 '16 09:12 tdegrunt

@rails-api/ams @rails-api/commit we really need to improve the docs on nesting more than 2 levels deep. This comes up not infrequently.

bf4 avatar Dec 23 '16 15:12 bf4

@bf4 totally agree on this. And I would actually say, the whole docs need to be improved. @tdegrunt I guess what you are missing is the include option as per https://github.com/rails-api/active_model_serializers/blob/a032201a91cbca407211bca0392ba881eef1f7ba/docs/general/adapters.md#included (this is for the JSONAPI adapter but it works the same for the JSON adapter). Try adding the option include: "shipment_service_types,shipment_service_types.time_windows". So:

render json: resource, include: "shipment_service_types,shipment_service_types.time_windows"

groyoh avatar Dec 26 '16 22:12 groyoh

👍 would be an epic pr

bf4 avatar Dec 27 '16 04:12 bf4

Ah thanks, totally missed that. Is this done because of performance benefits? It's kind of contrary to the "just works" principle. From that perspective "exclude" would almost make more sense? Anyways, I can continue upgrading to v0.10/master. Am a happy camper regardless! :)

tdegrunt avatar Dec 27 '16 09:12 tdegrunt

AFAIK it's more about avoiding serializing recursive relationships and finer control over what is included in the response. Maybe that goes against our dev instincts. It is still possible to use ActiveModelSerializers.config.default_includes = "**" to include every relationship by default if you know what you're doing though.

groyoh avatar Dec 27 '16 13:12 groyoh

The include ** is an idiosyncrasy of ams... I'd rather make it annoying to write deeply nested structures as a way of dissuading their usage. Sometimes it's good to make something hard. :)

Best practice is to flatten resources like json api does. Easier to manage, less redundant

B mobile phone

On Dec 27, 2016, at 8:54 AM, Yohan Robert [email protected] wrote:

AFAIK it's more about avoiding serializing recursive relationships and finer control over what is included in the response. Maybe that goes against our dev instincts. It is still possible to use ActiveModelSerializers.config.default_includes = "**" to include every relationship by default if you know what you're doing though.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

bf4 avatar Dec 28 '16 01:12 bf4

  • https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/adapters.md#included
  • https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/configuration_options.md#default_includes

bf4 avatar Jan 17 '17 14:01 bf4

+1

prokopsimek avatar Feb 14 '17 12:02 prokopsimek

This is a pretty severe performance bottleneck for me since the API I manage has a lot of nested relations, sometimes going 3-4 layers deep. An ideal solution IMO would be (if possible) to just include whatever was in the includes statement of the query if one was present.

danielbonnell avatar Jul 26 '17 22:07 danielbonnell

@groyoh Thanks for the solution you posted above:

render json: resource, include: "shipment_service_types,shipment_service_types.time_windows"

If you wanted to limit that query to the last 10 shipment_service_types.time_windows and sort them in descending order, is that possible?

LucasCioffi avatar Aug 01 '17 03:08 LucasCioffi

The include directive also supports hash :

render json: @accommodation, include: [
  :category,
  :owner,
  :average,
  :images,
  :commodities,
  :facilitations,
  rooms: [
    :commodities,
    :facilitations,
    beds: [
      :category
    ]
  ]
]

gloaec avatar Sep 14 '18 08:09 gloaec