Netflix / fast_jsonapi

No Longer Maintained - A lightning fast JSON:API serializer for Ruby Objects.
Apache License 2.0
5.07k stars 425 forks source link

Serialize only last record of a has_many association (as if it was a has_one association) #440

Open YassineDM opened 4 years ago

YassineDM commented 4 years ago

I have a user with many heights (stature measurements) recorded in a Height model but would like to serialize only last height (as if it was a has_many association).

I tried to create a (fake) custom has_one association but it does not give me what I was looking for...

app/serializers/user_serializer.rb

class UserSerializer < BaseSerializer
  attributes :email

  # has_many :heights
  has_one :last_height, record_type: :height do |user|
    user.heights.last
  end
end

app/controllers/users_controller.rb

options[:include] = [:heights]
render json: UserSerializer.new(user, options).

As is, I get the error: "heights is not specified as a relationship on UserSerializer."

If I uncomment # has_many :heights, I get though:

{
    "data": {
        "id": "1",
        "type": "user",
        "attributes": {
            "email": "fake@email.com"
        },
        "relationships": {
            "heights": {
                "data": [
                    {
                        "id": "1",
                        "type": "height"
                    },
                    {
                        "id": "2",
                        "type": "height"
                    }
                ]
            },
            "lastHeight": {
                "data": {
                    "id": "2",
                    "type": "height"
                }
            }
        }
    },
    "included": [
        {
            "id": "1",
            "type": "height",
            "attributes": {
                "value": "186.0"
            }
        },
        {
            "id": "2",
            "type": "height",
            "attributes": {
                "value": "187.0"
            }
        }
    ]
}

But I don't want to include in the compound document all recorded heights...

Expected result

{
    "data": {
        "id": "1",
        "type": "user",
        "attributes": {
            "email": "fake@email.com"
        },
        "relationships": {
            "lastHeight": {
                "data": {
                    "id": "2",
                    "type": "height"
                }
            }
        }
    },
    "included": [
        {
            "id": "2",
            "type": "height",
            "attributes": {
                "value": "187.0"
            }
        }
    ]
}
hicklin-james commented 4 years ago

@YassineDM - I have a very similar use case - did you come up with a workaround or otherwise solve this?

hicklin-james commented 4 years ago

Nevermind. I got this to work the way that I wanted. My structure is this:

class Parent
  has_many :children
end

class Child
  belongs_to :parent
end

class ParentController < ApplicationController
  def my_action
    parent = Parent.find(params[:id])
    child = parent.get_one_random_child

    options = {}
    options[:include] = [:child] # note -- this should be the same symbol as the has_one in your serializer
    options[:params] = {random_child: child}
    render json: MyParentSerializer.new(parent, options).serialized_json
  end
end

class MyParentSerializer
  include FastJsonapi::ObjectSerializer

  set_id :id
  set_type :parent

  attributes: ...

  has_one :child, serializer: MyChildSerializer do |object, params|
    params[:random_child]
  end
end

class MyChildSerializer
  include FastJsonapi::ObjectSerializer

  set_id :id
  set_type :child

  attributes: ...
end

The output matches your expected result above.

YassineDM commented 4 years ago

@hicklin-james, shouldn't we be able to do this without options[:params] in the controller?

hicklin-james commented 4 years ago

Yes - you can put whatever you want in has_one block in the serializer. The key is that relation specified in the include: [] option matches the has_one symbol. I just tried this without the options[:params] and instead serialized object.children.first in the has_one block and it also worked like above.

YassineDM commented 4 years ago

Thx @hicklin-james, I left it as a side note but will come back to it and let you know...