Rails Pattern For Including Nested Resources In API Responses

Dale Zak
4 min readMar 2, 2021

This Rails pattern provides a flexible way for clients to specify whether they want nested resources includes in the API response simply by passing a flag as a parameter to the endpoint.

For example if you were building a simple blog, you would get Posts with Comments by calling posts.json?comments=true, however calling posts.json or posts.json?comments=false would return Posts without Comments.

posts.json?comments=false
posts.json?comments=true

This is incredibly powerful giving the client the flexibility to consume lighter weight responses by default or make less requests by including nested resources when needed.

The trick to avoid N+1 queries is to use a scope which calls includes(:comments) when the flag is true. To achieve this the Posts and Comments models would be something like this.

class Post < ApplicationRecord
belongs_to :user, counter_cache: true
has_many :comments, -> { order(created_at: :desc)}, inverse_of: :post, dependent: :destroy
scope :with_comments, ->(include) { includes(:comments) if include.present? && include.to_bool }
end
class Comment < ApplicationRecord
belongs_to :user, counter_cache: true
belongs_to :post, counter_cache: true
end

The routes.rb would look something like the following.

Rails.application.routes.draw do
resources :posts do
resources :comments
end
end

Your Posts controller would be as such.

class PostsController < ApplicationController
def index
comments = params.fetch(:comments, nil)
@posts = Post.with_comments(comments).all
end
end

Your _post.json.jbuilder would then look like the following.

json.extract! post, :id, :title, :body, :user_id, :created_at, :updated_at
if params.fetch(:comments, false).to_bool
json.comments post.comments do |comment|
json.partial! "comments/comment", comment: comment
end
end
Dale Zak

Full stack developer specializing in web apps built on Rails with Stimulus, and mobile apps using Ionic and Vue.