Rails Pattern For Including Nested Resources In API Responses
Tags: ruby, rails, api
Published: 2021-03-27 13:49:29

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.

![1_qMMhj-G7mh42n8C-oIGqQQ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fqtb4tn9e4ma14f2rgwu.jpg) 

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

![1_yG3o1atd_48IvoPAKydqcA](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tp71ou7l0u46qei570ir.png) 

##### posts.json?comments=true

![1_lF4bUjqA7ePj4oCvWt6MiA](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vk4kjiu6o2vq3prvbdn5.png) 

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.

```ruby
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.

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

Your _Posts_ controller would be as such.

```ruby
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.

```javascript
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
```

Finally I like to include the following `to_bool.rb` in my initializers so I can call `to_bool` throughout my code.

```ruby
class Object
 def to_bool
  !!self
 end
end
class Integer
 def to_bool
  self == 1
 end
end
class String
 def to_bool
  if self == true || self =~ /(true|t|yes|y|1|on)$/i
   true
  elsif self == false || self.blank? || self =~ /(false|f|no|n|0|off)$/i
   false
  else
   false
  end
 end
end
class Symbol
 def to_bool
  self.downcase.to_s =~ /(true|t|yes|y|1|on)$/i
 end
end
```

I made the example overly simple so it was easy to follow, however this same pattern could be used for belongs_to resources or multiple `has_many` resources too.

---

It can also work with multiple level of nested resources. For example if you wanted to include User in the _Posts_ and _Comments_, you would just need to add with_user scope to your models, update your controller to use this scope, then update your `json.jbuilder` to render _User_ when the flag is set.

```ruby
class Post < ApplicationRecord
 belongs_to :user, counter_cache: true
 has_many :comments, -> { order(created_at: :desc)}, inverse_of: :post, dependent: :destroy
 scope :with_user, ->(include) { includes(:user) if include.present? && include.to_bool }
 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
 scope :with_user, ->(include) { includes(:user) if include.present? && include.to_bool }
end
```

In your _Posts_ controller you’d need to call `with_user` scope.

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

Then update `_post.json.jbuilder` to include the _User_.

```javascript
json.extract! post, :id, :title, :body, :user_id, :created_at, :updated_at
if params.fetch(:user, false).to_bool
 json.user post.user, partial: 'users/user', as: :user
end
if params.fetch(:comments, false).to_bool
 json.comments post.comments do |comment|
  json.partial! "comments/comment", comment: comment
 end
end
```

##### posts.json?comments=false&user=true

![1_NsFKA_O6pwA7kMbKlgJY3w](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1sxaddj4xixrh8l8p4i.png)

##### posts.json?comments=true&user=true

![1_tm2elhkZkFo1wUGU7gGuRQ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xxodj38fea5fa7vum252.png) 

Just a note, if your nested resources have a lot of results, you may want to limit the number of resources being returned, for example only include the last 10 _Comments_.

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

Or optionally use an integer instead of a boolean so you can pass the number of nested resources you’d like to receive. For example `posts.json?comments=10` could return 10 most recent _Comments_.

---

I love this pattern because it doesn’t take a lot of work to setup in Rails yet still generates efficient queries but is incredibly flexible and powerful for the consuming client giving them the option to tailor the JSON responses based on their needs.

Would this pattern help your Rails project? Have you used a similar strategy before? I’d love to hear from you in the comments!