jsonrubyassociationsruby-on-rails-7jsonapi-serializer

Rails 7: How to include a nested assosiation in a jsonapi-serializer


I have an author that has many books and a book that belongs to an author in an rails 7 api. I am using "jsonapi-serializer", "~> 2.2" to try and get the author and their books. When I look at the json file, I get this:

{
  "id": "1",
  "type": "author",
  "attributes": {
    "id": 1,
    "fname": "John",
    "lname": "Doe"
  },
  "relationships": {
    "books": {
      "data": [
        {
          "id": "1",
          "type": "books"
        },
        {
          "id": "2",
          "type": "books"
        },
        {
          "id": "3",
          "type": "books"
        },
        {
          "id": "4",
          "type": "books"
        },
        {
          "id": "5",
          "type": "books"
        }
      ]
    }
  }
}

I want to expand what is in relationships to show the full information or at least customize it so that it shows something like id, name, release_year rather than just id and type. I don't want to have to make another database query to get the books.

The AuthorSerializer looks like this:

class AuthorSerializer
  include JSONAPI::Serializer
  attributes :id, :fname, :lname
  has_many :books
end

The BookSerializer looks like this:

class BooksSerializer
  include JSONAPI::Serializer
  attributes :id, :name, :release_year, :awards, :genre, :price, :blurb, :isbn
  belongs_to :author
end

The Author controller looks like this:

class AuthorController < ApplicationController
  before_action :set_author, only: %i[ show update destroy ]

  # GET /authors
  def index
    @authors = Author.includes(:books).all

    render json: AuthorSerializer.new(@authors)
  end

  # GET /authors/1
  def show
    render json: AuthorSerializer.new(@author)
  end

  # POST /authors
  def create
    @author = Author.new(hospital_params)

    if @author.save
      render json: @author, status: :created, location: @author
    else
      render json: @author.errors, status: :unprocessable_entity
    end
  end

  # PATCH/PUT /authors/1
  def update
    if @author.update(author_params)
      render json: @author
    else
      render json: @author.errors, status: :unprocessable_entity
    end
  end

  # DELETE /authors/1
  def destroy
    @author.destroy
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_author
      @author= Author.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def author_params
      params.require(:author).permit(:id, :fname, :lname, :avatar[])
    end
end

Solution

  • What you are asking for violates the JSON:API spec.

    relationships: a relationships object describing relationships between the resource and other JSON:API resources.

    “relationship object” MUST contain at least one of the following: ... data: resource linkage

    Resource linkage MUST be represented as one of the following: ... an array of resource identifier objects for non-empty to-many relationships.

    A “resource identifier object” is an object that identifies an individual resource.

    A “resource identifier object” MUST contain a type member. It MUST also contain an id member... A “resource identifier object” MAY also include a meta member, whose value is a meta object that contains non-standard meta-information.

    As you can see a Resource Identifier cannot contain anything more than type, id, and meta (other than non persisted objects which must contain lid in place of id)

    The jsonapi-serializer gem strictly adheres to the spec and only returns the "Resource Identifier Objects" i.e. type and id (Code Source)

    You could use AuthorSerializer.new(@authors, include: [:books]) which will add the Book objects to the included member but it is definitely not the cleanest representation

    Example:

    {:data=>
      {:id=>"1",
       :type=>:author,
       :attributes=>{:id=>1, :fname=>"John", :lname=>"Doe"},
       :relationships=>
        {:books=>
          {:data=>
            [{:id=>"1", :type=>:book},
             {:id=>"2", :type=>:book},
             {:id=>"3", :type=>:book},
             {:id=>"4", :type=>:book},
             {:id=>"5", :type=>:book}]}}},
     :included=>
      [{:id=>"1",
        :type=>:book,
        :attributes=>{:id=>1, :name=>"Book 1", :release_year=>2011},
        :relationships=>{:author=>{:data=>{:id=>"1", :type=>:author}}}},
       {:id=>"2",
        :type=>:book,
        :attributes=>{:id=>2, :name=>"Book 2", :release_year=>2012},
        :relationships=>{:author=>{:data=>{:id=>"1", :type=>:author}}}},
       {:id=>"3",
        :type=>:book,
        :attributes=>{:id=>3, :name=>"Book 3", :release_year=>2013},
        :relationships=>{:author=>{:data=>{:id=>"1", :type=>:author}}}},
       {:id=>"4",
        :type=>:book,
        :attributes=>{:id=>4, :name=>"Book 4", :release_year=>2014},
        :relationships=>{:author=>{:data=>{:id=>"1", :type=>:author}}}},
       {:id=>"5",
        :type=>:book,
        :attributes=>{:id=>5, :name=>"Book 5", :release_year=>2015},
        :relationships=>{:author=>{:data=>{:id=>"1", :type=>:author}}}}
      ]}