JSONAPI-compliant filtering, sorting, and pagination for Rails API controllers, declared in two lines, zero boilerplate.
Add the gem:
gem 'fetcheable_on_api'bundle install
rails generate fetcheable_on_api:installDeclare what's allowed, then apply:
class UsersController < ApplicationController
filter_by :name, :email, :status
sort_by :name, :created_at
def index
render json: apply_fetcheable(User.all)
end
endYour API now supports:
GET /users?filter[name]=john&filter[status]=active
GET /users?sort=name,-created_at
GET /users?page[number]=2&page[size]=25
GET /users?filter[status]=active&sort=-created_at&page[number]=1&page[size]=10- 30+ filter predicates,
eq,ilike,between,in,gt,lt, and many more, plus custom lambdas - Multi-field sorting, comma-separated fields,
+/-prefix for direction, case-insensitive option - Automatic pagination, page-based with response headers (
Pagination-Current-Page,Pagination-Per,Pagination-Total-Pages,Pagination-Total-Count) - Association support, filter and sort through ActiveRecord associations
- Whitelisted by design, only explicitly declared attributes are queryable
Prerequisites: Ruby >= 2.7, Rails >= 5.2 (ActiveSupport >= 5.2, < 9)
| Ruby | Rails 5.2 | Rails 7.0 | Rails 7.1 | Rails 7.2 | Rails 8.0 |
|---|---|---|---|---|---|
| 2.7 | ✓ | ✓ | ✓ | ||
| 3.0 | ✓ | ✓ | ✓ | ||
| 3.1 | ✓ | ✓ | ✓ | ||
| 3.2 | ✓ | ✓ | ✓ | ✓ | |
| 3.3 | ✓ | ✓ | ✓ | ||
| 3.4 | ✓ |
# Gemfile
gem 'fetcheable_on_api'bundle install
rails generate fetcheable_on_api:installgem install fetcheable_on_apifilter_by :name # default: ilike (partial, case-insensitive)
filter_by :email, with: :eq # exact match
filter_by :age, with: :gteq # numeric comparison
filter_by :created_at, with: :between, format: :datetime # date rangeFilter through associations:
filter_by :author, class_name: User, as: 'name'Custom lambda predicates:
filter_by :full_name, with: ->(collection, value) {
collection.arel_table[:first_name].matches("%#{value}%").or(
collection.arel_table[:last_name].matches("%#{value}%")
)
}GET /users?filter[name]=john # partial match
GET /users?filter[status]=active,pending # multiple values (OR)
GET /users?filter[age]=21 # numeric
GET /users?filter[author]=jane # through associationsort_by :name, :created_at
sort_by :display_name, lower: true # case-insensitive
sort_by :author, class_name: User, as: 'name' # through associationGET /users?sort=name # ascending
GET /users?sort=-created_at # descending
GET /users?sort=status,-created_at # multiple fieldsPagination works automatically. Configure the default page size:
# config/initializers/fetcheable_on_api.rb
FetcheableOnApi.configure do |config|
config.pagination_default_size = 50 # default: 25
endGET /users?page[number]=2&page[size]=25Response headers:
Pagination-Current-Page: 2
Pagination-Per: 25
Pagination-Total-Pages: 8
Pagination-Total-Count: 200
class PostsController < ApplicationController
filter_by :title, :published
filter_by :author, class_name: User, as: 'name'
sort_by :title, :created_at
sort_by :author, class_name: User, as: 'name'
def index
render json: apply_fetcheable(Post.joins(:author).includes(:author))
end
endGET /posts?filter[author]=john&filter[published]=true&sort=-created_at&page[number]=1&page[size]=10- Full predicate reference, all 33 supported Arel predicates
- API docs (YARD)
- JSONAPI specification
Contributions welcome. See CODE_OF_CONDUCT.md for community guidelines.
bin/setup # install dependencies
rake spec # run tests
bin/console # interactive consoleThanks to all contributors.
Built on top of Arel and the JSONAPI specification.