filterameter 1.0.2 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9666ad7ecd3f4cb352a26a7b8522fdff5a4ed94f9fa26199111659944982d8b1
4
- data.tar.gz: 93b5c66bfac1d89d8e77b6e967da5cce8531633029718f8c5484b8688bcef428
3
+ metadata.gz: 02daf1402e83a27c8b13ed7e548dfe269b7a1e6c7c5085f9aad2220509793253
4
+ data.tar.gz: 1ddcce714232b71be40272ac3e84f116e38af380928407936a082ade5395c743
5
5
  SHA512:
6
- metadata.gz: 4daba3e1643f9b6cb5a1c00f81b734bba3e8d10e55038a8bad8ae2e1b74f9172b74870836d8c27d6afb506bb19092c5631ccea70e0feccc44dbe15758d0ca583
7
- data.tar.gz: 879d2e0e5bc26f3bd4341bad4c5974f4ef17696418e05e6da8bb1cd5f6c7b8e16bfe835ae78ad08e954c487e3f8164b5b1bf1006a59b7e99cab5e775b0b79026
6
+ metadata.gz: b33c74905694f9a1682a4c7faefb6e11cbeff81a7c2bae32dd3a39f25393d50a7eb86fc766825f9296c2ce19440aed296eab17bebfd6f1eac77a41aa749dbeeb
7
+ data.tar.gz: 36f85f875a0c15258988cbbf97062caa75e1e47de1142484eaa96b8bae7f757d5a94e5fd5ac07b2137db7f1d8cb146adb76b3d6aa2849ad3507707e22b87b783
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/filterameter.svg)](https://badge.fury.io/rb/filterameter)
2
2
  [![RuboCop](https://github.com/RockSolt/filterameter/workflows/RuboCop/badge.svg)](https://github.com/RockSolt/filterameter/actions?query=workflow%3ARuboCop)
3
3
  [![RSpec](https://github.com/RockSolt/filterameter/workflows/RSpec/badge.svg)](https://github.com/RockSolt/filterameter/actions?query=workflow%3ARSpec)
4
- [![Maintainability](https://api.codeclimate.com/v1/badges/d9d87f9ce8020eb6e656/maintainability)](https://codeclimate.com/github/RockSolt/filterameter/maintainability)
5
4
 
6
5
  # Filterameter
7
6
  Filterameter provides declarative filters for Rails controllers to reduce boilerplate code and increase readability. How many times have you seen (or written) this controller action?
@@ -43,6 +42,7 @@ Simplify and speed development of Rails controllers by making filter parameters
43
42
  - [Partial](#partial)
44
43
  - [Range](#range)
45
44
  - [Sortable](#sortable)
45
+ - [Converters](#converters)
46
46
  - [Scope Filters](#scope-filters)
47
47
  - [Sorting](#sorting)
48
48
  - [Building the Query](#building-the-query)
@@ -83,6 +83,9 @@ Include module `Filterameter::DeclarativeFilters` in the controller to provide t
83
83
  filter :brand_name, association: :brand, name: :name
84
84
  filter :on_sale, association: :price, validates: [{ numericality: { greater_than: 0 } },
85
85
  { numericality: { less_than: 100 } }]
86
+ filter :amount do |value|
87
+ value.is_a?(String) ? value.delete(",") : value
88
+ end
86
89
  ```
87
90
 
88
91
  Filters without options can be declared all at once with `filters`:
@@ -147,7 +150,7 @@ There are two shortcuts: : the partial option can be declared with `true`, which
147
150
  ```ruby
148
151
  filter :description, partial: true
149
152
  filter :department_name, partial: :from_start
150
- filter :reason, partial: { match: :dynamic, case_sensitive: true }
153
+ filter :reason, partial: { match: :dynamic, case_sensitive: true }
151
154
  ```
152
155
 
153
156
  The `match` options defines where you are searching (which then controls where the wildcard(s) appear):
@@ -186,6 +189,17 @@ The following filters are not sortable:
186
189
  - scope filters (see [_Sorting with a Scope_](#sorting-with-a-scope))
187
190
  - filters with collection associations
188
191
 
192
+ #### Converters
193
+
194
+ If the filter value needs to be converted before being applied to the query, a converter block can be provided. The block should take the parameter value as an argument and return the converted value.
195
+
196
+ For example, if the amount filter should remove commas from the value before applying it to the query, the declaration would look like this:
197
+
198
+ ```ruby
199
+ filter :amount do |value|
200
+ value.is_a?(String) ? value.delete(",") : value
201
+ end
202
+ ```
189
203
 
190
204
  ### Scope Filters
191
205
 
@@ -267,7 +281,7 @@ There are two ways to apply the filters and build the query, depending on how mu
267
281
 
268
282
  Add before action callback `build_filtered_query` for controller actions that should build the query. This can be done either in the `ApplicationController` or on a case-by-case basis.
269
283
 
270
- When using the callback, the variable name is the pluralized model name. For example, the Photo model will use the variable `@photos` to store the query. The variable name can be explicitly specified with with `filter_query_var_name`. For example, if the query is stored as `@data`, use the following:
284
+ When using the callback, the variable name is the pluralized model name. For example, the Photo model will use the variable `@photos` to store the query. The variable name can be explicitly specified with `filter_query_var_name`. For example, if the query is stored as `@data`, use the following:
271
285
 
272
286
  ```ruby
273
287
  filter_query_var_name :data
@@ -338,7 +352,7 @@ Note that the starting query provides the model, so the model is not looked up a
338
352
 
339
353
  ### Specifying the Model
340
354
 
341
- Rails conventions are used to determine the controller's model. For example, the PhotosController builds a query against the Photo model. If a controller is namespaced, the model will first be looked up without the namespace, then with the namespace.
355
+ Rails conventions are used to determine the controller's model. For example, the PhotosController builds a query against the Photo model. If a controller is namespaced, the model will first be looked up without the namespace, then with the namespace.
342
356
 
343
357
  **If the conventions do not provide the correct model**, the model can be named explicitly with the following:
344
358
 
@@ -356,7 +370,7 @@ There are three configuration options:
356
370
  - action_on_validation_failure
357
371
  - filter_key
358
372
 
359
- The configuration options can be set in an initializer, an environment file, or in `application.rb`.
373
+ The configuration options can be set in an initializer, an environment file, or in `application.rb`.
360
374
 
361
375
  The options can be set directly...
362
376
 
@@ -367,7 +381,7 @@ The options can be set directly...
367
381
  ```ruby
368
382
  Filterameter.configure do |config|
369
383
  config.action_on_undeclared_parameters = :log
370
- config.action_on_validation_failuer = :log
384
+ config.action_on_validation_failure = :log
371
385
  config.filter_key = :f
372
386
  end
373
387
  ```
@@ -433,7 +447,7 @@ The sort is also nested underneath the filter key:
433
447
 
434
448
  Use an array to pass multiple sorts. The order of the parameters is the order the sorts will be applied. For example, the following sorts first by size then by color:
435
449
 
436
- `/widgets?filter[sort]=size&filter[sort]=color`
450
+ `/widgets?filter[sort][]=size&filter[sort][]=color`
437
451
 
438
452
  Sorts are ascending by default, but can use a prefix can be added to control the sort:
439
453
 
@@ -446,7 +460,7 @@ For example, the following sorts by size descending:
446
460
 
447
461
  ## Contribute
448
462
 
449
- Feedback, feature requests, and proposed changes are welcomed. Please use the [issue tracker](https://github.com/RockSolt/filterameter/issues)
463
+ Feedback, feature requests, and proposed changes are welcomed. Please use the [issue tracker](https://github.com/RockSolt/filterameter/issues)
450
464
  for feedback and feature requests. To propose a change directly, please fork the repo and open a pull request. Keep an eye on the actions to make
451
465
  sure the tests and Rubocop are passing. [Code Climate](https://codeclimate.com/github/RockSolt/filterameter) is also used manually to assess the codeline.
452
466
 
@@ -460,11 +474,10 @@ Gold stars will be awarded if you are able to [replicate the issue with a test](
460
474
 
461
475
  ### Running Tests
462
476
 
463
- Tests are written in RSpec and the dummy app uses a docker database. The script `bin/start_db.sh` starts and prepares the test
464
- database. It is a one-time step before running the tests.
477
+ Tests are written in RSpec.
465
478
 
466
479
  ```bash
467
- bin/start_db.rb
480
+ bin/prepare_db.sh
468
481
  bundle exec rspec
469
482
  ```
470
483
 
@@ -61,8 +61,8 @@ module Filterameter
61
61
  # filter :department_name, partial: :from_start
62
62
  # filter :reason, partial: { match: :dynamic, case_sensitive: true }
63
63
  # filter :price, range: true
64
- def filter(name, options = {})
65
- filter_coordinator.add_filter(name, options)
64
+ def filter(name, options = {}, &converter)
65
+ filter_coordinator.add_filter(name, options.merge(converter:))
66
66
  end
67
67
 
68
68
  # Declares a list of filters without options. Filters that require options must be declared with `filter`.
@@ -93,7 +93,7 @@ module Filterameter
93
93
 
94
94
  # Declares a list of sorts without options. Sorts that require options must be declared with `sort`.
95
95
  def sorts(*parameter_names)
96
- parameter_names.each { |parameter_name| filter(parameter_name) }
96
+ parameter_names.each { |parameter_name| sort(parameter_name) }
97
97
  end
98
98
 
99
99
  # Declares a default sort order for the query. Specify a list of sort names and directions as pairs.
@@ -16,7 +16,7 @@ module Filterameter
16
16
  class FilterDeclaration
17
17
  VALID_RANGE_OPTIONS = [true, :min_only, :max_only].freeze
18
18
 
19
- attr_reader :name, :parameter_name, :association, :validations
19
+ attr_reader :name, :parameter_name, :association, :validations, :converter
20
20
 
21
21
  def initialize(parameter_name, options, range_type: nil)
22
22
  @parameter_name = parameter_name.to_s
@@ -29,6 +29,7 @@ module Filterameter
29
29
  @raw_range = options[:range]
30
30
  @range_type = range_type
31
31
  @sortable = options.fetch(:sortable, true)
32
+ @converter = options[:converter]
32
33
  end
33
34
 
34
35
  def nested?
@@ -88,7 +89,7 @@ module Filterameter
88
89
  private
89
90
 
90
91
  def validate_options(options)
91
- options.assert_valid_keys(:name, :association, :validates, :partial, :range, :sortable)
92
+ options.assert_valid_keys(:name, :association, :validates, :partial, :range, :sortable, :converter)
92
93
  validate_range(options[:range]) if options.key?(:range)
93
94
  end
94
95
 
@@ -15,7 +15,7 @@ module Filterameter
15
15
  if declaration.nested?
16
16
  build_nested_filter(declaration, context)
17
17
  else
18
- build_filter(@model_class, declaration, context.scope?)
18
+ build_filter_or_scope_filter(@model_class, declaration, context.scope?)
19
19
  end
20
20
  end
21
21
 
@@ -23,23 +23,29 @@ module Filterameter
23
23
 
24
24
  def build_nested_filter(declaration, context)
25
25
  model = context.model_from_association
26
- filter = build_filter(model, declaration, context.scope?)
26
+ filter = build_filter_or_scope_filter(model, declaration, context.scope?)
27
27
  nested_filter_class = context.any_collections? ? Filters::NestedCollectionFilter : Filters::NestedFilter
28
28
 
29
29
  nested_filter_class.new(declaration.association, model, filter)
30
30
  end
31
31
 
32
- def build_filter(model, declaration, declaration_is_a_scope) # rubocop:disable Metrics/MethodLength
32
+ def build_filter_or_scope_filter(model, declaration, declaration_is_a_scope)
33
33
  if declaration_is_a_scope
34
34
  build_scope_filter(model, declaration)
35
- elsif declaration.partial_search?
36
- Filterameter::Filters::MatchesFilter.new(declaration.name, declaration.partial_options)
35
+ else
36
+ build_filter(model, declaration)
37
+ end
38
+ end
39
+
40
+ def build_filter(model, declaration)
41
+ if declaration.partial_search?
42
+ Filterameter::Filters::MatchesFilter.new(declaration.name, declaration.partial_options, &declaration.converter)
37
43
  elsif declaration.minimum_range?
38
- Filterameter::Filters::MinimumFilter.new(model, declaration.name)
44
+ Filterameter::Filters::MinimumFilter.new(model, declaration.name, &declaration.converter)
39
45
  elsif declaration.maximum_range?
40
- Filterameter::Filters::MaximumFilter.new(model, declaration.name)
46
+ Filterameter::Filters::MaximumFilter.new(model, declaration.name, &declaration.converter)
41
47
  else
42
- Filterameter::Filters::AttributeFilter.new(declaration.name)
48
+ Filterameter::Filters::AttributeFilter.new(declaration.name, &declaration.converter)
43
49
  end
44
50
  end
45
51
 
@@ -48,9 +54,9 @@ module Filterameter
48
54
  def build_scope_filter(model, declaration)
49
55
  number_of_arguments = model.method(declaration.name).arity
50
56
  if number_of_arguments < 1
51
- Filterameter::Filters::ConditionalScopeFilter.new(declaration.name)
57
+ Filterameter::Filters::ConditionalScopeFilter.new(declaration.name, &declaration.converter)
52
58
  elsif number_of_arguments == 1
53
- Filterameter::Filters::ScopeFilter.new(declaration.name)
59
+ Filterameter::Filters::ScopeFilter.new(declaration.name, &declaration.converter)
54
60
  else
55
61
  raise Filterameter::DeclarationErrors::FilterScopeArgumentError.new(model.name, declaration.name)
56
62
  end
@@ -10,9 +10,10 @@ module Filterameter
10
10
  include Filterameter::Errors
11
11
  include Filterameter::Filters::AttributeValidator
12
12
 
13
- def initialize(model, attribute_name)
13
+ def initialize(model, attribute_name, &converter)
14
14
  @attribute_name = attribute_name
15
15
  @arel_attribute = model.arel_table[attribute_name]
16
+ @converter = converter
16
17
  end
17
18
  end
18
19
  end
@@ -9,11 +9,13 @@ module Filterameter
9
9
  include Filterameter::Errors
10
10
  include AttributeValidator
11
11
 
12
- def initialize(attribute_name)
12
+ def initialize(attribute_name, &converter)
13
13
  @attribute_name = attribute_name
14
+ @converter = converter
14
15
  end
15
16
 
16
17
  def apply(query, value)
18
+ value = @converter.call(value) if @converter
17
19
  query.where(@attribute_name => value)
18
20
  end
19
21
  end
@@ -8,11 +8,13 @@ module Filterameter
8
8
  class ConditionalScopeFilter
9
9
  include Filterameter::Errors
10
10
 
11
- def initialize(scope_name)
11
+ def initialize(scope_name, &converter)
12
12
  @scope_name = scope_name
13
+ @converter = converter
13
14
  end
14
15
 
15
16
  def apply(query, value)
17
+ value = @converter.call(value) if @converter
16
18
  return query unless ActiveModel::Type::Boolean.new.cast(value)
17
19
 
18
20
  query.public_send(@scope_name)
@@ -9,14 +9,16 @@ module Filterameter
9
9
  include Filterameter::Errors
10
10
  include Filterameter::Filters::AttributeValidator
11
11
 
12
- def initialize(attribute_name, options)
12
+ def initialize(attribute_name, options, &converter)
13
13
  @attribute_name = attribute_name
14
14
  @prefix = options.match_anywhere? ? '%' : nil
15
15
  @suffix = options.match_anywhere? || options.match_from_start? ? '%' : nil
16
16
  @case_sensitive = options.case_sensitive?
17
+ @converter = converter
17
18
  end
18
19
 
19
20
  def apply(query, value)
21
+ value = @converter.call(value) if @converter
20
22
  arel = query.arel_table[@attribute_name].matches("#{@prefix}#{value}#{@suffix}", false, @case_sensitive)
21
23
  query.where(arel)
22
24
  end
@@ -7,6 +7,7 @@ module Filterameter
7
7
  # Class MaximumFilter adds criteria for all values greater than or equal to a maximum.
8
8
  class MaximumFilter < ArelFilter
9
9
  def apply(query, value)
10
+ value = @converter.call(value) if @converter
10
11
  query.where(@arel_attribute.lteq(value))
11
12
  end
12
13
  end
@@ -7,6 +7,7 @@ module Filterameter
7
7
  # Class MinimumFilter adds criteria for all values greater than or equal to a minimum.
8
8
  class MinimumFilter < ArelFilter
9
9
  def apply(query, value)
10
+ value = @converter.call(value) if @converter
10
11
  query.where(@arel_attribute.gteq(value))
11
12
  end
12
13
  end
@@ -8,11 +8,13 @@ module Filterameter
8
8
  class ScopeFilter
9
9
  include Filterameter::Errors
10
10
 
11
- def initialize(scope_name)
11
+ def initialize(scope_name, &converter)
12
12
  @scope_name = scope_name
13
+ @converter = converter
13
14
  end
14
15
 
15
16
  def apply(query, value)
17
+ value = @converter.call(value) if @converter
16
18
  query.public_send(@scope_name, value)
17
19
  end
18
20
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Filterameter
4
- VERSION = '1.0.2'
4
+ VERSION = '1.1.0'
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filterameter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Todd Kummer
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-15 00:00:00.000000000 Z
10
+ date: 2026-06-19 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -37,62 +37,6 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: 2.5.0
40
- - !ruby/object:Gem::Dependency
41
- name: guard
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '2.16'
47
- type: :development
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '2.16'
54
- - !ruby/object:Gem::Dependency
55
- name: guard-rspec
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '4.7'
61
- type: :development
62
- prerelease: false
63
- version_requirements: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '4.7'
68
- - !ruby/object:Gem::Dependency
69
- name: guard-rubocop
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: 1.5.0
75
- type: :development
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: 1.5.0
82
- - !ruby/object:Gem::Dependency
83
- name: pg
84
- requirement: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: 1.5.4
89
- type: :development
90
- prerelease: false
91
- version_requirements: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - "~>"
94
- - !ruby/object:Gem::Version
95
- version: 1.5.4
96
40
  - !ruby/object:Gem::Dependency
97
41
  name: rspec-rails
98
42
  requirement: !ruby/object:Gem::Requirement
@@ -127,14 +71,14 @@ dependencies:
127
71
  requirements:
128
72
  - - "~>"
129
73
  - !ruby/object:Gem::Version
130
- version: 0.5.2
74
+ version: '0.6'
131
75
  type: :development
132
76
  prerelease: false
133
77
  version_requirements: !ruby/object:Gem::Requirement
134
78
  requirements:
135
79
  - - "~>"
136
80
  - !ruby/object:Gem::Version
137
- version: 0.5.2
81
+ version: '0.6'
138
82
  - !ruby/object:Gem::Dependency
139
83
  name: rubocop-rails
140
84
  requirement: !ruby/object:Gem::Requirement
@@ -155,28 +99,28 @@ dependencies:
155
99
  requirements:
156
100
  - - "~>"
157
101
  - !ruby/object:Gem::Version
158
- version: 3.2.0
102
+ version: '3.9'
159
103
  type: :development
160
104
  prerelease: false
161
105
  version_requirements: !ruby/object:Gem::Requirement
162
106
  requirements:
163
107
  - - "~>"
164
108
  - !ruby/object:Gem::Version
165
- version: 3.2.0
109
+ version: '3.9'
166
110
  - !ruby/object:Gem::Dependency
167
111
  name: rubocop-rspec_rails
168
112
  requirement: !ruby/object:Gem::Requirement
169
113
  requirements:
170
114
  - - "~>"
171
115
  - !ruby/object:Gem::Version
172
- version: 2.30.0
116
+ version: '2.32'
173
117
  type: :development
174
118
  prerelease: false
175
119
  version_requirements: !ruby/object:Gem::Requirement
176
120
  requirements:
177
121
  - - "~>"
178
122
  - !ruby/object:Gem::Version
179
- version: 2.30.0
123
+ version: '2.32'
180
124
  - !ruby/object:Gem::Dependency
181
125
  name: simplecov
182
126
  requirement: !ruby/object:Gem::Requirement
@@ -252,7 +196,8 @@ files:
252
196
  homepage: https://github.com/RockSolt/filterameter
253
197
  licenses:
254
198
  - MIT
255
- metadata: {}
199
+ metadata:
200
+ rubygems_mfa_required: 'true'
256
201
  rdoc_options: []
257
202
  require_paths:
258
203
  - lib
@@ -267,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
267
212
  - !ruby/object:Gem::Version
268
213
  version: '0'
269
214
  requirements: []
270
- rubygems_version: 3.6.2
215
+ rubygems_version: 3.6.9
271
216
  specification_version: 4
272
217
  summary: Declarative Filter Parameters
273
218
  test_files: []