propel_api 0.3.2 → 0.3.4
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/CHANGELOG.md +66 -0
- data/FILTERING_DOCUMENTATION.md +470 -0
- data/README.md +46 -1
- data/lib/generators/propel_api/core/named_base.rb +47 -76
- data/lib/generators/propel_api/install/install_generator.rb +43 -2
- data/lib/generators/propel_api/templates/concerns/propel_controller_filters_concern.rb +141 -0
- data/lib/generators/propel_api/templates/concerns/propel_model_filters_concern.rb +128 -0
- data/lib/generators/propel_api/templates/controllers/api_controller_propel_facets.rb +1 -0
- data/lib/generators/propel_api/templates/lib/XX_dynamic_scope_generator.rb +360 -0
- data/lib/generators/propel_api/templates/lib/propel_dynamic_scope_generator.rb +473 -0
- data/lib/generators/propel_api/templates/lib/propel_filter_operators.rb +16 -0
- data/lib/generators/propel_api/templates/scaffold/facet_model_template.rb.tt +14 -0
- data/lib/generators/propel_api/templates/seeds/seeds_template.rb.tt +100 -6
- data/lib/generators/propel_api/templates/tests/controller_test_template.rb.tt +9 -0
- data/lib/generators/propel_api/templates/tests/integration_test_template.rb.tt +660 -10
- data/lib/generators/propel_api/templates/tests/model_test_template.rb.tt +7 -0
- data/lib/propel_api.rb +1 -1
- metadata +12 -5
@@ -1263,38 +1263,54 @@ module PropelApi
|
|
1263
1263
|
say "="*70, :red
|
1264
1264
|
end
|
1265
1265
|
|
1266
|
-
# Route management helper methods
|
1266
|
+
# Route management helper methods - using Rails' gsub_file for proper insertion
|
1267
1267
|
def insert_routes
|
1268
|
+
return if route_already_exists?
|
1269
|
+
|
1270
|
+
if @api_namespace.present? && @api_version.present?
|
1271
|
+
insert_nested_namespace_route
|
1272
|
+
elsif @api_namespace.present?
|
1273
|
+
insert_single_namespace_route
|
1274
|
+
else
|
1275
|
+
route "resources :#{route_name}"
|
1276
|
+
end
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
private
|
1280
|
+
|
1281
|
+
def insert_nested_namespace_route
|
1268
1282
|
routes_file = File.join(destination_root, "config/routes.rb")
|
1269
|
-
return unless File.exist?(routes_file)
|
1270
|
-
|
1271
1283
|
routes_content = File.read(routes_file)
|
1272
1284
|
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
# Rails route method adds 2 spaces to each line, so we need to adjust for that
|
1281
|
-
route_content = "namespace :#{@api_namespace} do\n namespace :#{@api_version} do\n resources :#{route_name}\n end\nend"
|
1282
|
-
route route_content
|
1285
|
+
# Look for the nested namespace pattern and insert after it (flexible whitespace matching)
|
1286
|
+
nested_pattern = /namespace\s+:#{@api_namespace}\s+do\s*\n\s*namespace\s+:#{@api_version}\s+do\s*\n/
|
1287
|
+
|
1288
|
+
if routes_content.match?(nested_pattern)
|
1289
|
+
# Use gsub_file to replace the namespace opening with namespace + our resource
|
1290
|
+
gsub_file "config/routes.rb", nested_pattern do |match|
|
1291
|
+
match + " resources :#{route_name}\n"
|
1283
1292
|
end
|
1284
|
-
|
1285
|
-
#
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1293
|
+
else
|
1294
|
+
# Create new namespace block using Rails' route method
|
1295
|
+
route "namespace :#{@api_namespace} do\n namespace :#{@api_version} do\n resources :#{route_name}\n end\nend"
|
1296
|
+
end
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
def insert_single_namespace_route
|
1300
|
+
routes_file = File.join(destination_root, "config/routes.rb")
|
1301
|
+
routes_content = File.read(routes_file)
|
1302
|
+
|
1303
|
+
# Look for the single namespace pattern and insert after it
|
1304
|
+
single_pattern = /namespace\s+:#{@api_namespace}\s+do\s*\n/
|
1305
|
+
|
1306
|
+
if routes_content.match?(single_pattern)
|
1307
|
+
# Use gsub_file to replace the namespace opening with namespace + our resource
|
1308
|
+
gsub_file "config/routes.rb", single_pattern do |match|
|
1309
|
+
match + " resources :#{route_name}\n"
|
1294
1310
|
end
|
1295
1311
|
else
|
1296
|
-
#
|
1297
|
-
route "resources :#{route_name}"
|
1312
|
+
# Create new namespace block using Rails' route method
|
1313
|
+
route "namespace :#{@api_namespace} do\n resources :#{route_name}\nend"
|
1298
1314
|
end
|
1299
1315
|
end
|
1300
1316
|
|
@@ -1336,58 +1352,13 @@ module PropelApi
|
|
1336
1352
|
end
|
1337
1353
|
end
|
1338
1354
|
|
1339
|
-
def
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
def has_single_namespace?(content, namespace)
|
1344
|
-
content.match?(/namespace\s+:#{namespace}\s+do/)
|
1345
|
-
end
|
1346
|
-
|
1347
|
-
def insert_into_nested_namespace(content, namespace, version, resource_line)
|
1348
|
-
# Check if resource already exists
|
1349
|
-
existing_pattern = /namespace\s+:#{namespace}\s+do.*?namespace\s+:#{version}\s+do.*?resources\s+:#{route_name}/m
|
1350
|
-
|
1351
|
-
if content.match?(existing_pattern)
|
1352
|
-
say "Route for #{route_name} already exists in #{namespace}/#{version} namespace", :yellow
|
1353
|
-
return
|
1354
|
-
end
|
1355
|
-
|
1356
|
-
# Find the position to insert the resource line
|
1357
|
-
# Look for the end of the nested namespace's opening line
|
1358
|
-
after_pattern = /namespace\s+:#{namespace}\s+do\s*\n\s*namespace\s+:#{version}\s+do\s*\n/
|
1359
|
-
|
1360
|
-
if content.match?(after_pattern)
|
1361
|
-
# Insert the resource line with proper indentation (4 spaces for nested namespace)
|
1362
|
-
insert_into_file "config/routes.rb", "#{resource_line}\n", :after => after_pattern
|
1363
|
-
else
|
1364
|
-
# This shouldn't happen if has_nested_namespace? returned true, but handle it
|
1365
|
-
route_content = "namespace :#{namespace} do\n namespace :#{version} do\n#{resource_line}\n end\nend"
|
1366
|
-
route route_content
|
1367
|
-
end
|
1368
|
-
end
|
1369
|
-
|
1370
|
-
def insert_into_single_namespace(content, namespace, resource_line)
|
1371
|
-
# Check if resource already exists
|
1372
|
-
existing_pattern = /namespace\s+:#{namespace}\s+do.*?resources\s+:#{route_name}/m
|
1373
|
-
|
1374
|
-
if content.match?(existing_pattern)
|
1375
|
-
say "Route for #{route_name} already exists in #{namespace} namespace", :yellow
|
1376
|
-
return
|
1377
|
-
end
|
1378
|
-
|
1379
|
-
# Find the position to insert the resource line
|
1380
|
-
# Look for the end of the namespace's opening line
|
1381
|
-
after_pattern = /namespace\s+:#{namespace}\s+do\s*\n/
|
1355
|
+
def route_already_exists?
|
1356
|
+
routes_file = File.join(destination_root, "config/routes.rb")
|
1357
|
+
return false unless File.exist?(routes_file)
|
1382
1358
|
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
else
|
1387
|
-
# This shouldn't happen if has_single_namespace? returned true, but handle it
|
1388
|
-
route_content = "namespace :#{namespace} do\n resources :#{route_name}\n end"
|
1389
|
-
route route_content
|
1390
|
-
end
|
1359
|
+
routes_content = File.read(routes_file)
|
1360
|
+
# Check for actual route declarations (not comments) - must be at start of line or after whitespace
|
1361
|
+
routes_content.match?(/^\s*resources\s+:#{Regexp.escape(route_name)}(\s|$)/)
|
1391
1362
|
end
|
1392
1363
|
|
1393
1364
|
def cleanup_empty_namespaces(content)
|
@@ -37,6 +37,9 @@ module PropelApi
|
|
37
37
|
|
38
38
|
# Optionally enhance CSRF error messages (non-breaking)
|
39
39
|
add_csrf_error_handling
|
40
|
+
|
41
|
+
# Add dynamic filtering capabilities to ApplicationRecord
|
42
|
+
add_model_filters_concern
|
40
43
|
end
|
41
44
|
|
42
45
|
case @adapter
|
@@ -113,6 +116,28 @@ module PropelApi
|
|
113
116
|
|
114
117
|
private
|
115
118
|
|
119
|
+
def copy_propel_facets_concerns
|
120
|
+
if behavior == :revoke
|
121
|
+
# Remove concerns
|
122
|
+
remove_file "app/controllers/concerns/propel_controller_filters_concern.rb"
|
123
|
+
remove_file "app/models/concerns/propel_model_filters_concern.rb"
|
124
|
+
remove_file "lib/propel_filter_operators.rb"
|
125
|
+
remove_file "lib/propel_dynamic_scope_generator.rb"
|
126
|
+
say "🗑️ Removed PropelApi filtering concerns", :red
|
127
|
+
else
|
128
|
+
# Copy concerns to host app using XX-prefixed source files
|
129
|
+
copy_file "concerns/propel_controller_filters_concern.rb", "app/controllers/concerns/propel_controller_filters_concern.rb"
|
130
|
+
copy_file "concerns/propel_model_filters_concern.rb", "app/models/concerns/propel_model_filters_concern.rb"
|
131
|
+
copy_file "lib/propel_filter_operators.rb", "lib/propel_filter_operators.rb"
|
132
|
+
copy_file "lib/propel_dynamic_scope_generator.rb", "lib/propel_dynamic_scope_generator.rb"
|
133
|
+
say "📦 Copied PropelApi filtering concerns to app", :green
|
134
|
+
say " - Controller filtering: app/controllers/concerns/propel_controller_filters_concern.rb", :blue
|
135
|
+
say " - Model filtering: app/models/concerns/propel_model_filters_concern.rb", :blue
|
136
|
+
say " - Filter operators: lib/propel_filter_operators.rb", :blue
|
137
|
+
say " - Dynamic scope generator: lib/propel_dynamic_scope_generator.rb", :blue
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
116
141
|
def create_api_base_controller
|
117
142
|
template "controllers/api_base_controller.rb", "app/controllers/api/base_controller.rb"
|
118
143
|
say "Created Api::BaseController for CSRF-free API endpoints", :green
|
@@ -144,6 +169,18 @@ module PropelApi
|
|
144
169
|
say "You can manually add the rescue_from handler if desired", :blue
|
145
170
|
end
|
146
171
|
|
172
|
+
def add_model_filters_concern
|
173
|
+
# Inject PropelModelFiltersConcern into ApplicationRecord
|
174
|
+
inject_into_class "app/models/application_record.rb", ApplicationRecord, <<-RUBY
|
175
|
+
include PropelModelFiltersConcern
|
176
|
+
RUBY
|
177
|
+
|
178
|
+
say "Enhanced ApplicationRecord with dynamic filtering capabilities", :green
|
179
|
+
rescue => e
|
180
|
+
say "Could not inject PropelModelFiltersConcern into ApplicationRecord: #{e.message}", :yellow
|
181
|
+
say "You can manually add 'include PropelModelFiltersConcern' to ApplicationRecord if desired", :blue
|
182
|
+
end
|
183
|
+
|
147
184
|
def copy_propel_facets_controller
|
148
185
|
template "controllers/api_controller_propel_facets.rb", api_controller_path
|
149
186
|
copy_example_controller
|
@@ -256,8 +293,12 @@ module PropelApi
|
|
256
293
|
say " end", :yellow
|
257
294
|
say "\n5. See doc/api_controller_example.rb for complete usage examples"
|
258
295
|
say "6. Your API endpoints will be available at: #{api_route_prefix}/..."
|
259
|
-
say "7. Pagination and filtering are built-in via Pagy and
|
260
|
-
say "
|
296
|
+
say "7. Pagination and secure filtering are built-in via Pagy and PropelApi filtering"
|
297
|
+
say "8. Filtering examples:"
|
298
|
+
say " GET /api/v1/users?name_contains=john&active_eq=true&sort=-created_at", :yellow
|
299
|
+
say " - Secure field validation prevents SQL injection"
|
300
|
+
say " - Sensitive fields (passwords, tokens) automatically excluded", :cyan
|
301
|
+
say "\n9. Generate resources using:"
|
261
302
|
say " rails generate propel_api Resource field1:type field2:type", :yellow
|
262
303
|
end
|
263
304
|
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# DynamicControllerFiltersConcern
|
4
|
+
#
|
5
|
+
# Provides dynamic filtering capabilities for API controllers using has_scope.
|
6
|
+
# This concern automatically generates and registers scopes based on the model's
|
7
|
+
# database columns and their types.
|
8
|
+
#
|
9
|
+
# Features:
|
10
|
+
# - Automatic scope generation based on column types
|
11
|
+
# - Security validation of filter parameters
|
12
|
+
# - Error handling for malformed filters
|
13
|
+
# - Support for all standard filter operators
|
14
|
+
#
|
15
|
+
# Usage:
|
16
|
+
# class Api::V1::TeamsController < Api::V1::ApiController
|
17
|
+
# include PropelControllerFiltersConcern
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
module PropelControllerFiltersConcern
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
included do
|
24
|
+
include HasScope
|
25
|
+
|
26
|
+
# Track which scopes have been registered to avoid duplicates
|
27
|
+
class_attribute :_registered_scopes, default: Set.new
|
28
|
+
|
29
|
+
# Register dynamic scopes when the controller is first used
|
30
|
+
before_action :ensure_dynamic_scopes_registered, prepend: true
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def ensure_dynamic_scopes_registered
|
36
|
+
return if self.class._registered_scopes.include?(resource_class.name)
|
37
|
+
|
38
|
+
model_class = resource_class
|
39
|
+
return unless model_class&.respond_to?(:columns)
|
40
|
+
|
41
|
+
# Ensure scopes are generated for the model (lazy loading)
|
42
|
+
model_class.ensure_scopes_generated!
|
43
|
+
|
44
|
+
# Generate scopes for the model
|
45
|
+
generator = PropelDynamicScopeGenerator.new(model_class)
|
46
|
+
generator.generate_scopes!
|
47
|
+
|
48
|
+
# Register has_scope calls for this controller
|
49
|
+
generator.register_controller_scopes(self.class)
|
50
|
+
|
51
|
+
# Mark as registered to avoid duplicate work
|
52
|
+
self.class._registered_scopes << model_class.name
|
53
|
+
rescue StandardError => e
|
54
|
+
Rails.logger.error "Error registering dynamic scopes: #{e.message}"
|
55
|
+
Rails.logger.error e.backtrace.join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
# Override apply_scopes to add security validation and error handling
|
59
|
+
def apply_scopes(base_scope)
|
60
|
+
# Filter out any potentially dangerous or unsupported parameters
|
61
|
+
safe_params = filter_safe_parameters(params)
|
62
|
+
|
63
|
+
# Apply scopes with error handling
|
64
|
+
result = super(base_scope, safe_params)
|
65
|
+
result || base_scope
|
66
|
+
rescue StandardError => e
|
67
|
+
Rails.logger.warn "Error in apply_scopes: #{e.message}"
|
68
|
+
base_scope
|
69
|
+
end
|
70
|
+
|
71
|
+
# Security: Filter out dangerous or unsupported parameters
|
72
|
+
def filter_safe_parameters(params)
|
73
|
+
return {} unless params.is_a?(ActionController::Parameters)
|
74
|
+
|
75
|
+
# Get allowed filter parameters based on model columns
|
76
|
+
allowed_filters = allowed_filter_parameters
|
77
|
+
|
78
|
+
# Filter to only include known, safe parameters
|
79
|
+
safe_params = params.slice(*allowed_filters)
|
80
|
+
|
81
|
+
# Log any filtered out parameters for security monitoring
|
82
|
+
filtered_params = params.except(*allowed_filters).except(:controller, :action, :format)
|
83
|
+
if filtered_params.present?
|
84
|
+
Rails.logger.warn "Filtered out potentially unsafe parameters: #{filtered_params.keys.join(', ')}"
|
85
|
+
end
|
86
|
+
|
87
|
+
safe_params
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get list of allowed filter parameters based on model columns
|
91
|
+
def allowed_filter_parameters
|
92
|
+
return [] unless resource_class&.respond_to?(:columns)
|
93
|
+
|
94
|
+
allowed = []
|
95
|
+
|
96
|
+
resource_class.columns.each do |column|
|
97
|
+
name = column.name
|
98
|
+
type = column.type
|
99
|
+
sql_type = column.sql_type_metadata.sql_type
|
100
|
+
|
101
|
+
# Skip sensitive fields
|
102
|
+
next if sensitive_field?(name)
|
103
|
+
|
104
|
+
# Get operators for this column type
|
105
|
+
operators = PropelFilterOperators.operators_for(type, sql_type)
|
106
|
+
|
107
|
+
# Add parameter names for each operator
|
108
|
+
operators.each do |operator|
|
109
|
+
allowed << "#{name}_#{operator}".to_sym
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Add special parameters
|
114
|
+
allowed += [:order_by, :page, :limit, :per_page]
|
115
|
+
|
116
|
+
allowed
|
117
|
+
end
|
118
|
+
|
119
|
+
# Check if a field name is considered sensitive and should not be filterable
|
120
|
+
def sensitive_field?(field_name)
|
121
|
+
sensitive_fields = %w[
|
122
|
+
password password_hash password_digest
|
123
|
+
token secret_key api_key
|
124
|
+
encrypted_data encrypted_password
|
125
|
+
salt pepper
|
126
|
+
private_key public_key
|
127
|
+
ssn social_security_number
|
128
|
+
credit_card_number
|
129
|
+
bank_account_number
|
130
|
+
]
|
131
|
+
|
132
|
+
sensitive_fields.any? { |sensitive| field_name.downcase.include?(sensitive) }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Override to provide better error messages for invalid filters
|
136
|
+
def handle_invalid_filter(filter_name, error)
|
137
|
+
Rails.logger.warn "Invalid filter '#{filter_name}': #{error.message}"
|
138
|
+
# Don't raise the error, just log it and continue
|
139
|
+
# This prevents one bad filter from breaking the entire request
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# DynamicModelFiltersConcern
|
4
|
+
#
|
5
|
+
# Provides dynamic filtering capabilities for ActiveRecord models.
|
6
|
+
# This concern automatically generates scopes based on the model's
|
7
|
+
# database columns and their types.
|
8
|
+
#
|
9
|
+
# Features:
|
10
|
+
# - Automatic scope generation based on column types
|
11
|
+
# - Support for all standard filter operators
|
12
|
+
# - Type-aware filtering (boolean, string, numeric, datetime)
|
13
|
+
# - Security validation of filter parameters
|
14
|
+
#
|
15
|
+
# Usage:
|
16
|
+
# class Team < ApplicationRecord
|
17
|
+
# include PropelModelFiltersConcern
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
module PropelModelFiltersConcern
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
included do
|
24
|
+
# Track which scopes have been generated to avoid duplicates
|
25
|
+
class_attribute :_generated_scopes, default: Set.new
|
26
|
+
|
27
|
+
# Generate scopes lazily when first needed
|
28
|
+
# This prevents issues with abstract classes like ApplicationRecord
|
29
|
+
end
|
30
|
+
|
31
|
+
class_methods do
|
32
|
+
# Generate all dynamic scopes for this model
|
33
|
+
def generate_dynamic_scopes!
|
34
|
+
return if _generated_scopes.include?(name)
|
35
|
+
|
36
|
+
# Skip abstract classes (like ApplicationRecord)
|
37
|
+
return if abstract_class?
|
38
|
+
|
39
|
+
# Skip if the model doesn't have a table (e.g., abstract classes)
|
40
|
+
return unless table_exists?
|
41
|
+
|
42
|
+
generator = PropelDynamicScopeGenerator.new(self)
|
43
|
+
generator.generate_scopes!
|
44
|
+
|
45
|
+
# Mark as generated to avoid duplicate work
|
46
|
+
_generated_scopes << name
|
47
|
+
rescue StandardError => e
|
48
|
+
Rails.logger.error "Error generating dynamic scopes for #{name}: #{e.message}"
|
49
|
+
Rails.logger.error e.backtrace.join("\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
# Ensure scopes are generated (lazy loading)
|
53
|
+
def ensure_scopes_generated!
|
54
|
+
generate_dynamic_scopes! unless _generated_scopes.include?(name)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Get all available filter parameters for this model
|
58
|
+
def available_filter_parameters
|
59
|
+
return [] unless respond_to?(:columns)
|
60
|
+
|
61
|
+
allowed = []
|
62
|
+
|
63
|
+
columns.each do |column|
|
64
|
+
name = column.name
|
65
|
+
type = column.type
|
66
|
+
sql_type = column.sql_type_metadata.sql_type
|
67
|
+
|
68
|
+
# Skip sensitive fields
|
69
|
+
next if sensitive_field?(name)
|
70
|
+
|
71
|
+
# Get operators for this column type
|
72
|
+
operators = PropelFilterOperators.operators_for(type, sql_type)
|
73
|
+
|
74
|
+
# Add parameter names for each operator
|
75
|
+
operators.each do |operator|
|
76
|
+
allowed << "#{name}_#{operator}".to_sym
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
allowed
|
81
|
+
end
|
82
|
+
|
83
|
+
# Check if a field name is considered sensitive
|
84
|
+
def sensitive_field?(field_name)
|
85
|
+
sensitive_fields = %w[
|
86
|
+
password password_hash password_digest
|
87
|
+
token secret_key api_key
|
88
|
+
encrypted_data encrypted_password
|
89
|
+
salt pepper
|
90
|
+
private_key public_key
|
91
|
+
ssn social_security_number
|
92
|
+
credit_card_number
|
93
|
+
bank_account_number
|
94
|
+
]
|
95
|
+
|
96
|
+
sensitive_fields.any? { |sensitive| field_name.downcase.include?(sensitive) }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get filterable columns for this model
|
100
|
+
def filterable_columns
|
101
|
+
return [] unless respond_to?(:columns)
|
102
|
+
|
103
|
+
columns.reject { |column| sensitive_field?(column.name) }
|
104
|
+
end
|
105
|
+
|
106
|
+
# Get operators available for a specific column
|
107
|
+
def operators_for_column(column_name)
|
108
|
+
column = columns.find { |c| c.name == column_name }
|
109
|
+
return [] unless column
|
110
|
+
|
111
|
+
PropelFilterOperators.operators_for(column.type, column.sql_type_metadata.sql_type)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Validate filter parameters
|
115
|
+
def validate_filter_parameters(params)
|
116
|
+
return {} unless params.is_a?(ActionController::Parameters)
|
117
|
+
|
118
|
+
allowed_filters = available_filter_parameters
|
119
|
+
params.slice(*allowed_filters)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def ensure_dynamic_scopes_generated
|
126
|
+
self.class.generate_dynamic_scopes!
|
127
|
+
end
|
128
|
+
end
|
@@ -4,6 +4,7 @@ class <%= api_controller_class_name %> < Api::BaseController
|
|
4
4
|
include FacetRenderer
|
5
5
|
include StrongParamsHelper
|
6
6
|
include PropelAuthenticationConcern
|
7
|
+
include PropelControllerFiltersConcern
|
7
8
|
|
8
9
|
before_action :authenticate_user
|
9
10
|
before_action :set_resource, only: [:show, :update, :destroy]
|