filterameter 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +21 -0
- data/lib/filterameter/controller_filters.rb +40 -2
- data/lib/filterameter/declarative_filters.rb +33 -0
- data/lib/filterameter/filter_declaration.rb +27 -1
- data/lib/filterameter/filter_factory.rb +8 -1
- data/lib/filterameter/filters/arel_filter.rb +14 -0
- data/lib/filterameter/filters/maximum_filter.rb +14 -0
- data/lib/filterameter/filters/minimum_filter.rb +14 -0
- data/lib/filterameter/version.rb +1 -1
- metadata +8 -6
- data/lib/tasks/filterameter_tasks.rake +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0baada12bc6ad382e146635629a89c47f935e92f1e4c72597889440305c46ebf
|
4
|
+
data.tar.gz: 427ecf17bd6458fbe76af104ab5b1eb32a4edb2adc3c0b73ac4fb329f1eba0f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24ce1e8ca752befea919c57f5e79af75d80f77569d0171a052b1b1a14c65af5d0e6ad234127767e3f42bddb4d058feafdee58130cf38102cd4b10eef2bdc3ca9
|
7
|
+
data.tar.gz: 5e0ad2d3348d84df2d7198bc38611f0a3bb8ea52ce4882c61ca0289f48239afc11ec49f4b293fc79810cda25a92f8e236ad00b73bb3d4769e4d4912b99939651
|
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/filterameter.svg)](https://badge.fury.io/rb/filterameter)
|
2
|
+
[![RuboCop](https://github.com/RockSolt/filterameter/workflows/RuboCop/badge.svg)](https://github.com/RockSolt/filterameter/actions?query=workflow%3ARuboCop)
|
3
|
+
|
1
4
|
# Filterameter
|
2
5
|
Declarative Filter Parameters for Rails Controllers.
|
3
6
|
|
@@ -57,6 +60,24 @@ The `match` options defines where you are searching (which then controls where t
|
|
57
60
|
- from_start: adds a wildcard at the end, for example 'blue%'
|
58
61
|
- dynamic: adds no wildcards; this enables the client to fully control the search string
|
59
62
|
|
63
|
+
#### range
|
64
|
+
Specify the range option to enable searches by ranges, minimum values, or maximum values. (All of these are inclusive. A search for a minimum value of $10.00 would include all items priced at $10.00.)
|
65
|
+
|
66
|
+
Here are the available options:
|
67
|
+
- true: enable ranges, minimum values, and/or maximum values
|
68
|
+
- min_only: enables minimum values
|
69
|
+
- max_only: enables maximum values
|
70
|
+
|
71
|
+
Using the range option means that _in addition to the attribute filter_ minimum and maximum query parameters may also be specified. The parameter names are the attribute name plus the suffix <tt>_min</tt> or <tt>_max</tt>.
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
filter :price, range: true
|
75
|
+
filter :approved_at, range: :min_only
|
76
|
+
filter :sale_price, range: :max_only
|
77
|
+
```
|
78
|
+
|
79
|
+
In the first example, query parameters could include <tt>price</tt>, <tt>price_min</tt>, and <tt>price_max</tt>.
|
80
|
+
|
60
81
|
### Configuring Controllers
|
61
82
|
|
62
83
|
Rails conventions are used to determine the controller's model as well as the name of the instance variable to apply the filters to. For example, the PhotosController will use the variable `@photos` to store a query against the Photo model. If the conventions do not provide the correct info, they can be overridden with the following two methods:
|
@@ -25,6 +25,7 @@ module Filterameter
|
|
25
25
|
@controller_name = controller_name
|
26
26
|
@controller_path = controller_path
|
27
27
|
@declarations = {}
|
28
|
+
@ranges = {}
|
28
29
|
@filters = Hash.new { |hash, key| hash[key] = filter_factory.build(@declarations[key]) }
|
29
30
|
end
|
30
31
|
|
@@ -33,7 +34,10 @@ module Filterameter
|
|
33
34
|
end
|
34
35
|
|
35
36
|
def add_filter(parameter_name, options)
|
36
|
-
@declarations[parameter_name.to_s] =
|
37
|
+
@declarations[parameter_name.to_s] =
|
38
|
+
Filterameter::FilterDeclaration.new(parameter_name, options).tap do |fd|
|
39
|
+
add_declarations_for_range(fd, options, parameter_name) if fd.range_enabled?
|
40
|
+
end
|
37
41
|
end
|
38
42
|
|
39
43
|
def query_variable_name
|
@@ -41,7 +45,9 @@ module Filterameter
|
|
41
45
|
end
|
42
46
|
|
43
47
|
def build_query(filter_params, starting_query)
|
44
|
-
valid_filters(filter_params)
|
48
|
+
valid_filters(filter_params)
|
49
|
+
.tap { |parameters| convert_min_and_max_to_range(parameters) }
|
50
|
+
.reduce(starting_query || model_class.all) do |query, (name, value)|
|
45
51
|
@filters[name].apply(query, value)
|
46
52
|
end
|
47
53
|
end
|
@@ -66,6 +72,16 @@ module Filterameter
|
|
66
72
|
)
|
67
73
|
end
|
68
74
|
|
75
|
+
# if both min and max are present in the query parameters, replace with range
|
76
|
+
def convert_min_and_max_to_range(parameters)
|
77
|
+
@ranges.each do |attribute_name, min_max_names|
|
78
|
+
next unless min_max_names.values.all? { |min_max_name| parameters[min_max_name].present? }
|
79
|
+
|
80
|
+
parameters[attribute_name] = Range.new(parameters.delete(min_max_names[:min]),
|
81
|
+
parameters.delete(min_max_names[:max]))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
69
85
|
def remove_undeclared_filters(filter_params)
|
70
86
|
filter_params.slice(*declared_parameter_names).tap do |declared_parameters|
|
71
87
|
handle_undeclared_parameters(filter_params) if declared_parameters.size != filter_params.size
|
@@ -106,5 +122,27 @@ module Filterameter
|
|
106
122
|
def validator_class
|
107
123
|
@validator_class ||= Filterameter::ParametersBase.build_sub_class(@declarations.values)
|
108
124
|
end
|
125
|
+
|
126
|
+
# if range is enabled, then in addition to the attribute filter this also adds min and/or max filters
|
127
|
+
def add_declarations_for_range(attribute_declaration, options, parameter_name)
|
128
|
+
add_range_minimum(parameter_name, options) if attribute_declaration.range? || attribute_declaration.minimum?
|
129
|
+
add_range_maximum(parameter_name, options) if attribute_declaration.range? || attribute_declaration.maximum?
|
130
|
+
capture_range_declaration(parameter_name) if attribute_declaration.range?
|
131
|
+
end
|
132
|
+
|
133
|
+
def add_range_minimum(parameter_name, options)
|
134
|
+
@declarations["#{parameter_name}_min"] = Filterameter::FilterDeclaration.new(parameter_name,
|
135
|
+
options.merge(range: :min_only))
|
136
|
+
end
|
137
|
+
|
138
|
+
def add_range_maximum(parameter_name, options)
|
139
|
+
@declarations["#{parameter_name}_max"] = Filterameter::FilterDeclaration.new(parameter_name,
|
140
|
+
options.merge(range: :max_only))
|
141
|
+
end
|
142
|
+
|
143
|
+
# memoizing these makes it easier to spot and replace ranges in query parameters; see convert_min_and_max_to_range
|
144
|
+
def capture_range_declaration(name)
|
145
|
+
@ranges[name] = { min: "#{name}_min", max: "#{name}_max" }
|
146
|
+
end
|
109
147
|
end
|
110
148
|
end
|
@@ -19,6 +19,39 @@ module Filterameter
|
|
19
19
|
controller_filters.query_variable_name = query_variable_name
|
20
20
|
end
|
21
21
|
|
22
|
+
# Declares a filter that can be read from the parameters and applied to the ActiveRecord query. The <tt>name</tt>
|
23
|
+
# identifies the name of the parameter and is the default value to determine the criteria to be applied. The name
|
24
|
+
# can be either an attribute or a scope.
|
25
|
+
#
|
26
|
+
# === Options
|
27
|
+
#
|
28
|
+
# [:name]
|
29
|
+
# Specify the attribute or scope name if the parameter name is not the same. The default value
|
30
|
+
# is the parameter name, so if the two match this can be left out.
|
31
|
+
#
|
32
|
+
# [:association]
|
33
|
+
# Specify the name of the association if the attribute or scope is nested.
|
34
|
+
#
|
35
|
+
# [:validates]
|
36
|
+
# Specify a validation if the parameter value should be validated. This uses ActiveModel validations;
|
37
|
+
# please review those for types of validations and usage.
|
38
|
+
#
|
39
|
+
# [:partial]
|
40
|
+
# Specify the partial option if the filter should do a partial search (SQL's `LIKE`). The partial
|
41
|
+
# option accepts a hash to specify the search behavior. Here are the available options:
|
42
|
+
# - match: anywhere (default), from_start, dynamic
|
43
|
+
# - case_sensitive: true, false (default)
|
44
|
+
#
|
45
|
+
# There are two shortcuts: : the partial option can be declared with `true`, which just uses the
|
46
|
+
# defaults; or the partial option can be declared with the match option directly,
|
47
|
+
# such as `partial: :from_start`.
|
48
|
+
#
|
49
|
+
# [:range]
|
50
|
+
# Specify a range option if the filter also allows ranges to be searched. The range option accepts
|
51
|
+
# the following options:
|
52
|
+
# - true: enables two additional parameters with attribute name plus suffixes <tt>_min</tt> and <tt>_max</tt>
|
53
|
+
# - :min_only: enables additional parameter with attribute name plus suffix <tt>_min</tt>
|
54
|
+
# - :max_only: enables additional parameter with attribute name plus suffix <tt>_max</tt>
|
22
55
|
def filter(name, options = {})
|
23
56
|
controller_filters.add_filter(name, options)
|
24
57
|
end
|
@@ -8,6 +8,8 @@ module Filterameter
|
|
8
8
|
#
|
9
9
|
# Class FilterDeclaration captures the filter declaration within the controller.
|
10
10
|
class FilterDeclaration
|
11
|
+
VALID_RANGE_OPTIONS = [true, :min_only, :max_only].freeze
|
12
|
+
|
11
13
|
attr_reader :name, :parameter_name, :association, :validations
|
12
14
|
|
13
15
|
def initialize(parameter_name, options)
|
@@ -19,6 +21,7 @@ module Filterameter
|
|
19
21
|
@filter_on_empty = options.fetch(:filter_on_empty, false)
|
20
22
|
@validations = Array.wrap(options[:validates])
|
21
23
|
@raw_partial_options = options.fetch(:partial, false)
|
24
|
+
@raw_range = options[:range]
|
22
25
|
end
|
23
26
|
|
24
27
|
def nested?
|
@@ -41,10 +44,33 @@ module Filterameter
|
|
41
44
|
@partial_options ||= @raw_partial_options ? Options::PartialOptions.new(@raw_partial_options) : nil
|
42
45
|
end
|
43
46
|
|
47
|
+
def range_enabled?
|
48
|
+
@raw_range.present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def range?
|
52
|
+
@raw_range == true
|
53
|
+
end
|
54
|
+
|
55
|
+
def minimum?
|
56
|
+
@raw_range == :min_only
|
57
|
+
end
|
58
|
+
|
59
|
+
def maximum?
|
60
|
+
@raw_range == :max_only
|
61
|
+
end
|
62
|
+
|
44
63
|
private
|
45
64
|
|
46
65
|
def validate_options(options)
|
47
|
-
options.assert_valid_keys(:name, :association, :filter_on_empty, :validates, :partial)
|
66
|
+
options.assert_valid_keys(:name, :association, :filter_on_empty, :validates, :partial, :range)
|
67
|
+
validate_range(options[:range]) if options.key?(:range)
|
68
|
+
end
|
69
|
+
|
70
|
+
def validate_range(range)
|
71
|
+
return if VALID_RANGE_OPTIONS.include?(range)
|
72
|
+
|
73
|
+
raise ArgumentError, "Invalid range option: #{range}"
|
48
74
|
end
|
49
75
|
end
|
50
76
|
end
|
@@ -1,8 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'filterameter/filters/arel_filter'
|
3
4
|
require 'filterameter/filters/attribute_filter'
|
4
5
|
require 'filterameter/filters/conditional_scope_filter'
|
5
6
|
require 'filterameter/filters/matches_filter'
|
7
|
+
require 'filterameter/filters/maximum_filter'
|
8
|
+
require 'filterameter/filters/minimum_filter'
|
6
9
|
require 'filterameter/filters/nested_filter'
|
7
10
|
require 'filterameter/filters/scope_filter'
|
8
11
|
|
@@ -24,12 +27,16 @@ module Filterameter
|
|
24
27
|
|
25
28
|
private
|
26
29
|
|
27
|
-
def build_filter(model, declaration)
|
30
|
+
def build_filter(model, declaration) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
28
31
|
# checking dangerous_class_method? excludes any names that cannot be scope names, such as "name"
|
29
32
|
if model.respond_to?(declaration.name) && !model.dangerous_class_method?(declaration.name)
|
30
33
|
Filterameter::Filters::ScopeFilter.new(declaration.name)
|
31
34
|
elsif declaration.partial_search?
|
32
35
|
Filterameter::Filters::MatchesFilter.new(declaration.name, declaration.partial_options)
|
36
|
+
elsif declaration.minimum?
|
37
|
+
Filterameter::Filters::MinimumFilter.new(model, declaration.name)
|
38
|
+
elsif declaration.maximum?
|
39
|
+
Filterameter::Filters::MaximumFilter.new(model, declaration.name)
|
33
40
|
else
|
34
41
|
Filterameter::Filters::AttributeFilter.new(declaration.name)
|
35
42
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module Filters
|
5
|
+
# = Arel Filter
|
6
|
+
#
|
7
|
+
# Class ArelFilter is a base class for arel queries. It does not implement <tt>apply</tt>.
|
8
|
+
class ArelFilter
|
9
|
+
def initialize(model, attribute_name)
|
10
|
+
@arel_attribute = model.arel_table[attribute_name]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module Filters
|
5
|
+
# = Maximum Filter
|
6
|
+
#
|
7
|
+
# Class MaximumFilter adds criteria for all values greater than or equal to a maximum.
|
8
|
+
class MaximumFilter < ArelFilter
|
9
|
+
def apply(query, value)
|
10
|
+
query.where(@arel_attribute.lteq(value))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module Filters
|
5
|
+
# = Minimum Filter
|
6
|
+
#
|
7
|
+
# Class MinimumFilter adds criteria for all values greater than or equal to a minimum.
|
8
|
+
class MinimumFilter < ArelFilter
|
9
|
+
def apply(query, value)
|
10
|
+
query.where(@arel_attribute.gteq(value))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/filterameter/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: filterameter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Todd Kummer
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -142,21 +142,23 @@ files:
|
|
142
142
|
- lib/filterameter/exceptions/validation_error.rb
|
143
143
|
- lib/filterameter/filter_declaration.rb
|
144
144
|
- lib/filterameter/filter_factory.rb
|
145
|
+
- lib/filterameter/filters/arel_filter.rb
|
145
146
|
- lib/filterameter/filters/attribute_filter.rb
|
146
147
|
- lib/filterameter/filters/conditional_scope_filter.rb
|
147
148
|
- lib/filterameter/filters/matches_filter.rb
|
149
|
+
- lib/filterameter/filters/maximum_filter.rb
|
150
|
+
- lib/filterameter/filters/minimum_filter.rb
|
148
151
|
- lib/filterameter/filters/nested_filter.rb
|
149
152
|
- lib/filterameter/filters/scope_filter.rb
|
150
153
|
- lib/filterameter/log_subscriber.rb
|
151
154
|
- lib/filterameter/options/partial_options.rb
|
152
155
|
- lib/filterameter/parameters_base.rb
|
153
156
|
- lib/filterameter/version.rb
|
154
|
-
- lib/tasks/filterameter_tasks.rake
|
155
157
|
homepage: https://github.com/RockSolt/filterameter
|
156
158
|
licenses:
|
157
159
|
- MIT
|
158
160
|
metadata: {}
|
159
|
-
post_install_message:
|
161
|
+
post_install_message:
|
160
162
|
rdoc_options: []
|
161
163
|
require_paths:
|
162
164
|
- lib
|
@@ -172,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
174
|
version: '0'
|
173
175
|
requirements: []
|
174
176
|
rubygems_version: 3.0.8
|
175
|
-
signing_key:
|
177
|
+
signing_key:
|
176
178
|
specification_version: 4
|
177
179
|
summary: Declarative Filter Parameters for Rails Controllers
|
178
180
|
test_files: []
|