ActiveRecord Associations

View the Sample App

Understanding Nested Queries

JSONAPI Suite comes with an ActiveRecord adapter. Though other adapters can mimic this same interface, here’s what you’ll get out-of-the-box. The SQL here is roughly the same as using #includes.

Note: make sure to whitelist associations in your serializers or nothing will render!

has_many

/posts?include=comments
# app/resources/post_resource.rb
has_many :comments,
  scope: -> { Comment.all },
  resource: CommentResource,
  foreign_key: :post_id

belongs_to

/comments?include=posts
# app/resources/comment_resource.rb
belongs_to :post,
  scope: -> { Post.all },
  resource: PostResource,
  foreign_key: :post_id

has_one

/posts?include=detail
# app/resources/post_resource.rb
has_one :detail,
  scope: -> { PostDetail.all },
  resource: PostDetailResource,
  foreign_key: :post_id

has_and_belongs_to_many

/posts?include=tags
# app/resources/post_resource.rb
has_and_belongs_to_many :tags,
  scope: -> { Tag.all },
  resource: TagResource,
  foreign_key: { taggings: :tag_id }

The only difference here is the foreign_key - we’re passing a hash instead of a symbol. taggings is our join table, and tag_id is the true foreign key.

This will work, and for simple many-to-many relationships you can move on. But what if we want to add the property primary, a boolean, to the taggings table? Since we hid this relationship from the API, how will clients access it?

As this is metadata about the relationship it should go on the meta section of the corresponding relationship object. While supporting such an approach is on the JSONAPI Suite roadmap, we haven’t done so yet.

For now, it might be best to simply expose the intermediate table to the API. Using a client like JSORM, the overhead of this approach is minimal.

polymorphic_belongs_to

# app/models/employee.rb
belongs_to :workspace, polymorphic: true
# app/models/workspace.rb
has_many :employees, as: :workspace
# app/resources/employee_resource.rb
polymorphic_belongs_to :workspace,
  group_by: :workspace_type,
  groups: {
    'Office' => {
      scope: -> { Office.all },
      resource: OfficeResource,
      foreign_key: :workspace_id
    },
    'HomeOffice' => {
      scope: -> { HomeOffice.all },
      resource: HomeOfficeResource,
      foreign_key: :workspace_id
    }
  }
/employees?include=workspace

Here an Employee belongs to a Workspace. Workspaces have different types - HomeOffice, Office, CoworkingSpace, etc. The employees table has workspace_id and workspace_type columns to support this relationship.

We may need to query each workspace_type differently - perhaps they live in separate tables (home_offices, coworking_spaces, etc). So, when fetching the relationship, we’ll need to group our Employees by workspace_type and query differently for each group:

# app/resources/employee_resource.rb
polymorphic_belongs_to :workspace,
  group_by: :workspace_type,
  groups: {
    'Office' => {
      scope: -> { Office.all },
      resource: OfficeResource,
      foreign_key: :workspace_id
    },
    'HomeOffice' => {
      scope: -> { HomeOffice.all },
      resource: HomeOfficeResource,
      foreign_key: :workspace_id
    }
  }

Let’s say our API was returning 10 Employees, sideloading their corresponding Workspace. The underlying code would:

  • Fetch the employees
  • Group the employees by the given key: employees.group_by { |e| e.workspace_type }
  • Use the Office configuration for all Employees where workspace_type is Office, and use the HomeOffice configuration for all Employees where workspace_type is HomeOffice, and so forth.