filterameter 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f711873e7af07c76d8ce2df1c25fee486408b9072f4c7fc4fde29b0fd9cf60e3
4
- data.tar.gz: 568e9031173ae41b4a7cf514649c77d182be5cb7188516989f7213f69bd56f71
3
+ metadata.gz: d8110e021be072bc1f37dd324c42e590e3c91a94fbadd934fdc0758f1f85d700
4
+ data.tar.gz: 7ee31589a1fb66717647a1085658ab4dd5b9ba7deb58d847102fc72be1d9dffe
5
5
  SHA512:
6
- metadata.gz: 1b3cd23fed20b0d106ed1348eb6f874a2a883409237631c18e5ae77f3bc0a3a917512a764e3340cbfbbf357fa89053ec355267bc925e2ca8dfe29e507f159b7b
7
- data.tar.gz: 910188eaa493a2abadcb44b37c429529f5b8b3921d27f3abf5a2e7bd123b148ec449dc6d84697eb013f34f063a69dc892aebb76c380576fd7b7c81d13eb22706
6
+ metadata.gz: 83117da59a74ba92bf255cdb8ccd025f071f5549dfeb315af5894c12dc8829928fef6aae0eb6d94eaec7abfd5f2a85aa1d085bd7b3ec26d0b476d0506febd7a4
7
+ data.tar.gz: 3f777e21518ce18d2f62f36912ba3b532839a9f5d03bc73dd7796648d1fcec81992701b07ad639dbe021f7765ab5d5baa66d4d82bf92edc7264a2463df4044ff
data/README.md CHANGED
@@ -39,6 +39,24 @@ filter :size, validates: { inclusion: { in: %w[Small Medium Large] }, unless: ->
39
39
 
40
40
  Note that the `inclusion` validator does not allow arrays to be specified. If the filter should allow multiple values to be specified, then the validation needs to be disabled when the value an array.
41
41
 
42
+ #### partial
43
+ Specify the partial option if the filter should do a partial search (SQL's `LIKE`). The partial option accepts a hash to specify the search behavior. Here are the available options:
44
+ - match: anywhere (default), from_start, dynamic
45
+ - case_sensitive: true, false (default)
46
+
47
+ There are two shortcuts: : the partial option can be declared with `true`, which just uses the defaults; or the partial option can be declared with the match option directly, such as `partial: :from_start`.
48
+
49
+ ```ruby
50
+ filter :description, partial: true
51
+ filter :department_name, partial: :from_start
52
+ filter :reason, partial: { match: :dynamic, case_sensitive: true }
53
+ ```
54
+
55
+ The `match` options defines where you are searching (which then controls where the wildcard(s) appear):
56
+ - anywhere: adds wildcards at the start and end, for example '%blue%'
57
+ - from_start: adds a wildcard at the end, for example 'blue%'
58
+ - dynamic: adds no wildcards; this enables the client to fully control the search string
59
+
42
60
  ### Configuring Controllers
43
61
 
44
62
  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:
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/array/wrap'
4
+ require 'filterameter/options/partial_options'
4
5
 
5
6
  module Filterameter
6
7
  # = Filter Declaration
@@ -17,6 +18,7 @@ module Filterameter
17
18
  @association = options[:association]
18
19
  @filter_on_empty = options.fetch(:filter_on_empty, false)
19
20
  @validations = Array.wrap(options[:validates])
21
+ @raw_partial_options = options.fetch(:partial, false)
20
22
  end
21
23
 
22
24
  def nested?
@@ -31,10 +33,18 @@ module Filterameter
31
33
  @filter_on_empty
32
34
  end
33
35
 
36
+ def partial_search?
37
+ partial_options.present?
38
+ end
39
+
40
+ def partial_options
41
+ @partial_options ||= @raw_partial_options ? Options::PartialOptions.new(@raw_partial_options) : nil
42
+ end
43
+
34
44
  private
35
45
 
36
46
  def validate_options(options)
37
- options.assert_valid_keys(:name, :association, :filter_on_empty, :validates)
47
+ options.assert_valid_keys(:name, :association, :filter_on_empty, :validates, :partial)
38
48
  end
39
49
  end
40
50
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'filterameter/filters/attribute_filter'
4
4
  require 'filterameter/filters/conditional_scope_filter'
5
+ require 'filterameter/filters/matches_filter'
5
6
  require 'filterameter/filters/nested_filter'
6
7
  require 'filterameter/filters/scope_filter'
7
8
 
@@ -16,19 +17,21 @@ module Filterameter
16
17
 
17
18
  def build(declaration)
18
19
  model = declaration.nested? ? model_from_association(declaration.association) : @model_class
19
- filter = build_filter(model, declaration.name)
20
+ filter = build_filter(model, declaration)
20
21
 
21
22
  declaration.nested? ? Filterameter::Filters::NestedFilter.new(declaration.association, model, filter) : filter
22
23
  end
23
24
 
24
25
  private
25
26
 
26
- def build_filter(model, name)
27
+ def build_filter(model, declaration)
27
28
  # checking dangerous_class_method? excludes any names that cannot be scope names, such as "name"
28
- if model.respond_to?(name) && !model.dangerous_class_method?(name)
29
- Filterameter::Filters::ScopeFilter.new(name)
29
+ if model.respond_to?(declaration.name) && !model.dangerous_class_method?(declaration.name)
30
+ Filterameter::Filters::ScopeFilter.new(declaration.name)
31
+ elsif declaration.partial_search?
32
+ Filterameter::Filters::MatchesFilter.new(declaration.name, declaration.partial_options)
30
33
  else
31
- Filterameter::Filters::AttributeFilter.new(name)
34
+ Filterameter::Filters::AttributeFilter.new(declaration.name)
32
35
  end
33
36
  end
34
37
 
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Filterameter
4
+ module Filters
5
+ # = Matches Filter
6
+ #
7
+ # Class MatchesFilter uses arel's `matches` to generate a LIKE query.
8
+ class MatchesFilter
9
+ def initialize(attribute_name, options)
10
+ @attribute_name = attribute_name
11
+ @prefix = options.match_anywhere? ? '%' : nil
12
+ @suffix = options.match_anywhere? || options.match_from_start? ? '%' : nil
13
+ @case_sensitive = options.case_sensitive?
14
+ end
15
+
16
+ def apply(query, value)
17
+ arel = query.arel_table[@attribute_name].matches("#{@prefix}#{value}#{@suffix}", false, @case_sensitive)
18
+ query.where(arel)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Options
4
+ # = Partial Options
5
+ #
6
+ # Class PartialOptions parses the options passed in as partial, then exposes those. Here are the options along with
7
+ # their valid values:
8
+ # - match: anywhere (default), from_start, dynamic
9
+ # - case_sensitive: true, false (default)
10
+ #
11
+ # Options may be specified by passing a hash with the option keys:
12
+ #
13
+ # partial: { match: :from_start, case_sensitive: true }
14
+ #
15
+ # There are two shortcuts: the partial option can be declared with `true`, which just uses the defaults; or the
16
+ # partial option can be declared with the match option directly, such as partial: :from_start.
17
+ class PartialOptions
18
+ VALID_OPTIONS = %i[match case_sensitive].freeze
19
+ VALID_MATCH_OPTIONS = %w[anywhere from_start dynamic].freeze
20
+
21
+ def initialize(options)
22
+ @match = 'anywhere'
23
+ @case_sensitive = false
24
+
25
+ if options.is_a?(TrueClass)
26
+ nil
27
+ elsif options.is_a? Hash
28
+ evaluate_hash(options)
29
+ elsif options.is_a?(String) || options.is_a?(Symbol)
30
+ assign_match(options)
31
+ end
32
+ end
33
+
34
+ def case_sensitive?
35
+ @case_sensitive
36
+ end
37
+
38
+ def match_anywhere?
39
+ @match == 'anywhere'
40
+ end
41
+
42
+ def match_from_start?
43
+ @match == 'from_start'
44
+ end
45
+
46
+ def match_dynamically?
47
+ @match == 'dynamic'
48
+ end
49
+
50
+ private
51
+
52
+ def evaluate_hash(options)
53
+ options.assert_valid_keys(:match, :case_sensitive)
54
+ assign_match(options[:match]) if options.key?(:match)
55
+ assign_case_sensitive(options[:case_sensitive]) if options.key?(:case_sensitive)
56
+ end
57
+
58
+ def assign_match(value)
59
+ validate_match(value)
60
+ @match = value.to_s
61
+ end
62
+
63
+ def validate_match(value)
64
+ return if VALID_MATCH_OPTIONS.include? value.to_s
65
+
66
+ raise ArgumentError,
67
+ "Invalid match option for partial: #{value}. Valid options are #{VALID_MATCH_OPTIONS.to_sentence}"
68
+ end
69
+
70
+ def assign_case_sensitive(value)
71
+ validate_case_sensitive(value)
72
+ @case_sensitive = value
73
+ end
74
+
75
+ def validate_case_sensitive(value)
76
+ return if value.is_a?(TrueClass) || value.is_a?(FalseClass)
77
+
78
+ raise ArgumentError, "Invalid case_sensitive option for partial: #{value}. Valid options are true and false."
79
+ end
80
+ end
81
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Filterameter
4
- VERSION = '0.1.2'
4
+ VERSION = '0.1.3'
5
5
  end
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.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Todd Kummer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-18 00:00:00.000000000 Z
11
+ date: 2020-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -144,9 +144,11 @@ files:
144
144
  - lib/filterameter/filter_factory.rb
145
145
  - lib/filterameter/filters/attribute_filter.rb
146
146
  - lib/filterameter/filters/conditional_scope_filter.rb
147
+ - lib/filterameter/filters/matches_filter.rb
147
148
  - lib/filterameter/filters/nested_filter.rb
148
149
  - lib/filterameter/filters/scope_filter.rb
149
150
  - lib/filterameter/log_subscriber.rb
151
+ - lib/filterameter/options/partial_options.rb
150
152
  - lib/filterameter/parameters_base.rb
151
153
  - lib/filterameter/version.rb
152
154
  - lib/tasks/filterameter_tasks.rake