Techioz Blog

Rails 7: jsonapi-serializer にネストされた関連付けを含める方法

概要

Rails 7 APIには、多くの本を持っている著者と、その著者に属する本があります。私は「jsonapi-serializer」、「~> 2.2」を使用して著者とその書籍を取得しようとしています。 json ファイルを見ると、次のようになります。

{
  "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"
        }
      ]
    }
  }
}

関係にあるものを展開して完全な情報を表示するか、少なくとも ID とタイプだけではなく ID、名前、リリース年などを表示するようにカスタマイズしたいと考えています。本を取得するために別のデータベース クエリを作成する必要はありません。

AuthorSerializer は次のようになります。

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

BookSerializer は次のようになります。

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

Author コントローラーは次のようになります。

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

解決策

あなたが求めているものは JSON:API 仕様に違反しています。

ご覧のとおり、リソース識別子には、タイプ、ID、メタ以外のものを含めることはできません (ID の代わりに蓋を含める必要がある非永続オブジェクトを除く)

jsonapi-serializer gem は仕様に厳密に従っており、「リソース識別子オブジェクト」、つまりタイプと ID (コード ソース) のみを返します。

Book オブジェクトをインクルードされたメンバーに追加する AuthorSerializer.new(@authors, include: [:books]) を使用することもできますが、これは明らかに最もクリーンな表現ではありません

例:

{: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}}}}
  ]}