fetcheable_on_api 0.4.1 → 0.6.1
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 +311 -1
- data/lib/fetcheable_on_api/configuration.rb +33 -2
- data/lib/fetcheable_on_api/filterable.rb +274 -75
- 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
data/lib/fetcheable_on_api.rb
CHANGED
@@ -1,49 +1,96 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
3
|
+
require 'fetcheable_on_api/configuration'
|
4
|
+
require 'fetcheable_on_api/filterable'
|
5
|
+
require 'fetcheable_on_api/pageable'
|
6
|
+
require 'fetcheable_on_api/sortable'
|
7
|
+
require 'fetcheable_on_api/version'
|
8
|
+
require 'active_support'
|
9
|
+
require 'date'
|
10
10
|
|
11
11
|
# FetcheableOnApi provides standardized sorting, filtering and pagination for
|
12
|
-
#
|
12
|
+
# Rails API controllers following the JSONAPI specification.
|
13
13
|
#
|
14
|
+
# This gem automatically adds support for query parameters like:
|
15
|
+
# - `filter[attribute]=value` for filtering data
|
16
|
+
# - `sort=attribute1,-attribute2` for sorting (- prefix for descending)
|
17
|
+
# - `page[number]=1&page[size]=25` for pagination
|
18
|
+
#
|
19
|
+
# @example Basic usage in a controller
|
20
|
+
# class UsersController < ApplicationController
|
21
|
+
# # Configure allowed filters and sorts
|
22
|
+
# filter_by :name, :email, :status
|
23
|
+
# sort_by :name, :created_at, :updated_at
|
24
|
+
#
|
25
|
+
# def index
|
26
|
+
# users = apply_fetcheable(User.all)
|
27
|
+
# render json: users
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# @example Using with associations
|
32
|
+
# class PostsController < ApplicationController
|
33
|
+
# filter_by :title
|
34
|
+
# filter_by :author, class_name: User, as: 'name'
|
35
|
+
# sort_by :title, :created_at
|
36
|
+
# sort_by :author, class_name: User, as: 'name'
|
37
|
+
#
|
38
|
+
# def index
|
39
|
+
# posts = apply_fetcheable(Post.joins(:author).includes(:author))
|
40
|
+
# render json: posts
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @author Fabien Piette
|
45
|
+
# @since 0.1.0
|
14
46
|
module FetcheableOnApi
|
15
|
-
#
|
16
|
-
#
|
47
|
+
# Global configuration settings for FetcheableOnApi.
|
48
|
+
# This method provides access to the singleton configuration instance
|
49
|
+
# that can be used to customize default behavior across the application.
|
17
50
|
#
|
18
51
|
# @example Set default pagination size
|
19
52
|
# FetcheableOnApi.configuration.pagination_default_size = 25
|
20
53
|
#
|
21
54
|
# @return [Configuration] The global configuration instance
|
55
|
+
# @see Configuration
|
22
56
|
def self.configuration
|
23
57
|
@configuration ||= Configuration.new
|
24
58
|
end
|
25
59
|
|
26
60
|
# Configure FetcheableOnApi using a block.
|
61
|
+
# This is the recommended way to set up configuration in an initializer.
|
27
62
|
#
|
28
|
-
# @example Set default pagination size
|
63
|
+
# @example Set default pagination size in config/initializers/fetcheable_on_api.rb
|
29
64
|
# FetcheableOnApi.configure do |config|
|
30
|
-
# config.pagination_default_size =
|
65
|
+
# config.pagination_default_size = 50
|
31
66
|
# end
|
32
67
|
#
|
33
|
-
# @yield [Configuration] Gives the global instance to the block
|
68
|
+
# @yield [Configuration] Gives the global configuration instance to the block
|
69
|
+
# @see Configuration
|
34
70
|
def self.configure
|
35
71
|
yield(configuration)
|
36
72
|
end
|
37
73
|
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
74
|
+
# Custom exception classes for FetcheableOnApi-specific errors.
|
75
|
+
# These inherit from standard Ruby exceptions but allow for more
|
76
|
+
# specific error handling in applications using this gem.
|
77
|
+
|
78
|
+
# Raised when invalid parameters are provided to filtering, sorting, or pagination
|
79
|
+
# @example
|
80
|
+
# raise FetcheableOnApi::ArgumentError, "Invalid filter parameter type"
|
41
81
|
ArgumentError = Class.new(ArgumentError)
|
82
|
+
|
83
|
+
# Raised when a feature is not yet implemented or supported
|
84
|
+
# @example
|
85
|
+
# raise FetcheableOnApi::NotImplementedError, "Custom predicate not supported"
|
42
86
|
NotImplementedError = Class.new(NotImplementedError)
|
43
87
|
|
88
|
+
# Hook called when this module is included in a class.
|
89
|
+
# Automatically includes the three main concern modules that provide
|
90
|
+
# filtering, sorting, and pagination functionality.
|
44
91
|
#
|
45
|
-
#
|
46
|
-
#
|
92
|
+
# @param klass [Class] The class that is including FetcheableOnApi
|
93
|
+
# @private
|
47
94
|
def self.included(klass)
|
48
95
|
klass.class_eval do
|
49
96
|
include Filterable
|
@@ -52,60 +99,131 @@ module FetcheableOnApi
|
|
52
99
|
end
|
53
100
|
end
|
54
101
|
|
102
|
+
# Protected instance methods available to controllers that include this module
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
# Apply filters, sorting, and pagination to a collection in sequence.
|
107
|
+
# This is the main entry point for processing JSONAPI query parameters.
|
55
108
|
#
|
56
|
-
#
|
109
|
+
# The operations are applied in this specific order:
|
110
|
+
# 1. Filtering (apply_filters) - reduces the dataset
|
111
|
+
# 2. Sorting (apply_sort) - orders the results
|
112
|
+
# 3. Pagination (apply_pagination) - limits and offsets for page
|
57
113
|
#
|
58
|
-
|
114
|
+
# @param collection [ActiveRecord::Relation] The base collection to process
|
115
|
+
# @return [ActiveRecord::Relation] The processed collection with filters, sorting, and pagination applied
|
59
116
|
#
|
60
|
-
#
|
117
|
+
# @example Basic usage
|
118
|
+
# def index
|
119
|
+
# users = apply_fetcheable(User.all)
|
120
|
+
# render json: users
|
121
|
+
# end
|
61
122
|
#
|
62
|
-
|
63
|
-
|
64
|
-
#
|
123
|
+
# @example With joins for association filtering/sorting
|
124
|
+
# def index
|
125
|
+
# posts = apply_fetcheable(Post.joins(:author).includes(:author))
|
126
|
+
# render json: posts
|
127
|
+
# end
|
65
128
|
def apply_fetcheable(collection)
|
129
|
+
# Apply filtering first to reduce dataset size
|
66
130
|
collection = apply_filters(collection)
|
131
|
+
|
132
|
+
# Apply sorting to the filtered results
|
67
133
|
collection = apply_sort(collection)
|
68
134
|
|
135
|
+
# Apply pagination last to get the final page
|
69
136
|
apply_pagination(collection)
|
70
137
|
end
|
71
138
|
|
72
|
-
#
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
139
|
+
# Validates that the specified parameter keys contain values of permitted types.
|
140
|
+
# This is used internally by the filtering, sorting, and pagination modules
|
141
|
+
# to ensure that malformed or malicious parameters don't cause errors.
|
142
|
+
#
|
143
|
+
# @param keys [Array<Symbol>] Path to the parameter to validate (e.g., [:filter], [:page, :number])
|
144
|
+
# @param foa_permitted_types [Array<Class>] Array of allowed parameter types
|
145
|
+
# @raise [FetcheableOnApi::ArgumentError] When parameter type is not in permitted types
|
146
|
+
#
|
147
|
+
# @example
|
148
|
+
# # Validates that params[:filter] is a Hash or ActionController::Parameters
|
149
|
+
# foa_valid_parameters!(:filter)
|
150
|
+
#
|
151
|
+
# # Validates that params[:sort] is a String
|
152
|
+
# foa_valid_parameters!(:sort, foa_permitted_types: [String])
|
153
|
+
#
|
154
|
+
# @private
|
155
|
+
def foa_valid_parameters!(*keys, foa_permitted_types: foa_default_permitted_types)
|
156
|
+
return if foa_valid_params_types(*keys, foa_permitted_types: foa_permitted_types)
|
79
157
|
|
158
|
+
actual_type = params.dig(*keys).class
|
80
159
|
raise FetcheableOnApi::ArgumentError,
|
81
|
-
"Incorrect type #{
|
160
|
+
"Incorrect type #{actual_type} for params #{keys}"
|
82
161
|
end
|
83
162
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
163
|
+
# Checks if the parameter value at the specified keys matches any of the permitted types.
|
164
|
+
#
|
165
|
+
# @param keys [Array<Symbol>] Path to the parameter to check
|
166
|
+
# @param foa_permitted_types [Array<Class>] Array of allowed parameter types
|
167
|
+
# @return [Boolean] True if the parameter type is valid, false otherwise
|
168
|
+
# @private
|
169
|
+
def foa_valid_params_types(*keys, foa_permitted_types: foa_default_permitted_types)
|
170
|
+
foa_permitted_types.inject(false) do |result, type|
|
171
|
+
result || foa_valid_params_type(params.dig(*keys), type)
|
88
172
|
end
|
89
173
|
end
|
90
174
|
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
175
|
+
# Checks if a value is of the specified type using Ruby's is_a? method.
|
176
|
+
# This handles inheritance and module inclusion correctly.
|
177
|
+
#
|
178
|
+
# @param value [Object] The value to type-check
|
179
|
+
# @param type [Class] The expected type/class
|
180
|
+
# @return [Boolean] True if value is an instance of type (or its subclass/module)
|
181
|
+
# @private
|
94
182
|
def foa_valid_params_type(value, type)
|
95
183
|
value.is_a?(type)
|
96
184
|
end
|
97
185
|
|
98
|
-
#
|
186
|
+
# Default permitted parameter types for most operations.
|
187
|
+
# ActionController::Parameters is the standard Rails params object,
|
188
|
+
# while Hash is allowed for direct hash parameters in tests or non-Rails usage.
|
189
|
+
#
|
190
|
+
# @return [Array<Class>] Array of default permitted parameter types
|
191
|
+
# @private
|
99
192
|
def foa_default_permitted_types
|
100
193
|
[ActionController::Parameters, Hash]
|
101
194
|
end
|
102
195
|
|
103
|
-
# Convert string to
|
196
|
+
# Convert string timestamp to DateTime object.
|
197
|
+
# This is used for date/time filtering when the format is set to :datetime.
|
198
|
+
# By default, it expects Unix epoch timestamps as strings.
|
199
|
+
#
|
200
|
+
# This method can be overridden in controllers to support different date formats:
|
201
|
+
#
|
202
|
+
# @param string [String] The timestamp string to convert
|
203
|
+
# @return [DateTime] The parsed DateTime object
|
204
|
+
#
|
205
|
+
# @example Override in controller for custom date format
|
206
|
+
# class UsersController < ApplicationController
|
207
|
+
# private
|
208
|
+
#
|
209
|
+
# def foa_string_to_datetime(string)
|
210
|
+
# DateTime.strptime(string, '%Y-%m-%d %H:%M:%S')
|
211
|
+
# end
|
212
|
+
# end
|
213
|
+
#
|
214
|
+
# @example Default usage with epoch timestamps
|
215
|
+
# foa_string_to_datetime('1609459200') # => 2021-01-01 00:00:00 +0000
|
104
216
|
def foa_string_to_datetime(string)
|
105
|
-
DateTime.strptime(string,
|
217
|
+
DateTime.strptime(string, '%s')
|
106
218
|
end
|
107
219
|
end
|
108
220
|
|
221
|
+
# Automatically include FetcheableOnApi in all ActionController classes when Rails loads.
|
222
|
+
# This makes the filtering, sorting, and pagination functionality available
|
223
|
+
# to all controllers without requiring manual inclusion.
|
224
|
+
#
|
225
|
+
# @note This uses ActiveSupport's lazy loading mechanism to ensure ActionController
|
226
|
+
# is fully loaded before including the module.
|
109
227
|
ActiveSupport.on_load :action_controller do
|
110
228
|
include FetcheableOnApi
|
111
229
|
end
|
@@ -1,10 +1,24 @@
|
|
1
1
|
module FetcheableOnApi
|
2
2
|
module Generators
|
3
|
-
#
|
3
|
+
# Rails generator for creating FetcheableOnApi initializer file.
|
4
|
+
#
|
5
|
+
# This generator creates a configuration initializer file that allows
|
6
|
+
# developers to customize FetcheableOnApi settings for their application.
|
7
|
+
#
|
8
|
+
# @example Running the generator
|
9
|
+
# rails generate fetcheable_on_api:install
|
10
|
+
# # Creates: config/initializers/fetcheable_on_api.rb
|
11
|
+
#
|
12
|
+
# @since 0.1.0
|
4
13
|
class InstallGenerator < Rails::Generators::Base
|
5
14
|
source_root File.expand_path('../templates', __dir__)
|
6
15
|
desc 'Creates FetcheableOnApi initializer for your application'
|
7
16
|
|
17
|
+
# Copy the initializer template to the Rails application's config/initializers directory.
|
18
|
+
# The generated file contains configuration options with sensible defaults and
|
19
|
+
# documentation about available settings.
|
20
|
+
#
|
21
|
+
# @return [void]
|
8
22
|
def copy_initializer
|
9
23
|
template 'fetcheable_on_api_initializer.rb',
|
10
24
|
'config/initializers/fetcheable_on_api.rb'
|
@@ -1,3 +1,17 @@
|
|
1
|
+
# FetcheableOnApi configuration
|
2
|
+
#
|
3
|
+
# This initializer configures the FetcheableOnApi gem settings for your application.
|
4
|
+
# These settings affect the behavior of filtering, sorting, and pagination across
|
5
|
+
# all controllers that use the FetcheableOnApi module.
|
6
|
+
|
1
7
|
FetcheableOnApi.configure do |config|
|
8
|
+
# Default number of records per page when no page[size] parameter is provided.
|
9
|
+
# This affects the Pageable module when clients don't specify a page size.
|
10
|
+
#
|
11
|
+
# Examples:
|
12
|
+
# - With default (25): GET /users?page[number]=2 returns 25 records
|
13
|
+
# - With custom (50): GET /users?page[number]=2 returns 50 records
|
14
|
+
#
|
15
|
+
# Default: 25
|
2
16
|
config.pagination_default_size = 25
|
3
17
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fetcheable_on_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fabien
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -30,28 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '2.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '2.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '13.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '13.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,6 +87,8 @@ executables: []
|
|
87
87
|
extensions: []
|
88
88
|
extra_rdoc_files: []
|
89
89
|
files:
|
90
|
+
- ASSOCIATION_SORTING_SOLUTION.md
|
91
|
+
- CLAUDE.md
|
90
92
|
- CODE_OF_CONDUCT.md
|
91
93
|
- Gemfile
|
92
94
|
- Gemfile.lock
|
@@ -121,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
123
|
- !ruby/object:Gem::Version
|
122
124
|
version: '0'
|
123
125
|
requirements: []
|
124
|
-
rubygems_version: 3.
|
126
|
+
rubygems_version: 3.1.6
|
125
127
|
signing_key:
|
126
128
|
specification_version: 4
|
127
129
|
summary: A controller filters engine gem based on jsonapi spec.
|