toller 1.0.0 → 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: 565b78efab21039ddd7025adad94e0ac2e045943acef17e97374aac58b0b3b10
4
- data.tar.gz: a73035fb270e002dfd8cfe8ed38dff7317dbbf033e4aac922e5b409fc9aad92b
3
+ metadata.gz: bb4ebc1d6c6e8f4eaae0c68c740ae3b35693cd678c6e14e245295a40ad48f2d0
4
+ data.tar.gz: f380d915cb7f85ed2f3cd6873db803c71d01b9a28e96b9ba8375bb241a02432f
5
5
  SHA512:
6
- metadata.gz: b6a9f24aeec9383b065c39c3430c63ab90bd75cd963b414418e552d3fdd22a151e0c1da7ece697c92116958d294781c83330145889266d6be739e7ba2698b78a
7
- data.tar.gz: b816afc011e2070a7adfeee999702ecdf2ca04af45e50a4ec7966d98ef1fd5486aaa1bcffd4c48a443db9aafdc43a60c5679c7b4dd20e5ff918304b6b38232bd
6
+ metadata.gz: b84c1d90d5d1ebddb6ac81713874ce935d141664a3cb9b4601e08bd4394e844e26aa495d77d88b6381f39587093b3794411cefcc1fe082951239033ba2da070d
7
+ data.tar.gz: 2a1f0894da895687f8b8699073bd84932c41b1a1af591c95269f6e596e8555111af92dea2b065b598a1e3e18ee3f0869033385c46a44c04696d2fd1b74f37940
data/README.md CHANGED
@@ -16,7 +16,7 @@ See the [wiki](https://github.com/dfreerksen/toller/wiki) for usage information.
16
16
  Add this line to your application's Gemfile:
17
17
 
18
18
  ```ruby
19
- gem 'toller', '~> 1.0'
19
+ gem 'toller', '~> 1.1'
20
20
  ```
21
21
 
22
22
  And then execute:
data/lib/toller/filter.rb CHANGED
@@ -42,7 +42,7 @@ module Toller
42
42
  if type == :scope
43
43
  Filters::ScopeHandler.new.call(collection, value, properties)
44
44
  else
45
- Filters::WhereHandler.new.call(collection, type, value, properties)
45
+ Filters::ColumnHandler.new.call(collection, type, value, properties)
46
46
  end
47
47
  end
48
48
 
@@ -4,19 +4,27 @@ module Toller
4
4
  # :nodoc:
5
5
  module Filters
6
6
  ##
7
- # Where handler for filter
8
- class WhereHandler
7
+ # Column handler for filter
8
+ class ColumnHandler
9
9
  ##
10
10
  # Applies a plain `where` clause to +collection+ for a non-scope filter.
11
11
  #
12
+ # If +field+ isn't a real column on the collection's model, the filter is
13
+ # logged and skipped instead of raising once the relation is evaluated.
14
+ #
12
15
  # @param collection [ActiveRecord::Relation] the collection to filter
13
16
  # @param type [Symbol] the filter type (e.g. :string, :integer, :boolean)
14
17
  # @param value [Object] the raw filter param value
15
18
  # @param properties [Hash] the filter's properties, used to resolve `:field`
16
- # @return [ActiveRecord::Relation] the filtered collection
19
+ # @return [ActiveRecord::Relation] the filtered collection, or +collection+ unchanged if `:field` is unknown
17
20
  def call(collection, type, value, properties)
18
21
  field_name = properties[:field]
19
22
 
23
+ unless collection.klass.column_names.include?(field_name.to_s)
24
+ Rails.logger.warn("[Toller] Skipping filter: #{collection.klass} has no column `#{field_name}`")
25
+ return collection
26
+ end
27
+
20
28
  mutated_value = value_mutator(type, value)
21
29
 
22
30
  collection.where(field_name => mutated_value)
@@ -16,7 +16,7 @@ module Toller
16
16
  # @param value [String] the raw filter param value
17
17
  # @return [Boolean] true if +value+ is one of "1", "t", "true", "y", "yes"; false otherwise
18
18
  def call(value)
19
- %w[1 t true y yes].include?(value)
19
+ %w[1 t true y yes].include?(value.to_s.downcase)
20
20
  end
21
21
  end
22
22
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toller
4
+ # :nodoc:
5
+ module Filters
6
+ # :nodoc:
7
+ module Mutators
8
+ # :nodoc:
9
+ module Common
10
+ ##
11
+ # Shared range-parsing behavior for filter mutators. Extend this module to get a
12
+ # `call(value)` that returns +value+ unchanged, or a Range when +value+ contains
13
+ # Ruby range syntax (`..` for inclusive, `...` for exclusive).
14
+ #
15
+ # Note: methods here must refer to Ruby's core Range class as `::Range` — a bare
16
+ # `Range` would resolve to this module (Mutators::Common::Range) instead, since
17
+ # Ruby's constant lookup checks the lexical scope before the top level.
18
+ module Range
19
+ ##
20
+ # Coerces a raw filter param into its original value or a Range.
21
+ #
22
+ # @param value [String] the raw filter param value
23
+ # @return [String,::Range] the original value, or a Range when the value contains range syntax
24
+ # (`..` or `...`)
25
+ def call(value)
26
+ range_dots = inclusive_or_exclusive_range(value)
27
+
28
+ return value if range_dots.blank?
29
+
30
+ range(value, range_dots)
31
+ end
32
+
33
+ ##
34
+ # Builds a Range by splitting +value+ on the given range dots.
35
+ #
36
+ # @param value [String] the raw range string, e.g. "1..10"
37
+ # @param dots [String] the range separator, either ".." or "..."
38
+ # @return [::Range] the resulting range, exclusive when +dots+ is "..."
39
+ def range(value, dots)
40
+ ::Range.new(*value.split(dots), dots == "...")
41
+ end
42
+
43
+ ##
44
+ # Detects whether +value+ contains inclusive or exclusive range syntax.
45
+ #
46
+ # @param value [String] the raw filter param value
47
+ # @return [String,nil] "..." for an exclusive range, ".." for an inclusive range, or nil if +value+ is not
48
+ # a range
49
+ def inclusive_or_exclusive_range(value)
50
+ return "..." if value.include?("...")
51
+ return ".." if value.include?("..")
52
+
53
+ nil
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -8,43 +8,7 @@ module Toller
8
8
  ##
9
9
  # Date filter mutator
10
10
  module Date
11
- module_function
12
-
13
- ##
14
- # Coerces a raw filter param into a date value or range.
15
- #
16
- # @param value [String] the raw filter param value
17
- # @return [String,Range] the original value, or a Range when the value contains range syntax (`..` or `...`)
18
- def call(value)
19
- range_dots = inclusive_or_exclusive_range(value)
20
-
21
- return value if range_dots.blank?
22
-
23
- range(value, range_dots)
24
- end
25
-
26
- ##
27
- # Builds a Range by splitting +value+ on the given range dots.
28
- #
29
- # @param value [String] the raw range string, e.g. "2024-01-01..2024-12-31"
30
- # @param dots [String] the range separator, either ".." or "..."
31
- # @return [Range] the resulting range
32
- def range(value, dots)
33
- Range.new(*value.split(dots))
34
- end
35
-
36
- ##
37
- # Detects whether +value+ contains inclusive or exclusive range syntax.
38
- #
39
- # @param value [String] the raw filter param value
40
- # @return [String,nil] "..." for an exclusive range, ".." for an inclusive range, or nil if +value+ is not
41
- # a range
42
- def inclusive_or_exclusive_range(value)
43
- return "..." if value.include?("...")
44
- return ".." if value.include?("..")
45
-
46
- nil
47
- end
11
+ extend Common::Range
48
12
  end
49
13
  end
50
14
  end
@@ -8,43 +8,7 @@ module Toller
8
8
  ##
9
9
  # Datetime filter mutator
10
10
  module Datetime
11
- module_function
12
-
13
- ##
14
- # Coerces a raw filter param into a datetime value or range.
15
- #
16
- # @param value [String] the raw filter param value
17
- # @return [String,Range] the original value, or a Range when the value contains range syntax (`..` or `...`)
18
- def call(value)
19
- range_dots = inclusive_or_exclusive_range(value)
20
-
21
- return value if range_dots.blank?
22
-
23
- range(value, range_dots)
24
- end
25
-
26
- ##
27
- # Builds a Range by splitting +value+ on the given range dots.
28
- #
29
- # @param value [String] the raw range string, e.g. "2024-01-01..2024-12-31"
30
- # @param dots [String] the range separator, either ".." or "..."
31
- # @return [Range] the resulting range
32
- def range(value, dots)
33
- Range.new(*value.split(dots))
34
- end
35
-
36
- ##
37
- # Detects whether +value+ contains inclusive or exclusive range syntax.
38
- #
39
- # @param value [String] the raw filter param value
40
- # @return [String,nil] "..." for an exclusive range, ".." for an inclusive range, or nil if +value+ is not
41
- # a range
42
- def inclusive_or_exclusive_range(value)
43
- return "..." if value.include?("...")
44
- return ".." if value.include?("..")
45
-
46
- nil
47
- end
11
+ extend Common::Range
48
12
  end
49
13
  end
50
14
  end
@@ -8,51 +8,7 @@ module Toller
8
8
  ##
9
9
  # Integer filter mutator
10
10
  module Integer
11
- module_function
12
-
13
- ##
14
- # Coerces a raw filter param into an integer value or range.
15
- #
16
- # @param value [String] the raw filter param value
17
- # @return [String, Range] the original value, or a Range when the value contains range syntax (`..` or `...`)
18
- def call(value)
19
- return value unless range?(value)
20
-
21
- range(value)
22
- end
23
-
24
- ##
25
- # Checks whether +value+ contains range syntax.
26
- #
27
- # @param value [String] the raw filter param value
28
- # @return [Boolean] true if +value+ contains `..` or `...`
29
- def range?(value)
30
- range_dots = inclusive_or_exclusive_range(value)
31
-
32
- range_dots.present?
33
- end
34
-
35
- ##
36
- # Builds a Range by splitting +value+ on its range dots.
37
- #
38
- # @param value [String] the raw range string, e.g. "1..10"
39
- # @return [Range] the resulting range
40
- def range(value)
41
- Range.new(*value.split(inclusive_or_exclusive_range(value)))
42
- end
43
-
44
- ##
45
- # Detects whether +value+ contains inclusive or exclusive range syntax.
46
- #
47
- # @param value [String] the raw filter param value
48
- # @return [String,nil] "..." for an exclusive range, ".." for an inclusive range, or nil if +value+ is not
49
- # a range
50
- def inclusive_or_exclusive_range(value)
51
- return "..." if value.include?("...")
52
- return ".." if value.include?("..")
53
-
54
- nil
55
- end
11
+ extend Common::Range
56
12
  end
57
13
  end
58
14
  end
@@ -8,43 +8,7 @@ module Toller
8
8
  ##
9
9
  # Time filter mutator
10
10
  module Time
11
- module_function
12
-
13
- ##
14
- # Coerces a raw filter param into a time value or range.
15
- #
16
- # @param value [String] the raw filter param value
17
- # @return [String,Range] the original value, or a Range when the value contains range syntax (`..` or `...`)
18
- def call(value)
19
- range_dots = inclusive_or_exclusive_range(value)
20
-
21
- return value if range_dots.blank?
22
-
23
- range(value, range_dots)
24
- end
25
-
26
- ##
27
- # Builds a Range by splitting +value+ on the given range dots.
28
- #
29
- # @param value [String] the raw range string, e.g. "09:00..17:00"
30
- # @param dots [String] the range separator, either ".." or "..."
31
- # @return [Range] the resulting range
32
- def range(value, dots)
33
- Range.new(*value.split(dots))
34
- end
35
-
36
- ##
37
- # Detects whether +value+ contains inclusive or exclusive range syntax.
38
- #
39
- # @param value [String] the raw filter param value
40
- # @return [String,nil] "..." for an exclusive range, ".." for an inclusive range, or nil if +value+ is not
41
- # a range
42
- def inclusive_or_exclusive_range(value)
43
- return "..." if value.include?("...")
44
- return ".." if value.include?("..")
45
-
46
- nil
47
- end
11
+ extend Common::Range
48
12
  end
49
13
  end
50
14
  end
@@ -9,13 +9,21 @@ module Toller
9
9
  ##
10
10
  # Applies a named scope to +collection+ for a `type: :scope` filter.
11
11
  #
12
+ # If the resolved scope doesn't exist on the collection's model, the
13
+ # filter is logged and skipped instead of raising a NoMethodError.
14
+ #
12
15
  # @param collection [ActiveRecord::Relation] the collection to filter
13
16
  # @param value [Object] the filter param value to pass to the scope
14
17
  # @param properties [Hash] the filter's properties, used to resolve `:scope_name` (falling back to `:field`)
15
- # @return [ActiveRecord::Relation] the scoped collection
18
+ # @return [ActiveRecord::Relation] the scoped collection, or +collection+ unchanged if the scope is unknown
16
19
  def call(collection, value, properties)
17
20
  scoped_name = properties[:scope_name] || properties[:field]
18
21
 
22
+ unless collection.klass.respond_to?(scoped_name)
23
+ Rails.logger.warn("[Toller] Skipping filter: #{collection.klass} has no scope `#{scoped_name}`")
24
+ return collection
25
+ end
26
+
19
27
  collection.public_send(scoped_name, value)
20
28
  end
21
29
  end
data/lib/toller/sort.rb CHANGED
@@ -42,7 +42,7 @@ module Toller
42
42
  if type == :scope
43
43
  Sorts::ScopeHandler.new.call(collection, direction, properties)
44
44
  else
45
- Sorts::OrderHandler.new.call(collection, direction, properties)
45
+ Sorts::ColumnHandler.new.call(collection, direction, properties)
46
46
  end
47
47
  end
48
48
 
@@ -4,18 +4,26 @@ module Toller
4
4
  # :nodoc:
5
5
  module Sorts
6
6
  ##
7
- # Order handler for filter
8
- class OrderHandler
7
+ # Column handler for filter
8
+ class ColumnHandler
9
9
  ##
10
10
  # Applies a plain `order` clause to +collection+ for a non-scope sort.
11
11
  #
12
+ # If +field+ isn't a real column on the collection's model, the sort is
13
+ # logged and skipped instead of raising once the relation is evaluated.
14
+ #
12
15
  # @param collection [ActiveRecord::Relation] the collection to sort
13
16
  # @param direction [Symbol] the sort direction, :asc or :desc
14
17
  # @param properties [Hash] the sort's properties, used to resolve `:field`
15
- # @return [ActiveRecord::Relation] the sorted collection
18
+ # @return [ActiveRecord::Relation] the sorted collection, or +collection+ unchanged if `:field` is unknown
16
19
  def call(collection, direction, properties)
17
20
  field_name = properties[:field]
18
21
 
22
+ unless collection.klass.column_names.include?(field_name.to_s)
23
+ Rails.logger.warn("[Toller] Skipping sort: #{collection.klass} has no column `#{field_name}`")
24
+ return collection
25
+ end
26
+
19
27
  collection.order(field_name => direction)
20
28
  end
21
29
  end
@@ -9,13 +9,21 @@ module Toller
9
9
  ##
10
10
  # Applies a named scope to +collection+ for a `type: :scope` sort.
11
11
  #
12
+ # If the resolved scope doesn't exist on the collection's model, the
13
+ # sort is logged and skipped instead of raising a NoMethodError.
14
+ #
12
15
  # @param collection [ActiveRecord::Relation] the collection to sort
13
16
  # @param direction [Symbol] the sort direction, :asc or :desc, passed to the scope
14
17
  # @param properties [Hash] the sort's properties, used to resolve `:scope_name` (falling back to `:field`)
15
- # @return [ActiveRecord::Relation] the scoped collection
18
+ # @return [ActiveRecord::Relation] the scoped collection, or +collection+ unchanged if the scope is unknown
16
19
  def call(collection, direction, properties)
17
20
  scoped_name = properties[:scope_name] || properties[:field]
18
21
 
22
+ unless collection.klass.respond_to?(scoped_name)
23
+ Rails.logger.warn("[Toller] Skipping sort: #{collection.klass} has no scope `#{scoped_name}`")
24
+ return collection
25
+ end
26
+
19
27
  collection.public_send(scoped_name, direction)
20
28
  end
21
29
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Toller
4
4
  # @return [String] the current gem version
5
- VERSION = "1.0.0"
5
+ VERSION = "1.1.0"
6
6
  end
data/lib/toller.rb CHANGED
@@ -1,16 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "toller/filter"
4
+ require "toller/filters/column_handler"
4
5
  require "toller/filters/mutators/boolean"
6
+ require "toller/filters/mutators/common/range"
5
7
  require "toller/filters/mutators/date"
6
8
  require "toller/filters/mutators/datetime"
7
9
  require "toller/filters/mutators/integer"
8
10
  require "toller/filters/mutators/time"
9
11
  require "toller/filters/scope_handler"
10
- require "toller/filters/where_handler"
11
12
  require "toller/retriever"
12
13
  require "toller/sort"
13
- require "toller/sorts/order_handler"
14
+ require "toller/sorts/column_handler"
14
15
  require "toller/sorts/scope_handler"
15
16
 
16
17
  ##
@@ -20,6 +21,18 @@ require "toller/sorts/scope_handler"
20
21
  module Toller
21
22
  extend ActiveSupport::Concern
22
23
 
24
+ ##
25
+ # Coerces a raw string value into a boolean, using the same rule Toller's
26
+ # own `type: :boolean` filters use internally. Handy inside a model scope
27
+ # backing a `type: :scope` filter, which receives the raw param value
28
+ # unmutated.
29
+ #
30
+ # @param value [String] the raw value
31
+ # @return [Boolean] true if +value+ is one of "1", "t", "true", "y", "yes"; false otherwise
32
+ def self.truthy(value)
33
+ Filters::Mutators::Boolean.call(value)
34
+ end
35
+
23
36
  ##
24
37
  # Applies every active filter/sort for the current request to +collection+.
25
38
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toller
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Freerksen
@@ -37,16 +37,17 @@ files:
37
37
  - Rakefile
38
38
  - lib/toller.rb
39
39
  - lib/toller/filter.rb
40
+ - lib/toller/filters/column_handler.rb
40
41
  - lib/toller/filters/mutators/boolean.rb
42
+ - lib/toller/filters/mutators/common/range.rb
41
43
  - lib/toller/filters/mutators/date.rb
42
44
  - lib/toller/filters/mutators/datetime.rb
43
45
  - lib/toller/filters/mutators/integer.rb
44
46
  - lib/toller/filters/mutators/time.rb
45
47
  - lib/toller/filters/scope_handler.rb
46
- - lib/toller/filters/where_handler.rb
47
48
  - lib/toller/retriever.rb
48
49
  - lib/toller/sort.rb
49
- - lib/toller/sorts/order_handler.rb
50
+ - lib/toller/sorts/column_handler.rb
50
51
  - lib/toller/sorts/scope_handler.rb
51
52
  - lib/toller/version.rb
52
53
  homepage: https://github.com/dfreerksen/toller