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.
@@ -1,49 +1,96 @@
1
1
  # frozen_string_literal: true
2
2
 
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"
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
- # you API controllers.
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
- # Global configuration settings for FetcheableOnApi
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 = 25
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
- # Supports
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
- # Public class methods
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
- # Public instance methods
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
- # Protected instance methods
117
+ # @example Basic usage
118
+ # def index
119
+ # users = apply_fetcheable(User.all)
120
+ # render json: users
121
+ # end
61
122
  #
62
- protected
63
-
64
- # Apply filters, sort and page on a collection.
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
- # Checks if the type of arguments is included in the permitted types
73
- def foa_valid_parameters!(
74
- *keys, foa_permitted_types: foa_default_permitted_types)
75
- return if foa_valid_params_types(
76
- *keys,
77
- foa_permitted_types: foa_permitted_types,
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 #{params.dig(*keys).class} for params #{keys}"
160
+ "Incorrect type #{actual_type} for params #{keys}"
82
161
  end
83
162
 
84
- def foa_valid_params_types(
85
- *keys, foa_permitted_types: foa_default_permitted_types)
86
- foa_permitted_types.inject(false) do |res, type|
87
- res || foa_valid_params_type(params.dig(*keys), type)
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
- # Returns true if class is the class of value,
92
- # or if class is one of the superclasses of value
93
- # or modules included in value.
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
- # Types allowed by default.
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 datetime.
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, "%s")
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
- # Create conf file
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.1
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: 2020-01-13 00:00:00.000000000 Z
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: '1.16'
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: '1.16'
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: '10.0'
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: '10.0'
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.0.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.