filterameter 0.9.0 → 0.10.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 +4 -4
- data/README.md +14 -0
- data/lib/filterameter/declaration_errors/cannot_be_inline_scope_error.rb +24 -0
- data/lib/filterameter/declaration_errors/filter_scope_argument_error.rb +16 -0
- data/lib/filterameter/declaration_errors/no_such_attribute_error.rb +16 -0
- data/lib/filterameter/declaration_errors/not_a_scope_error.rb +17 -0
- data/lib/filterameter/declaration_errors/sort_scope_requires_one_argument_error.rb +16 -0
- data/lib/filterameter/declaration_errors/unexpected_error.rb +22 -0
- data/lib/filterameter/declaration_errors.rb +11 -0
- data/lib/filterameter/declarations_validator.rb +99 -0
- data/lib/filterameter/declarative_filters.rb +2 -0
- data/lib/filterameter/errors.rb +24 -0
- data/lib/filterameter/filter_coordinator.rb +4 -0
- data/lib/filterameter/filter_factory.rb +5 -2
- data/lib/filterameter/filters/arel_filter.rb +4 -0
- data/lib/filterameter/filters/attribute_filter.rb +3 -0
- data/lib/filterameter/filters/attribute_validator.rb +18 -0
- data/lib/filterameter/filters/conditional_scope_filter.rb +16 -0
- data/lib/filterameter/filters/matches_filter.rb +3 -0
- data/lib/filterameter/filters/nested_filter.rb +15 -0
- data/lib/filterameter/filters/scope_filter.rb +14 -0
- data/lib/filterameter/registries/filter_registry.rb +0 -4
- data/lib/filterameter/registries/registry.rb +3 -1
- data/lib/filterameter/registries/sort_registry.rb +4 -0
- data/lib/filterameter/registries/sub_registry.rb +4 -0
- data/lib/filterameter/sort_factory.rb +1 -1
- data/lib/filterameter/sorts/attribute_sort.rb +3 -0
- data/lib/filterameter/sorts/scope_sort.rb +25 -0
- data/lib/filterameter/version.rb +1 -1
- metadata +42 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 929699bdcf63b640acc335ab2b6690e4d57822541e354f93aa9faf71ef0faf3c
|
4
|
+
data.tar.gz: 205958bfb5a55418e740382146934b8aa7f3ef122686f903621b6de1f14a2788
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd04092048f8c2d00d4df6f77136e4714e21385b08ca21f0fe1608ba0ff7b12d97d1ca27fc26024f7d52180cb1013546c520131cfe35e90fa688d5c36a185071
|
7
|
+
data.tar.gz: 1a065d5dcd20ac179e80b1d507ed52de4be84704651cbba2b7fa68c65c0793db06577eec83c68949e880173bfa8ffae930eca2d0ceec68952d26901be7649c93
|
data/README.md
CHANGED
@@ -340,8 +340,22 @@ If the filter parameters are NOT nested, set this to false. Doing so will restri
|
|
340
340
|
those that have been declared, meaning undeclared parameters are ignored (and the action_on_undeclared_parameters
|
341
341
|
configuration option does not come into play).
|
342
342
|
|
343
|
+
### Testing Declarations
|
343
344
|
|
345
|
+
The declarations can be tested for each controller, catching typos, incorrectly defined scopes, or any other issues. Method `declarations_validator` is added to each controller, and a single controller test can be added to validate all the declarations for that controller.
|
344
346
|
|
347
|
+
An RSpec test might look like this:
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
expect(WidgetsController.declarations_validator).to be_valid
|
351
|
+
```
|
352
|
+
|
353
|
+
In Minitest it might look like this:
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
validator = WidgetsController.declarations_validator
|
357
|
+
assert_predicate validator, :valid?, -> { validator.errors }
|
358
|
+
```
|
345
359
|
|
346
360
|
## Installation
|
347
361
|
Add this line to your application's Gemfile:
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module DeclarationErrors
|
5
|
+
# = CannotBeInlineScopeError
|
6
|
+
#
|
7
|
+
# Error CannotBeInlineScopeError occurs when an inline scope has been used to define a filter that takes a
|
8
|
+
# parameter. This is not valid for use as a Filterameter filter because an inline scope always has an arity of -1
|
9
|
+
# meaning the factory cannot tell if it has an argument or not. As such, all inline scopes are assumed to not have
|
10
|
+
# arguments and thus be conditional scopes.
|
11
|
+
#
|
12
|
+
# [The Rails guide](https://guides.rubyonrails.org/active_record_querying.html#passing-in-arguments) provides
|
13
|
+
# guidance suggesting scopes that take arguments be written as class methods. This takes that guidance a step
|
14
|
+
# further and makes it a requirement for a scope that will be used as a filter.
|
15
|
+
class CannotBeInlineScopeError < DeclarationError
|
16
|
+
def initialize(model_name, scope_name)
|
17
|
+
super(<<~ERROR.chomp)
|
18
|
+
#{model_name} scope '#{scope_name}' needs to be written as a class method, not as an inline scope. This is a
|
19
|
+
suggestion from the Rails guide but a requirement in order to use a scope that has an argument as a filter.
|
20
|
+
ERROR
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module DeclarationErrors
|
5
|
+
# = Filter Scope Argument Error
|
6
|
+
#
|
7
|
+
# Error FilterScopeArgumentError occurs when a scope used as a filter but does not have either zero or one
|
8
|
+
# arument. A conditional scope filter should take zero arguments; other scope filters should take one argument.
|
9
|
+
class FilterScopeArgumentError < DeclarationError
|
10
|
+
def initialize(model_name, scope_name)
|
11
|
+
super("#{model_name} scope '#{scope_name}' takes too many arguments. Scopes for filters can only have either " \
|
12
|
+
'zero (conditional scope) or one argument')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module DeclarationErrors
|
5
|
+
# = No Such Attribute Error
|
6
|
+
#
|
7
|
+
# Error NoSuchAttributeError occurs when a filter or sort references an attribute that does not exist on the model.
|
8
|
+
# The most likely case of this is a typo. Note that if the typo was supposed to reference a scope, this error is
|
9
|
+
# added as attributes are assumed when no matching scopes are found.
|
10
|
+
class NoSuchAttributeError < DeclarationError
|
11
|
+
def initialize(model_name, attribute_name)
|
12
|
+
super("Attribute '#{attribute_name}' does not exist on #{model_name}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module DeclarationErrors
|
5
|
+
# = Not A Scope Error
|
6
|
+
#
|
7
|
+
# Error NotAScopeError flags a class method that has been used as a filter but is not a scope. This could occur if
|
8
|
+
# there is a class method of the same name an attribute, in which case the class method is going to block the
|
9
|
+
# creation of an attribute filter. The work around (if the class method cannot be renamed) is to create a scope
|
10
|
+
# that provides a filter on the attribute.
|
11
|
+
class NotAScopeError < DeclarationError
|
12
|
+
def initialize(model_name, scope_name)
|
13
|
+
super("#{model_name} class method '#{scope_name}' is not a scope.")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module DeclarationErrors
|
5
|
+
# = Sort Scope Requires One Argument Error
|
6
|
+
#
|
7
|
+
# Error SortScopeRequiresOneArgumentError occurs when a sort has been declared for a scope that does not take
|
8
|
+
# exactly one argument. Sort scopes must take a single argument and will receive either :asc or :desc to indicate
|
9
|
+
# the direction.
|
10
|
+
class SortScopeRequiresOneArgumentError < DeclarationError
|
11
|
+
def initialize(model_name, scope_name)
|
12
|
+
super("#{model_name} scope '#{scope_name}' must take exactly one argument to sort by.")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module DeclarationErrors
|
5
|
+
# = Unexpected Error
|
6
|
+
#
|
7
|
+
# Error UnexpectedError occurs when the filter or scope factory raises an exception that the validator did not
|
8
|
+
# expect.
|
9
|
+
class UnexpectedError < DeclarationError
|
10
|
+
def initialize(error)
|
11
|
+
super(<<~ERROR)
|
12
|
+
The previous error was unexpected. It occurred during while building a filter or sort (see below). Please
|
13
|
+
report this to the library so that the error can be handled and provide clearer feedback about what is wrong
|
14
|
+
with the declaration.
|
15
|
+
|
16
|
+
#{error.message}
|
17
|
+
#{error.backtrace.join("\n\t")}
|
18
|
+
ERROR
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module DeclarationErrors
|
5
|
+
# = Declaration Error
|
6
|
+
#
|
7
|
+
# Error DeclarationError provides a base class for errors from filter or sort declarations.
|
8
|
+
class DeclarationError < StandardError
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
# = Declarations Validator
|
5
|
+
#
|
6
|
+
# Class DeclarationsValidtor fetches each filter and sort from the registry to validate the declaration. This class
|
7
|
+
# can be accessed from the controller as `declarations_validator` (via the FilterCoordinator) and be used in tests.
|
8
|
+
#
|
9
|
+
# Use the `valid?` method to test, then report errors with the `errors` attribute.
|
10
|
+
#
|
11
|
+
# A test in RSpec might look like this:
|
12
|
+
#
|
13
|
+
# expect(WidgetsController.declarations_validator).to be_valid
|
14
|
+
#
|
15
|
+
# In Minitest it might look like this:
|
16
|
+
#
|
17
|
+
# assert WidgetsController.declarations_validator.valid?, WidgetsController.declarations_validator.errors
|
18
|
+
class DeclarationsValidator
|
19
|
+
include Filterameter::Errors
|
20
|
+
|
21
|
+
def initialize(controller_name, model, registry)
|
22
|
+
@controller_name = controller_name
|
23
|
+
@model = model
|
24
|
+
@registry = registry
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"filter declarations on #{@controller_name.titleize}Controller"
|
29
|
+
end
|
30
|
+
|
31
|
+
def errors
|
32
|
+
@errors&.join("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def validate(_)
|
38
|
+
@errors.push(*validation_errors_for('filter', fetch_filters))
|
39
|
+
@errors.push(*validation_errors_for('sort', fetch_sorts))
|
40
|
+
end
|
41
|
+
|
42
|
+
def fetch_filters
|
43
|
+
@registry.filter_parameter_names.index_with { |name| fetch_filter(name) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def fetch_filter(name)
|
47
|
+
@registry.fetch_filter(name)
|
48
|
+
rescue StandardError => e
|
49
|
+
FactoryErrors.new(e)
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_sorts
|
53
|
+
(@registry.sort_parameter_names - @registry.filter_parameter_names).index_with { |name| fetch_sort(name) }
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch_sort(name)
|
57
|
+
@registry.fetch_sort(name)
|
58
|
+
rescue StandardError => e
|
59
|
+
FactoryErrors.new(e)
|
60
|
+
end
|
61
|
+
|
62
|
+
def validation_errors_for(type, items)
|
63
|
+
items.select { |_name, item| item.respond_to? :valid? }
|
64
|
+
.reject { |_name, item| item.valid?(@model) }
|
65
|
+
.map { |name, item| error_message(type, name, item.errors) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def error_message(type, name, errors)
|
69
|
+
"\nInvalid #{type} for '#{name}':\n #{errors.join("\n ")}"
|
70
|
+
end
|
71
|
+
|
72
|
+
# = Factory Errors
|
73
|
+
#
|
74
|
+
# Class FactoryErrors is swapped in if the fetch from a factory fails. It is always invalid and provides the reason.
|
75
|
+
class FactoryErrors
|
76
|
+
attr_reader :errors
|
77
|
+
|
78
|
+
def initialize(error)
|
79
|
+
@errors = [wrap_if_unexpected(error)]
|
80
|
+
end
|
81
|
+
|
82
|
+
def valid?(_)
|
83
|
+
false
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
@errors.join("\n")
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def wrap_if_unexpected(error)
|
93
|
+
return error if error.is_a?(Filterameter::DeclarationErrors::DeclarationError)
|
94
|
+
|
95
|
+
Filterameter::DeclarationErrors::UnexpectedError.new(error)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -10,6 +10,8 @@ module Filterameter
|
|
10
10
|
include Filterameter::Sortable
|
11
11
|
|
12
12
|
class_methods do
|
13
|
+
delegate :declarations_validator, to: :filter_coordinator
|
14
|
+
|
13
15
|
def filter_model(model_class, query_var_name = nil)
|
14
16
|
filter_coordinator.model_class = model_class
|
15
17
|
filter_query_var_name(query_var_name) if query_var_name.present?
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
# = Errors
|
5
|
+
#
|
6
|
+
# Module Errors provides `valid?` and `errors` to implementing classes. If the `valid?` method is not overridden,
|
7
|
+
# then it returns true.
|
8
|
+
#
|
9
|
+
# To provide validations rules, override `validate`. If any fail, populate the errors attribute with the
|
10
|
+
# reason for the failures.
|
11
|
+
module Errors
|
12
|
+
attr_reader :errors
|
13
|
+
|
14
|
+
def valid?(model = nil)
|
15
|
+
@errors = []
|
16
|
+
validate(model)
|
17
|
+
@errors.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def validate(_model); end
|
23
|
+
end
|
24
|
+
end
|
@@ -46,10 +46,13 @@ module Filterameter
|
|
46
46
|
# Inline scopes return an arity of -1 regardless of arguments, so those will always be assumed to be
|
47
47
|
# conditional scopes. To have a filter that passes a value to a scope, it must be a class method.
|
48
48
|
def build_scope_filter(model, declaration)
|
49
|
-
|
49
|
+
number_of_arguments = model.method(declaration.name).arity
|
50
|
+
if number_of_arguments < 1
|
51
|
+
Filterameter::Filters::ConditionalScopeFilter.new(declaration.name)
|
52
|
+
elsif number_of_arguments == 1
|
50
53
|
Filterameter::Filters::ScopeFilter.new(declaration.name)
|
51
54
|
else
|
52
|
-
Filterameter::
|
55
|
+
raise Filterameter::DeclarationErrors::FilterScopeArgumentError.new(model.name, declaration.name)
|
53
56
|
end
|
54
57
|
end
|
55
58
|
end
|
@@ -6,7 +6,11 @@ module Filterameter
|
|
6
6
|
#
|
7
7
|
# Class ArelFilter is a base class for arel queries. It does not implement <tt>apply</tt>.
|
8
8
|
class ArelFilter
|
9
|
+
include Filterameter::Errors
|
10
|
+
include Filterameter::Filters::AttributeValidator
|
11
|
+
|
9
12
|
def initialize(model, attribute_name)
|
13
|
+
@attribute_name = attribute_name
|
10
14
|
@arel_attribute = model.arel_table[attribute_name]
|
11
15
|
end
|
12
16
|
end
|
@@ -6,6 +6,9 @@ module Filterameter
|
|
6
6
|
#
|
7
7
|
# Class AttributeFilter leverages ActiveRecord's where query method to add criteria for an attribute.
|
8
8
|
class AttributeFilter
|
9
|
+
include Filterameter::Errors
|
10
|
+
include AttributeValidator
|
11
|
+
|
9
12
|
def initialize(attribute_name)
|
10
13
|
@attribute_name = attribute_name
|
11
14
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module Filters
|
5
|
+
# = Attribute Validator
|
6
|
+
#
|
7
|
+
# Module AttributeValidator validates that the attribute exists on the model.
|
8
|
+
module AttributeValidator
|
9
|
+
private
|
10
|
+
|
11
|
+
def validate(model)
|
12
|
+
return if model.attribute_method? @attribute_name
|
13
|
+
|
14
|
+
@errors << Filterameter::DeclarationErrors::NoSuchAttributeError.new(model, @attribute_name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -6,6 +6,8 @@ module Filterameter
|
|
6
6
|
#
|
7
7
|
# Class ConditionalScopeFilter applies the scope if the parameter is not false.
|
8
8
|
class ConditionalScopeFilter
|
9
|
+
include Filterameter::Errors
|
10
|
+
|
9
11
|
def initialize(scope_name)
|
10
12
|
@scope_name = scope_name
|
11
13
|
end
|
@@ -15,6 +17,20 @@ module Filterameter
|
|
15
17
|
|
16
18
|
query.public_send(@scope_name)
|
17
19
|
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def validate(model)
|
24
|
+
validate_is_a_scope(model)
|
25
|
+
rescue ArgumentError
|
26
|
+
@errors << Filterameter::DeclarationErrors::CannotBeInlineScopeError.new(model.name, @scope_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_is_a_scope(model)
|
30
|
+
return if model.public_send(@scope_name).is_a? ActiveRecord::Relation
|
31
|
+
|
32
|
+
@errors << Filterameter::DeclarationErrors::NotAScopeError.new(model.name, @scope_name)
|
33
|
+
end
|
18
34
|
end
|
19
35
|
end
|
20
36
|
end
|
@@ -6,6 +6,9 @@ module Filterameter
|
|
6
6
|
#
|
7
7
|
# Class MatchesFilter uses arel's `matches` to generate a LIKE query.
|
8
8
|
class MatchesFilter
|
9
|
+
include Filterameter::Errors
|
10
|
+
include Filterameter::Filters::AttributeValidator
|
11
|
+
|
9
12
|
def initialize(attribute_name, options)
|
10
13
|
@attribute_name = attribute_name
|
11
14
|
@prefix = options.match_anywhere? ? '%' : nil
|
@@ -6,6 +6,8 @@ module Filterameter
|
|
6
6
|
#
|
7
7
|
# Class NestedFilter joins the nested table(s) then merges the filter to the association's model.
|
8
8
|
class NestedFilter
|
9
|
+
include Filterameter::Errors
|
10
|
+
|
9
11
|
def initialize(association_names, association_model, attribute_filter)
|
10
12
|
@joins_values = Filterameter::Helpers::JoinsValuesBuilder.build(association_names)
|
11
13
|
@association_model = association_model
|
@@ -16,6 +18,19 @@ module Filterameter
|
|
16
18
|
query.joins(@joins_values)
|
17
19
|
.merge(@attribute_filter.apply(@association_model.all, value))
|
18
20
|
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def validate(model)
|
25
|
+
@errors.push(*@attribute_filter.errors) unless @attribute_filter.valid?(@association_model)
|
26
|
+
validate_associations(model)
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate_associations(model)
|
30
|
+
model.joins(@joins_values).to_sql
|
31
|
+
rescue ActiveRecord::ConfigurationError => e
|
32
|
+
@errors << e.message
|
33
|
+
end
|
19
34
|
end
|
20
35
|
end
|
21
36
|
end
|
@@ -6,6 +6,8 @@ module Filterameter
|
|
6
6
|
#
|
7
7
|
# Class ScopeFilter applies the named scope passing in the parameter value.
|
8
8
|
class ScopeFilter
|
9
|
+
include Filterameter::Errors
|
10
|
+
|
9
11
|
def initialize(scope_name)
|
10
12
|
@scope_name = scope_name
|
11
13
|
end
|
@@ -13,6 +15,18 @@ module Filterameter
|
|
13
15
|
def apply(query, value)
|
14
16
|
query.public_send(@scope_name, value)
|
15
17
|
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def validate(model)
|
22
|
+
validate_is_a_scope(model)
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_is_a_scope(model)
|
26
|
+
return if model.public_send(@scope_name, '42').is_a? ActiveRecord::Relation
|
27
|
+
|
28
|
+
@errors << Filterameter::DeclarationErrors::NotAScopeError.new(model.name, @scope_name)
|
29
|
+
end
|
16
30
|
end
|
17
31
|
end
|
18
32
|
end
|
@@ -6,7 +6,9 @@ module Filterameter
|
|
6
6
|
#
|
7
7
|
# Class Registry records declarations and allows resulting filters and sorts to be fetched from sub-registries.
|
8
8
|
class Registry
|
9
|
-
delegate :filter_declarations, :
|
9
|
+
delegate :filter_declarations, :ranges, to: :@filter_registry
|
10
|
+
delegate :parameter_names, prefix: :filter, to: :@filter_registry
|
11
|
+
delegate :parameter_names, prefix: :sort, to: :@sort_registry
|
10
12
|
|
11
13
|
def initialize(model_class)
|
12
14
|
@filter_registry = Filterameter::Registries::FilterRegistry.new(Filterameter::FilterFactory.new(model_class))
|
@@ -7,6 +7,10 @@ module Filterameter
|
|
7
7
|
# Class SortRegistry is a collection of the sorts. It captures the declarations when classes are loaded,
|
8
8
|
# then uses the injected SortFactory to build the sorts on demand as they are needed.
|
9
9
|
class SortRegistry < SubRegistry
|
10
|
+
def sort_parameter_names
|
11
|
+
@declarations.keys
|
12
|
+
end
|
13
|
+
|
10
14
|
private
|
11
15
|
|
12
16
|
def build_declaration(name, options)
|
@@ -6,6 +6,9 @@ module Filterameter
|
|
6
6
|
#
|
7
7
|
# Class AttributeSort leverages ActiveRecord's `order` query method to add sorting for an attribute.
|
8
8
|
class AttributeSort
|
9
|
+
include Filterameter::Errors
|
10
|
+
include Filterameter::Filters::AttributeValidator
|
11
|
+
|
9
12
|
def initialize(attribute_name)
|
10
13
|
@attribute_name = attribute_name
|
11
14
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Filterameter
|
4
|
+
module Sorts
|
5
|
+
# = Scope Sort
|
6
|
+
#
|
7
|
+
# Class ScopeSort applies the scope with the a param that is either :asc or :desc. A scope that does not take
|
8
|
+
# exactly one argument is not valid for sorting.
|
9
|
+
class ScopeSort < Filterameter::Filters::ScopeFilter
|
10
|
+
private
|
11
|
+
|
12
|
+
def validate(model)
|
13
|
+
validate_is_a_scope(model)
|
14
|
+
rescue ArgumentError
|
15
|
+
@errors << Filterameter::DeclarationErrors::SortScopeRequiresOneArgumentError.new(model.name, @scope_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate_is_a_scope(model)
|
19
|
+
return if model.public_send(@scope_name, :asc).is_a? ActiveRecord::Relation
|
20
|
+
|
21
|
+
@errors << Filterameter::DeclarationErrors::NotAScopeError.new(model.name, @scope_name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
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.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Todd Kummer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -150,6 +150,34 @@ dependencies:
|
|
150
150
|
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '2.25'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rubocop-rspec
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 3.0.3
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 3.0.3
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rubocop-rspec_rails
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 2.30.0
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 2.30.0
|
153
181
|
- !ruby/object:Gem::Dependency
|
154
182
|
name: simplecov
|
155
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -176,7 +204,16 @@ files:
|
|
176
204
|
- Rakefile
|
177
205
|
- lib/filterameter.rb
|
178
206
|
- lib/filterameter/configuration.rb
|
207
|
+
- lib/filterameter/declaration_errors.rb
|
208
|
+
- lib/filterameter/declaration_errors/cannot_be_inline_scope_error.rb
|
209
|
+
- lib/filterameter/declaration_errors/filter_scope_argument_error.rb
|
210
|
+
- lib/filterameter/declaration_errors/no_such_attribute_error.rb
|
211
|
+
- lib/filterameter/declaration_errors/not_a_scope_error.rb
|
212
|
+
- lib/filterameter/declaration_errors/sort_scope_requires_one_argument_error.rb
|
213
|
+
- lib/filterameter/declaration_errors/unexpected_error.rb
|
214
|
+
- lib/filterameter/declarations_validator.rb
|
179
215
|
- lib/filterameter/declarative_filters.rb
|
216
|
+
- lib/filterameter/errors.rb
|
180
217
|
- lib/filterameter/exceptions.rb
|
181
218
|
- lib/filterameter/exceptions/cannot_determine_model_error.rb
|
182
219
|
- lib/filterameter/exceptions/collection_association_sort_error.rb
|
@@ -189,6 +226,7 @@ files:
|
|
189
226
|
- lib/filterameter/filterable.rb
|
190
227
|
- lib/filterameter/filters/arel_filter.rb
|
191
228
|
- lib/filterameter/filters/attribute_filter.rb
|
229
|
+
- lib/filterameter/filters/attribute_validator.rb
|
192
230
|
- lib/filterameter/filters/conditional_scope_filter.rb
|
193
231
|
- lib/filterameter/filters/matches_filter.rb
|
194
232
|
- lib/filterameter/filters/maximum_filter.rb
|
@@ -211,6 +249,7 @@ files:
|
|
211
249
|
- lib/filterameter/sort_factory.rb
|
212
250
|
- lib/filterameter/sortable.rb
|
213
251
|
- lib/filterameter/sorts/attribute_sort.rb
|
252
|
+
- lib/filterameter/sorts/scope_sort.rb
|
214
253
|
- lib/filterameter/validators/inclusion_validator.rb
|
215
254
|
- lib/filterameter/version.rb
|
216
255
|
homepage: https://github.com/RockSolt/filterameter
|
@@ -225,7 +264,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
225
264
|
requirements:
|
226
265
|
- - ">="
|
227
266
|
- !ruby/object:Gem::Version
|
228
|
-
version:
|
267
|
+
version: 3.1.0
|
229
268
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
230
269
|
requirements:
|
231
270
|
- - ">="
|