fetcheable_on_api 0.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ASSOCIATION_SORTING_SOLUTION.md +119 -0
- data/CLAUDE.md +97 -0
- data/Gemfile.lock +54 -39
- data/README.md +315 -1
- data/lib/fetcheable_on_api/configuration.rb +33 -2
- data/lib/fetcheable_on_api/filterable.rb +250 -77
- data/lib/fetcheable_on_api/pageable.rb +85 -27
- data/lib/fetcheable_on_api/sortable.rb +192 -31
- data/lib/fetcheable_on_api/version.rb +5 -1
- data/lib/fetcheable_on_api.rb +160 -42
- data/lib/generators/fetcheable_on_api/install_generator.rb +15 -1
- data/lib/generators/templates/fetcheable_on_api_initializer.rb +14 -0
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 204213e15d42807d8bb56fccb5fc87cdc5ece5915bb01ac9b117cfc111d51fc1
|
4
|
+
data.tar.gz: 8bb425ec48b7b84cd8f43444e47f88e7a459fa5f135a54aef36846d99557a0c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 662357c5bec2bd2ba08717ba1d5eba8f7e3ab8d5e68bcea6a7019eee657e95ebb83845478e83ad8a369f741c7e61a05d5ff61d80f2c85bd5253313031992b66f
|
7
|
+
data.tar.gz: c9980e7009bcd25abbc620ac0c57ea716ec5887bb3f981df100e1f2a0ce270701a3d4b150ccb3c6e528a9f908b1f8b12b96119faf6bfa341373bf062da6c3198
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# Association Sorting Solution
|
2
|
+
|
3
|
+
## The Issue
|
4
|
+
|
5
|
+
You want to sort books by their author's name using:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
sort_by :author_name,
|
9
|
+
as: :name,
|
10
|
+
class_name: User,
|
11
|
+
association: :author
|
12
|
+
```
|
13
|
+
|
14
|
+
## Current Status
|
15
|
+
|
16
|
+
The current implementation **should already work** with the following setup:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
class BooksController < ApplicationController
|
20
|
+
# This configuration should work
|
21
|
+
sort_by :author_name, class_name: User, as: 'name'
|
22
|
+
|
23
|
+
def index
|
24
|
+
# THE KEY: Make sure to join the association
|
25
|
+
books = apply_fetcheable(Book.joins(:author))
|
26
|
+
render json: books
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
## Why It Should Work
|
32
|
+
|
33
|
+
1. `class_name: User` tells Sortable to use the `users` table
|
34
|
+
2. `as: 'name'` tells it to sort by the `name` column
|
35
|
+
3. `Book.joins(:author)` ensures the `users` table is available in the query
|
36
|
+
4. The result is `ORDER BY users.name ASC/DESC`
|
37
|
+
|
38
|
+
## The Missing Piece: Association Option Support
|
39
|
+
|
40
|
+
To fully implement the `:association` option like in Filterable, we need to:
|
41
|
+
|
42
|
+
1. ✅ Add `:association` to valid options in `sort_by` (done)
|
43
|
+
2. ✅ Update documentation with examples (done)
|
44
|
+
3. 🔧 **Optional**: Add association validation logic
|
45
|
+
|
46
|
+
## Complete Implementation
|
47
|
+
|
48
|
+
Here's your controller setup:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
class BooksController < ApplicationController
|
52
|
+
# Basic sorting
|
53
|
+
sort_by :title, :created_at
|
54
|
+
|
55
|
+
# Association sorting (current working version)
|
56
|
+
sort_by :author_name, class_name: User, as: 'name'
|
57
|
+
|
58
|
+
# Association sorting (with new association option)
|
59
|
+
sort_by :author_name, class_name: User, as: 'name', association: :author
|
60
|
+
|
61
|
+
def index
|
62
|
+
# IMPORTANT: Join the association
|
63
|
+
books = apply_fetcheable(Book.joins(:author))
|
64
|
+
render json: books
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
## API Usage
|
70
|
+
|
71
|
+
```bash
|
72
|
+
# Sort by book title
|
73
|
+
GET /books?sort=title
|
74
|
+
|
75
|
+
# Sort by author name (ascending)
|
76
|
+
GET /books?sort=author_name
|
77
|
+
|
78
|
+
# Sort by author name (descending)
|
79
|
+
GET /books?sort=-author_name
|
80
|
+
|
81
|
+
# Multiple sorts: author name asc, then created_at desc
|
82
|
+
GET /books?sort=author_name,-created_at
|
83
|
+
```
|
84
|
+
|
85
|
+
## Testing the Implementation
|
86
|
+
|
87
|
+
The implementation should generate SQL like:
|
88
|
+
|
89
|
+
```sql
|
90
|
+
SELECT books.*
|
91
|
+
FROM books
|
92
|
+
INNER JOIN users ON users.id = books.author_id
|
93
|
+
ORDER BY users.name ASC
|
94
|
+
```
|
95
|
+
|
96
|
+
## If It's Still Not Working
|
97
|
+
|
98
|
+
Check these common issues:
|
99
|
+
|
100
|
+
1. **Missing Join**: Ensure `Book.joins(:author)` is called
|
101
|
+
2. **Wrong Association Name**: Verify the association name matches your model
|
102
|
+
3. **Field Name**: Ensure the `as: 'name'` field exists on the User model
|
103
|
+
4. **Case Sensitivity**: Some databases are case-sensitive
|
104
|
+
|
105
|
+
## Debug Steps
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
def index
|
109
|
+
puts "Sorts configuration: #{sorts_configuration}"
|
110
|
+
|
111
|
+
books = Book.joins(:author)
|
112
|
+
puts "Before apply_fetcheable SQL: #{books.to_sql}"
|
113
|
+
|
114
|
+
books = apply_fetcheable(books)
|
115
|
+
puts "After apply_fetcheable SQL: #{books.to_sql}"
|
116
|
+
|
117
|
+
render json: books
|
118
|
+
end
|
119
|
+
```
|
data/CLAUDE.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# CLAUDE.md
|
2
|
+
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
4
|
+
|
5
|
+
## Project Overview
|
6
|
+
|
7
|
+
FetcheableOnApi is a Ruby gem that provides filtering, sorting, and pagination functionality for Rails API controllers following JSONAPI specification. The gem automatically adds query parameter support for `filter`, `sort`, and `page` parameters to controllers.
|
8
|
+
|
9
|
+
## Common Development Commands
|
10
|
+
|
11
|
+
### Setup
|
12
|
+
```bash
|
13
|
+
bin/setup # Install dependencies and setup project
|
14
|
+
```
|
15
|
+
|
16
|
+
### Testing
|
17
|
+
```bash
|
18
|
+
rake spec # Run all tests (default rake task)
|
19
|
+
bundle exec rspec # Run tests with explicit bundler
|
20
|
+
```
|
21
|
+
|
22
|
+
### Console
|
23
|
+
```bash
|
24
|
+
bin/console # Start IRB console with gem loaded
|
25
|
+
```
|
26
|
+
|
27
|
+
### Gem Management
|
28
|
+
```bash
|
29
|
+
bundle exec rake install # Install gem locally
|
30
|
+
bundle exec rake release # Release new version (updates version, creates git tag, pushes to rubygems)
|
31
|
+
```
|
32
|
+
|
33
|
+
## Architecture
|
34
|
+
|
35
|
+
### Core Module Structure
|
36
|
+
|
37
|
+
The gem follows a modular architecture with three main concern modules:
|
38
|
+
|
39
|
+
1. **FetcheableOnApi::Filterable** (`lib/fetcheable_on_api/filterable.rb`)
|
40
|
+
- Handles `filter[attribute]=value` query parameters
|
41
|
+
- Supports 30+ Arel predicates (`:eq`, `:ilike`, `:between`, `:in`, etc.)
|
42
|
+
- Supports filtering through associations
|
43
|
+
- Supports custom lambda predicates
|
44
|
+
|
45
|
+
2. **FetcheableOnApi::Sortable** (`lib/fetcheable_on_api/sortable.rb`)
|
46
|
+
- Handles `sort=attribute` query parameters
|
47
|
+
- Supports multiple sort fields (comma-separated)
|
48
|
+
- Supports ascending/descending with `+`/`-` prefixes
|
49
|
+
- Supports sorting through associations
|
50
|
+
|
51
|
+
3. **FetcheableOnApi::Pageable** (`lib/fetcheable_on_api/pageable.rb`)
|
52
|
+
- Handles `page[number]` and `page[size]` query parameters
|
53
|
+
- Adds pagination headers to responses
|
54
|
+
- Configurable default page size
|
55
|
+
|
56
|
+
### Main Entry Point
|
57
|
+
|
58
|
+
The main module (`lib/fetcheable_on_api.rb`) includes all three concerns and provides the `apply_fetcheable(collection)` method that controllers use to apply filtering, sorting, and pagination in sequence.
|
59
|
+
|
60
|
+
### Controller Integration
|
61
|
+
|
62
|
+
Controllers gain access to the functionality by including the module (automatically done for ActionController::Base):
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
class QuestionsController < ActionController::Base
|
66
|
+
# Define allowed filters and sorts
|
67
|
+
filter_by :content, :category_id
|
68
|
+
sort_by :position, :created_at
|
69
|
+
|
70
|
+
def index
|
71
|
+
questions = apply_fetcheable(Question.all)
|
72
|
+
render json: questions
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
### Configuration
|
78
|
+
|
79
|
+
Global configuration is handled through `FetcheableOnApi::Configuration`:
|
80
|
+
- Default pagination size (default: 25)
|
81
|
+
- Configurable via `FetcheableOnApi.configure` block
|
82
|
+
|
83
|
+
### Key Design Patterns
|
84
|
+
|
85
|
+
- **Class Attributes**: Each concern uses `class_attribute` to store configuration per controller
|
86
|
+
- **Parameter Validation**: Built-in parameter type validation with helpful error messages
|
87
|
+
- **Flexible Predicates**: Support for both built-in Arel predicates and custom lambda predicates
|
88
|
+
- **Association Support**: Can filter/sort through ActiveRecord associations
|
89
|
+
- **Header Integration**: Pagination info automatically added to response headers
|
90
|
+
|
91
|
+
## Rails Integration
|
92
|
+
|
93
|
+
The gem integrates with Rails through `ActiveSupport.on_load :action_controller` hook, automatically including the module in all ActionController classes.
|
94
|
+
|
95
|
+
## Testing Approach
|
96
|
+
|
97
|
+
Tests are located in `spec/` directory using RSpec. The gem supports testing against multiple Rails versions via gemfiles in `gemfiles/` directory (Rails 4.1 through 5.2 and head).
|
data/Gemfile.lock
CHANGED
@@ -1,44 +1,60 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fetcheable_on_api (0.
|
4
|
+
fetcheable_on_api (0.5.0)
|
5
5
|
activesupport (>= 4.1)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activesupport (
|
10
|
+
activesupport (7.1.5.1)
|
11
|
+
base64
|
12
|
+
benchmark (>= 0.3)
|
13
|
+
bigdecimal
|
11
14
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
connection_pool (>= 2.2.5)
|
16
|
+
drb
|
17
|
+
i18n (>= 1.6, < 2)
|
18
|
+
logger (>= 1.4.2)
|
19
|
+
minitest (>= 5.1)
|
20
|
+
mutex_m
|
21
|
+
securerandom (>= 0.3)
|
22
|
+
tzinfo (~> 2.0)
|
23
|
+
ast (2.4.3)
|
24
|
+
base64 (0.3.0)
|
25
|
+
benchmark (0.4.1)
|
26
|
+
bigdecimal (3.2.2)
|
27
|
+
concurrent-ruby (1.3.5)
|
28
|
+
connection_pool (2.5.3)
|
29
|
+
diff-lcs (1.6.2)
|
30
|
+
drb (2.2.3)
|
31
|
+
i18n (1.14.7)
|
20
32
|
concurrent-ruby (~> 1.0)
|
21
|
-
jaro_winkler (1.5.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
rspec
|
34
|
-
rspec-
|
35
|
-
|
33
|
+
jaro_winkler (1.5.6)
|
34
|
+
logger (1.7.0)
|
35
|
+
minitest (5.25.5)
|
36
|
+
mutex_m (0.3.0)
|
37
|
+
parallel (1.27.0)
|
38
|
+
parser (3.3.8.0)
|
39
|
+
ast (~> 2.4.1)
|
40
|
+
racc
|
41
|
+
powerpack (0.1.3)
|
42
|
+
racc (1.8.1)
|
43
|
+
rainbow (3.1.1)
|
44
|
+
rake (13.3.0)
|
45
|
+
rspec (3.13.1)
|
46
|
+
rspec-core (~> 3.13.0)
|
47
|
+
rspec-expectations (~> 3.13.0)
|
48
|
+
rspec-mocks (~> 3.13.0)
|
49
|
+
rspec-core (3.13.4)
|
50
|
+
rspec-support (~> 3.13.0)
|
51
|
+
rspec-expectations (3.13.5)
|
36
52
|
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
-
rspec-support (~> 3.
|
38
|
-
rspec-mocks (3.
|
53
|
+
rspec-support (~> 3.13.0)
|
54
|
+
rspec-mocks (3.13.5)
|
39
55
|
diff-lcs (>= 1.2.0, < 2.0)
|
40
|
-
rspec-support (~> 3.
|
41
|
-
rspec-support (3.
|
56
|
+
rspec-support (~> 3.13.0)
|
57
|
+
rspec-support (3.13.4)
|
42
58
|
rubocop (0.59.2)
|
43
59
|
jaro_winkler (~> 1.5.1)
|
44
60
|
parallel (~> 1.10)
|
@@ -47,22 +63,21 @@ GEM
|
|
47
63
|
rainbow (>= 2.2.2, < 4.0)
|
48
64
|
ruby-progressbar (~> 1.7)
|
49
65
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
50
|
-
ruby-progressbar (1.
|
51
|
-
|
52
|
-
tzinfo (
|
53
|
-
|
54
|
-
unicode-display_width (1.
|
55
|
-
zeitwerk (2.2.2)
|
66
|
+
ruby-progressbar (1.13.0)
|
67
|
+
securerandom (0.3.2)
|
68
|
+
tzinfo (2.0.6)
|
69
|
+
concurrent-ruby (~> 1.0)
|
70
|
+
unicode-display_width (1.8.0)
|
56
71
|
|
57
72
|
PLATFORMS
|
58
|
-
|
73
|
+
x86_64-linux
|
59
74
|
|
60
75
|
DEPENDENCIES
|
61
|
-
bundler (~>
|
76
|
+
bundler (~> 2.0)
|
62
77
|
fetcheable_on_api!
|
63
|
-
rake (~>
|
78
|
+
rake (~> 13.0)
|
64
79
|
rspec (~> 3.0)
|
65
80
|
rubocop (~> 0.59.2)
|
66
81
|
|
67
82
|
BUNDLED WITH
|
68
|
-
|
83
|
+
2.4.22
|
data/README.md
CHANGED
@@ -1,7 +1,71 @@
|
|
1
1
|
|
2
2
|
# FetcheableOnApi
|
3
3
|
|
4
|
-
|
4
|
+
[](https://badge.fury.io/rb/fetcheable_on_api)
|
5
|
+
[](https://rubydoc.info/gems/fetcheable_on_api)
|
6
|
+
|
7
|
+
FetcheableOnApi is a Ruby gem that provides **filtering**, **sorting**, and **pagination** functionality for Rails API controllers following the [JSONAPI specification](https://jsonapi.org/). It allows you to quickly and easily transform query parameters into ActiveRecord scopes without writing repetitive controller code.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- 🔍 **Comprehensive Filtering**: 30+ filter predicates (eq, ilike, between, in, gt, lt, etc.)
|
12
|
+
- 📊 **Flexible Sorting**: Multi-field sorting with ascending/descending control
|
13
|
+
- 📄 **Built-in Pagination**: Page-based pagination with automatic response headers
|
14
|
+
- 🔗 **Association Support**: Filter and sort through model associations
|
15
|
+
- 🛡️ **Security**: Built-in parameter validation and SQL injection protection
|
16
|
+
- ⚙️ **Configurable**: Customize defaults and behavior per application
|
17
|
+
- 🎯 **JSONAPI Compliant**: Follows official JSONAPI specification patterns
|
18
|
+
|
19
|
+
## Quick Start
|
20
|
+
|
21
|
+
Add the gem to your Gemfile and configure your controllers:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class UsersController < ApplicationController
|
25
|
+
# Define allowed filters and sorts
|
26
|
+
filter_by :name, :email, :status
|
27
|
+
sort_by :name, :created_at, :updated_at
|
28
|
+
|
29
|
+
def index
|
30
|
+
users = apply_fetcheable(User.all)
|
31
|
+
render json: users
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
Now your API supports rich query parameters:
|
37
|
+
|
38
|
+
```bash
|
39
|
+
# Filter users by name and status
|
40
|
+
GET /users?filter[name]=john&filter[status]=active
|
41
|
+
|
42
|
+
# Sort by name ascending, then created_at descending
|
43
|
+
GET /users?sort=name,-created_at
|
44
|
+
|
45
|
+
# Paginate results
|
46
|
+
GET /users?page[number]=2&page[size]=25
|
47
|
+
|
48
|
+
# Combine all features
|
49
|
+
GET /users?filter[status]=active&sort=-created_at&page[number]=1&page[size]=10
|
50
|
+
```
|
51
|
+
|
52
|
+
## Table of Contents
|
53
|
+
|
54
|
+
- [Installation](#installation)
|
55
|
+
- [Configuration](#configuration)
|
56
|
+
- [Usage](#usage)
|
57
|
+
- [Basic Filtering](#basic-filtering)
|
58
|
+
- [Advanced Filtering](#advanced-filtering)
|
59
|
+
- [Sorting](#sorting)
|
60
|
+
- [Pagination](#pagination)
|
61
|
+
- [Association Filtering and Sorting](#association-filtering-and-sorting)
|
62
|
+
- [API Reference](#api-reference)
|
63
|
+
- [Filter Predicates](#filter-predicates)
|
64
|
+
- [Configuration Options](#configuration-options)
|
65
|
+
- [Examples](#examples)
|
66
|
+
- [Development](#development)
|
67
|
+
- [Contributing](#contributing)
|
68
|
+
- [License](#license)
|
5
69
|
|
6
70
|
## Installation
|
7
71
|
|
@@ -26,8 +90,254 @@ Finally, run the install generator:
|
|
26
90
|
It will create the following initializer `config/initializers/fetcheable_on_api.rb`.
|
27
91
|
This file contains all the informations about the existing configuration options.
|
28
92
|
|
93
|
+
## Configuration
|
94
|
+
|
95
|
+
Configure FetcheableOnApi in `config/initializers/fetcheable_on_api.rb`:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
FetcheableOnApi.configure do |config|
|
99
|
+
# Default number of records per page (default: 25)
|
100
|
+
config.pagination_default_size = 50
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
### Available Configuration Options
|
105
|
+
|
106
|
+
| Option | Description | Default | Example |
|
107
|
+
|--------|-------------|---------|---------|
|
108
|
+
| `pagination_default_size` | Default page size when not specified | `25` | `50` |
|
109
|
+
|
29
110
|
## Usage
|
30
111
|
|
112
|
+
### Basic Filtering
|
113
|
+
|
114
|
+
Configure which attributes can be filtered in your controllers:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
class UsersController < ApplicationController
|
118
|
+
# Allow filtering by these attributes
|
119
|
+
filter_by :name, :email, :status, :created_at
|
120
|
+
|
121
|
+
def index
|
122
|
+
users = apply_fetcheable(User.all)
|
123
|
+
render json: users
|
124
|
+
end
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
Examples of filter queries:
|
129
|
+
|
130
|
+
```bash
|
131
|
+
# Simple text filtering (uses ILIKE by default)
|
132
|
+
GET /users?filter[name]=john
|
133
|
+
|
134
|
+
# Multiple filters (AND logic between different fields)
|
135
|
+
GET /users?filter[name]=john&filter[status]=active
|
136
|
+
|
137
|
+
# Multiple values for same field (OR logic)
|
138
|
+
GET /users?filter[status]=active,pending
|
139
|
+
```
|
140
|
+
|
141
|
+
### Advanced Filtering
|
142
|
+
|
143
|
+
Use custom predicates for more specific filtering:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
class UsersController < ApplicationController
|
147
|
+
filter_by :name # Default: ilike (partial match)
|
148
|
+
filter_by :email, with: :eq # Exact match
|
149
|
+
filter_by :age, with: :gteq # Greater than or equal
|
150
|
+
filter_by :created_at, with: :between, format: :datetime
|
151
|
+
|
152
|
+
def index
|
153
|
+
users = apply_fetcheable(User.all)
|
154
|
+
render json: users
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
### Sorting
|
160
|
+
|
161
|
+
Configure sortable attributes:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
class UsersController < ApplicationController
|
165
|
+
# Allow sorting by these attributes
|
166
|
+
sort_by :name, :email, :created_at, :updated_at
|
167
|
+
|
168
|
+
# Case-insensitive sorting
|
169
|
+
sort_by :display_name, lower: true
|
170
|
+
|
171
|
+
def index
|
172
|
+
users = apply_fetcheable(User.all)
|
173
|
+
render json: users
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
Sorting query examples:
|
179
|
+
|
180
|
+
```bash
|
181
|
+
# Single field ascending
|
182
|
+
GET /users?sort=name
|
183
|
+
|
184
|
+
# Single field descending
|
185
|
+
GET /users?sort=-created_at
|
186
|
+
|
187
|
+
# Multiple fields (priority order)
|
188
|
+
GET /users?sort=status,-created_at,name
|
189
|
+
```
|
190
|
+
|
191
|
+
### Pagination
|
192
|
+
|
193
|
+
Pagination is automatically available and follows JSONAPI specification:
|
194
|
+
|
195
|
+
```bash
|
196
|
+
# Get page 2 with 25 records per page (default size)
|
197
|
+
GET /users?page[number]=2
|
198
|
+
|
199
|
+
# Custom page size
|
200
|
+
GET /users?page[number]=1&page[size]=50
|
201
|
+
|
202
|
+
# Combine with filtering and sorting
|
203
|
+
GET /users?filter[status]=active&sort=-created_at&page[number]=2&page[size]=10
|
204
|
+
```
|
205
|
+
|
206
|
+
Response headers automatically include pagination metadata:
|
207
|
+
|
208
|
+
```
|
209
|
+
Pagination-Current-Page: 2
|
210
|
+
Pagination-Per: 10
|
211
|
+
Pagination-Total-Pages: 15
|
212
|
+
Pagination-Total-Count: 150
|
213
|
+
```
|
214
|
+
|
215
|
+
### Association Filtering and Sorting
|
216
|
+
|
217
|
+
Filter and sort through model associations:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
class PostsController < ApplicationController
|
221
|
+
# Filter/sort by post attributes
|
222
|
+
filter_by :title, :content, :published
|
223
|
+
sort_by :title, :created_at
|
224
|
+
|
225
|
+
# Filter/sort by author name (User model)
|
226
|
+
filter_by :author, class_name: User, as: 'name'
|
227
|
+
sort_by :author, class_name: User, as: 'name'
|
228
|
+
|
229
|
+
# Sort by author name with explicit association (useful when field name differs from association)
|
230
|
+
sort_by :author_name, class_name: User, as: 'name', association: :author
|
231
|
+
|
232
|
+
# Filter by category name
|
233
|
+
filter_by :category, class_name: Category, as: 'name'
|
234
|
+
|
235
|
+
def index
|
236
|
+
# Make sure to join the associations
|
237
|
+
posts = apply_fetcheable(Post.joins(:author, :category))
|
238
|
+
render json: posts
|
239
|
+
end
|
240
|
+
end
|
241
|
+
```
|
242
|
+
|
243
|
+
Query examples:
|
244
|
+
|
245
|
+
```bash
|
246
|
+
# Filter by author name
|
247
|
+
GET /posts?filter[author]=john
|
248
|
+
|
249
|
+
# Sort by author name, then post creation date
|
250
|
+
GET /posts?sort=author,-created_at
|
251
|
+
|
252
|
+
# Sort by author name using explicit field name
|
253
|
+
GET /posts?sort=author_name
|
254
|
+
|
255
|
+
# Complex query with associations
|
256
|
+
GET /posts?filter[author]=john&filter[category]=tech&sort=author_name,-created_at
|
257
|
+
```
|
258
|
+
|
259
|
+
## API Reference
|
260
|
+
|
261
|
+
### Filter Predicates
|
262
|
+
|
263
|
+
FetcheableOnApi supports 30+ filter predicates for different data types and use cases:
|
264
|
+
|
265
|
+
#### Text Predicates
|
266
|
+
- `:ilike` (default) - Case-insensitive partial match (`ILIKE '%value%'`)
|
267
|
+
- `:eq` - Exact match (`= 'value'`)
|
268
|
+
- `:matches` - Pattern match with SQL wildcards
|
269
|
+
- `:does_not_match` - Inverse pattern match
|
270
|
+
|
271
|
+
#### Numeric/Date Predicates
|
272
|
+
- `:gt` - Greater than
|
273
|
+
- `:gteq` - Greater than or equal
|
274
|
+
- `:lt` - Less than
|
275
|
+
- `:lteq` - Less than or equal
|
276
|
+
- `:between` - Between two values (requires array)
|
277
|
+
|
278
|
+
#### Array Predicates
|
279
|
+
- `:in` - Value in list
|
280
|
+
- `:not_in` - Value not in list
|
281
|
+
- `:in_any` - Any value in list
|
282
|
+
- `:in_all` - All values in list
|
283
|
+
|
284
|
+
#### Custom Predicates
|
285
|
+
You can also define custom lambda predicates:
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
filter_by :full_name, with: ->(collection, value) {
|
289
|
+
collection.arel_table[:first_name].matches("%#{value}%").or(
|
290
|
+
collection.arel_table[:last_name].matches("%#{value}%")
|
291
|
+
)
|
292
|
+
}
|
293
|
+
```
|
294
|
+
|
295
|
+
## Examples
|
296
|
+
|
297
|
+
### Real-world API Controller
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
class API::V1::UsersController < ApplicationController
|
301
|
+
# Basic attribute filters
|
302
|
+
filter_by :email, with: :eq
|
303
|
+
filter_by :name, :username # Default :ilike predicate
|
304
|
+
filter_by :status, with: :in # Allow multiple values
|
305
|
+
filter_by :age, with: :gteq # Numeric comparison
|
306
|
+
filter_by :created_at, with: :between, format: :datetime
|
307
|
+
|
308
|
+
# Association filters
|
309
|
+
filter_by :company, class_name: Company, as: 'name'
|
310
|
+
filter_by :role, class_name: Role, as: 'name'
|
311
|
+
|
312
|
+
# Sorting configuration
|
313
|
+
sort_by :name, :username, :email, :created_at, :updated_at
|
314
|
+
sort_by :company, class_name: Company, as: 'name'
|
315
|
+
sort_by :display_name, lower: true # Case-insensitive sort
|
316
|
+
|
317
|
+
def index
|
318
|
+
users = apply_fetcheable(
|
319
|
+
User.joins(:company, :role)
|
320
|
+
.includes(:company, :role)
|
321
|
+
)
|
322
|
+
|
323
|
+
render json: users
|
324
|
+
end
|
325
|
+
end
|
326
|
+
```
|
327
|
+
|
328
|
+
### Example API Requests
|
329
|
+
|
330
|
+
```bash
|
331
|
+
# Find active users named John from Acme company, sorted by creation date
|
332
|
+
GET /api/v1/users?filter[name]=john&filter[status]=active&filter[company]=acme&sort=-created_at
|
333
|
+
|
334
|
+
# Users created in the last month, paginated
|
335
|
+
GET /api/v1/users?filter[created_at]=1640995200,1643673600&page[number]=1&page[size]=20
|
336
|
+
|
337
|
+
# Search users by partial name, case-insensitive sort
|
338
|
+
GET /api/v1/users?filter[name]=john&sort=display_name
|
339
|
+
```
|
340
|
+
|
31
341
|
Imagine the following models called question and answer:
|
32
342
|
|
33
343
|
```ruby
|
@@ -505,6 +815,10 @@ $ curl -X GET \
|
|
505
815
|
]
|
506
816
|
```
|
507
817
|
|
818
|
+
By default fetcheable_on_api will join the associated model using the
|
819
|
+
`class_name` option you have provided. If another association should be used as
|
820
|
+
the target, use the `association:` option instead.
|
821
|
+
|
508
822
|
Furthermore you can specify one of the supported `Arel` predicate.
|
509
823
|
|
510
824
|
```ruby
|