ruby-on-railsrubygraphqlgraphql-ruby

How can a many-to-many join table's attributes be exposed in a GraphQL-Ruby edge?


I'm in the process of building a GraphQL API in a Rails 7 application, using the graphql-ruby gem.

In the app, users can create lists of catalogue items, and their lists can be manually ordered. So the simplified ActiveRecords look a bit like this:

class List < ApplicationRecord
  has_many :list_items
  has_many :items, through: :list_items
end

class ListItem < ApplicationRecord
  belongs_to :list
  belongs_to :item
  
  # attributes include :position, :updated_at, etc.
end

class Item < ApplicationRecord
  has_many :list_items
  has_many :lists, through: :list_items
end

What I'd like to be able to do is to include the ListItem's attributes within the edge of the response, e.g.:

list(id: $id) {
  title
  createdAt
  items {
    edges {
      position
      updatedAt
      node {
        id
        title
        thumbnailUrl
      }
    }
  }
}

I've tried creating custom Connection and Edge objects, but have so far failed to work out how to express that the edge has a direct ActiveRecord equivalence to a particular ListItem record such that I can retrieve the necessary attributes.


Solution

  • So I think I've cracked it.

    In my ListType I declare my custom connection:

    module Types
      class ListType < Types::BaseObject
        field :id, ID, null: false
        field :title, String, null: false
    
        field :items, Types::ListItemConnectionType, connection: true
    
        def items
          object.list_items.order(:position)
        end
      end
    end
    

    The connection inherits from my base connection type but specifies the custom edge type:

    module Types
      class ListItemConnectionType < Types::BaseConnection
        edge_type Types::ListItemEdgeType
      end
    end
    

    In the edge type, object.node is the ListItem record - so we need to provide a custom node method to expose its associated Item

    module Types
      class ListItemEdgeType < Types::BaseEdge
        node_type(Types::ItemType)
    
        field :position, Integer
        field :updated_at, GraphQL::Types::ISO9601DateTime
    
        def position
          object.node.position
        end
    
        def updated_at
          object.node.updated_at
        end
    
        def node
          object.node.item
        end
      end
    end
    

    If anybody has an alternative approach, I'd love to hear it.