actionset 0.8.0 → 0.8.1

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
  SHA1:
3
- metadata.gz: a4c9246a4316813c78ee58c47708a977b5811c2c
4
- data.tar.gz: d6f45a1104daad185b971f7e9a4ac47abc698432
3
+ metadata.gz: 2d8befffeaabf8526df7cdd15f501f2e9e99732b
4
+ data.tar.gz: 9dc431b8b1af60ed1d6d57d8ba9e66ac6e6a0688
5
5
  SHA512:
6
- metadata.gz: df2cc22b86dfc5bc3579bd9f84964844920afe47a3906ed31ed150f297e1eea0c9feecfdd0767c9c8e3dcec03e82d721ab1a5cd4a3a98833ea94b7f956cce03f
7
- data.tar.gz: 9851ffa5d0e416ed93af12ae5a939fbbbe60b49a4670386e085af9a9d9c38f8d7bd313125d04710c891b3c950e296de1940e2f7c6137976f069669ef58a9bf0c
6
+ metadata.gz: 076e19f851950c2a0c2ac377c51a04eaf16198060ce8a1e6a766339f659c61eab1ae125c34005db809b27b0b0c02aad85e9ad1502f13c11ad04a821be6d40d0d
7
+ data.tar.gz: c2de49f8fb3a36ecf86dfc00c51a880b675ea8f7db129de84a0eace0565c9175528cd53fcf25096ff04151c727189ce09c53ac54b50221df20784e8ba8e52270
data/CHANGELOG CHANGED
@@ -1,3 +1,20 @@
1
+ v 0.8.1
2
+ - add a case-insensitive filtering/sorting option (`/i/`)
3
+ - standardize how filtering by class scopes behaves
4
+ + only collection-returning class methods are handled
5
+ + scopes defined on computed associations aren't handled
6
+ - refactor parts of the strategy implemention layer
7
+ + create generalize "set instruction" classes for ActiveRecord and Enumerable strategies
8
+ + handle default operators in the strategy layer, not the instruction layer
9
+ - refactor parts of the instruction implementation layer
10
+ + memoize instruction getters to avoid recomputing on access
11
+ - refactor specs
12
+ + update filtering by data types when using an invalid field test
13
+ + remove case-insensitive sorting example
14
+ + simplify the sorted collection expectation
15
+ + add some helper methods for generating unique factory data
16
+ + print a detailed report of the state data on spec failure
17
+ + update the filtering request specs to run more of then, but still running random subsets
1
18
  v 0.8.0
2
19
  - merge ActiveSet and ActionSet into a monorepo
3
20
  - merge and overhaul the specs
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- actionset (0.8.0)
4
+ actionset (0.8.1)
5
5
  activesupport (>= 4.0.2)
6
6
  railties
7
7
 
@@ -6,7 +6,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
6
  Gem::Specification.new do |spec|
7
7
  spec.platform = Gem::Platform::RUBY
8
8
  spec.name = 'actionset'
9
- spec.version = '0.8.0'
9
+ spec.version = '0.8.1'
10
10
  spec.authors = ['Stephen Margheim']
11
11
  spec.email = ['stephen.margheim@gmail.com']
12
12
 
@@ -9,7 +9,12 @@ module Pagination
9
9
  include Pagination::TotalPagesForHelper
10
10
 
11
11
  def pagination_current_page_description_for(set)
12
- description = "Page&nbsp;<strong>#{pagination_current_page_for(set)}</strong>&nbsp;of&nbsp;<strong>#{pagination_total_pages_for(set)}</strong>".html_safe
12
+ description = [
13
+ 'Page',
14
+ "<strong>#{pagination_current_page_for(set)}</strong>",
15
+ 'of',
16
+ "<strong>#{pagination_total_pages_for(set)}</strong>"
17
+ ].join('&nbsp;').html_safe
13
18
 
14
19
  content_tag(:span,
15
20
  description,
@@ -8,7 +8,7 @@ module Pagination
8
8
 
9
9
  def pagination_total_pages_for(set)
10
10
  total_set_size = set.instructions.dig(:paginate, :count)
11
- return 1 if total_set_size == 0
11
+ return 1 if total_set_size.zero?
12
12
 
13
13
  (total_set_size.to_f / pagination_page_size_for(set)).ceil
14
14
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveSet
4
+ class ActiveRecordSetInstruction < SimpleDelegator
5
+ def initialize(attribute_instruction, set)
6
+ @attribute_instruction = attribute_instruction
7
+ @set = set
8
+ super(@attribute_instruction)
9
+ end
10
+
11
+ def initial_relation
12
+ return @set if @attribute_instruction.associations_array.empty?
13
+
14
+ @set.eager_load(@attribute_instruction.associations_hash)
15
+ end
16
+
17
+ def arel_type
18
+ attribute_model
19
+ .columns_hash[@attribute_instruction.attribute]
20
+ .type
21
+ end
22
+
23
+ def arel_table
24
+ # This is to work around an bug in ActiveRecord,
25
+ # where BINARY fields aren't found properly when using
26
+ # the `arel_table` class method to build an ARel::Node
27
+ if arel_type == :binary
28
+ Arel::Table.new(attribute_model.table_name)
29
+ else
30
+ attribute_model.arel_table
31
+ end
32
+ end
33
+
34
+ def arel_column
35
+ _arel_column = arel_table[@attribute_instruction.attribute]
36
+ return _arel_column.lower if case_insensitive_operation?
37
+
38
+ _arel_column
39
+ end
40
+
41
+ def arel_operator
42
+ @attribute_instruction.operator(default: :eq)
43
+ end
44
+
45
+ def arel_value
46
+ _arel_value = @attribute_instruction.value
47
+ return _arel_value.downcase if case_insensitive_operation?
48
+
49
+ _arel_value
50
+ end
51
+
52
+ def case_insensitive_operation?
53
+ @attribute_instruction.case_insensitive? && arel_type.presence_in(%i[string text])
54
+ end
55
+
56
+ def attribute_model
57
+ return @set.klass if @attribute_instruction.associations_array.empty?
58
+ return @attribute_model if defined? @attribute_model
59
+
60
+ @attribute_model = @attribute_instruction
61
+ .associations_array
62
+ .reduce(@set) do |obj, assoc|
63
+ obj.reflections[assoc.to_s]&.klass
64
+ end
65
+ end
66
+ end
67
+ end
@@ -17,36 +17,56 @@ class ActiveSet
17
17
  @processed
18
18
  end
19
19
 
20
+ def case_insensitive?
21
+ return false unless options
22
+
23
+ options.include? :i
24
+ end
25
+
20
26
  def attribute
21
- attribute = @keypath.last
22
- return attribute.sub(operator_regex, '') if attribute&.match operator_regex
27
+ return @attribute if defined? @attribute
23
28
 
24
- attribute
29
+ attribute = @keypath.last
30
+ attribute = attribute&.sub(operator_regex, '')
31
+ attribute = attribute&.sub(options_regex, '')
32
+ @attribute = attribute
25
33
  end
26
34
 
27
35
  def operator(default: '==')
28
- attribute = @keypath.last
29
- return attribute[operator_regex, 1] if attribute&.match operator_regex
36
+ return @operator if defined? @operator
37
+
38
+ attribute_instruction = @keypath.last
39
+ @operator = (attribute_instruction[operator_regex, 1] || default).to_sym
40
+ end
41
+
42
+ def options
43
+ return @options if defined? @options
30
44
 
31
- default
45
+ @options = @keypath.last[options_regex, 1]&.split('')&.map(&:to_sym)
32
46
  end
33
47
 
34
48
  def associations_array
49
+ return @associations_array if defined? @associations_array
35
50
  return [] unless @keypath.any?
36
51
 
37
- @keypath.slice(0, @keypath.length - 1)
52
+ @associations_array = @keypath.slice(0, @keypath.length - 1)
38
53
  end
39
54
 
40
55
  def associations_hash
56
+ return @associations_hash if defined? @associations_hash
41
57
  return {} unless @keypath.any?
42
58
 
43
- associations_array.reverse.reduce({}) do |hash, association|
59
+ @associations_hash = associations_array.reverse.reduce({}) do |hash, association|
44
60
  { association => hash }
45
61
  end
46
62
  end
47
63
 
48
64
  def value_for(item:)
49
- resource_for(item: item).public_send(attribute)
65
+ @values_for ||= Hash.new do |h, key|
66
+ h[key] = resource_for(item: key).public_send(attribute)
67
+ end
68
+
69
+ @values_for[item]
50
70
  rescue StandardError
51
71
  # :nocov:
52
72
  nil
@@ -54,11 +74,15 @@ class ActiveSet
54
74
  end
55
75
 
56
76
  def resource_for(item:)
57
- associations_array.reduce(item) do |resource, association|
58
- return nil unless resource.respond_to? association
77
+ @resources_for ||= Hash.new do |h, key|
78
+ h[key] = associations_array.reduce(key) do |resource, association|
79
+ break nil unless resource.respond_to? association
59
80
 
60
- resource.public_send(association)
81
+ resource.public_send(association)
82
+ end
61
83
  end
84
+
85
+ @resources_for[item]
62
86
  rescue StandardError
63
87
  # :nocov:
64
88
  nil
@@ -68,7 +92,11 @@ class ActiveSet
68
92
  private
69
93
 
70
94
  def operator_regex
71
- /\((.*?)\)/
95
+ %r{\((.*?)\)}
96
+ end
97
+
98
+ def options_regex
99
+ %r{\/(.*?)\/}
72
100
  end
73
101
  end
74
102
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveSet
4
+ class EnumerableSetInstruction < SimpleDelegator
5
+ def initialize(attribute_instruction, set)
6
+ @attribute_instruction = attribute_instruction
7
+ @set = set
8
+ super(@attribute_instruction)
9
+ end
10
+
11
+ def attribute_value_for(item)
12
+ item_value = @attribute_instruction
13
+ .value_for(item: item)
14
+ item_value = item_value.downcase if case_insensitive_operation_for?(item_value)
15
+ item_value
16
+ end
17
+
18
+ def attribute_value
19
+ _attribute_value = @attribute_instruction.value
20
+ _attribute_value = _attribute_value.downcase if case_insensitive_operation_for?(_attribute_value)
21
+ _attribute_value
22
+ end
23
+
24
+ def case_insensitive_operation_for?(value)
25
+ return false unless @attribute_instruction.case_insensitive?
26
+
27
+ value.is_a?(String) || value.is_a?(Symbol)
28
+ end
29
+
30
+ def attribute_instance
31
+ set_item = @set.find(&:present?)
32
+ return set_item if @attribute_instruction.associations_array.empty?
33
+ return @attribute_model if defined? @attribute_model
34
+
35
+ @attribute_model = @attribute_instruction
36
+ .associations_array
37
+ .reduce(set_item) do |obj, assoc|
38
+ obj.public_send(assoc)
39
+ end
40
+ end
41
+
42
+ def attribute_class
43
+ attribute_instance&.class
44
+ end
45
+ end
46
+ end
@@ -1,21 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../active_record_set_instruction'
4
+ require 'active_support/core_ext/module/delegation'
5
+
3
6
  class ActiveSet
4
7
  module Filtering
5
8
  class ActiveRecordStrategy
9
+ delegate :attribute_model,
10
+ :arel_column,
11
+ :arel_operator,
12
+ :arel_value,
13
+ :arel_type,
14
+ :initial_relation,
15
+ :attribute,
16
+ to: :@set_instruction
17
+
6
18
  def initialize(set, attribute_instruction)
7
19
  @set = set
8
20
  @attribute_instruction = attribute_instruction
21
+ @set_instruction = ActiveRecordSetInstruction.new(attribute_instruction, set)
9
22
  end
10
23
 
11
24
  def execute
12
25
  return false unless @set.respond_to? :to_sql
13
26
 
14
- if execute_where_operation?
15
- statement = where_operation
16
- elsif execute_merge_operation?
27
+ if execute_filter_operation?
28
+ statement = filter_operation
29
+ elsif execute_intersect_operation?
17
30
  begin
18
- statement = merge_operation
31
+ statement = intersect_operation
19
32
  rescue ArgumentError # thrown if merging a non-ActiveRecord::Relation
20
33
  return false
21
34
  end
@@ -28,71 +41,45 @@ class ActiveSet
28
41
 
29
42
  private
30
43
 
31
- def execute_where_operation?
44
+ def execute_filter_operation?
32
45
  return false unless attribute_model
33
46
  return false unless attribute_model.respond_to?(:attribute_names)
34
- return false unless attribute_model.attribute_names.include?(@attribute_instruction.attribute)
47
+ return false unless attribute_model.attribute_names.include?(attribute)
35
48
 
36
49
  true
37
50
  end
38
51
 
39
- def execute_merge_operation?
52
+ def execute_intersect_operation?
40
53
  return false unless attribute_model
41
- return false unless attribute_model.respond_to?(@attribute_instruction.attribute)
42
- return false if attribute_model.method(@attribute_instruction.attribute).arity.zero?
54
+ return false unless attribute_model.respond_to?(attribute)
55
+ return false if attribute_model.method(attribute).arity.zero?
43
56
 
44
57
  true
45
58
  end
46
59
 
47
- def where_operation
60
+ def filter_operation
48
61
  initial_relation
49
62
  .where(
50
63
  arel_column.send(
51
- @attribute_instruction.operator(default: 'eq'),
52
- @attribute_instruction.value
64
+ arel_operator,
65
+ arel_value
53
66
  )
54
67
  )
55
68
  end
56
69
 
57
- def merge_operation
70
+ def intersect_operation
71
+ # NOTE: If merging relations that contain duplicate column conditions,
72
+ # the second condition will replace the first.
73
+ # e.g. Thing.where(id: [1,2]).merge(Thing.where(id: [2,3]))
74
+ # => [Thing<2>, Thing<3>] NOT [Thing<2>]
58
75
  initial_relation
59
76
  .merge(
60
77
  attribute_model.public_send(
61
- @attribute_instruction.attribute,
62
- @attribute_instruction.value
78
+ attribute,
79
+ arel_value
63
80
  )
64
81
  )
65
82
  end
66
-
67
- def initial_relation
68
- return @set if @attribute_instruction.associations_array.empty?
69
-
70
- @set.eager_load(@attribute_instruction.associations_hash)
71
- end
72
-
73
- def arel_column
74
- attribute_type = attribute_model.columns_hash[@attribute_instruction.attribute].type
75
-
76
- # This is to work around an bug in ActiveRecord,
77
- # where BINARY fields aren't found properly when using the `arel_table` class method
78
- # to build an ARel::Node
79
- if attribute_type == :binary
80
- Arel::Table.new(attribute_model.table_name)[@attribute_instruction.attribute]
81
- else
82
- attribute_model.arel_table[@attribute_instruction.attribute]
83
- end
84
- end
85
-
86
- def attribute_model
87
- return @set.klass if @attribute_instruction.associations_array.empty?
88
- return @attribute_model if defined? @attribute_model
89
-
90
- @attribute_model = @attribute_instruction
91
- .associations_array
92
- .reduce(@set) do |obj, assoc|
93
- obj.reflections[assoc.to_s]&.klass
94
- end
95
- end
96
83
  end
97
84
  end
98
85
  end
@@ -1,77 +1,78 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../enumerable_set_instruction'
4
+ require 'active_support/core_ext/module/delegation'
5
+
3
6
  class ActiveSet
4
7
  module Filtering
5
8
  class EnumerableStrategy
9
+ delegate :attribute_instance,
10
+ :attribute_class,
11
+ :attribute_value,
12
+ :attribute_value_for,
13
+ :operator,
14
+ :attribute,
15
+ to: :@set_instruction
16
+
6
17
  def initialize(set, attribute_instruction)
7
18
  @set = set
8
19
  @attribute_instruction = attribute_instruction
20
+ @set_instruction = EnumerableSetInstruction.new(attribute_instruction, set)
9
21
  end
10
22
 
11
23
  def execute
12
24
  return false unless @set.respond_to? :select
13
25
 
14
- @set.select do |item|
15
- next attribute_matches_for?(item) if can_match_attribute_for?(item)
16
- next class_method_matches_for?(item) if can_match_class_method_for?(item)
17
-
18
- false
26
+ if execute_filter_operation?
27
+ set = filter_operation
28
+ elsif execute_intersect_operation?
29
+ begin
30
+ set = intersect_operation
31
+ rescue TypeError # thrown if intersecting with a non-Array
32
+ return false
33
+ end
34
+ else
35
+ return false
19
36
  end
37
+
38
+ set
20
39
  end
21
40
 
22
41
  private
23
42
 
24
- def can_match_attribute_for?(item)
25
- attribute_item = attribute_item_for(item)
26
-
27
- return false unless attribute_item
28
- return false unless attribute_item.respond_to?(@attribute_instruction.attribute)
29
- return false if attribute_item.method(@attribute_instruction.attribute).arity.positive?
43
+ def execute_filter_operation?
44
+ return false unless attribute_instance
45
+ return false unless attribute_instance.respond_to?(attribute)
46
+ return false if attribute_instance.method(attribute).arity.positive?
30
47
 
31
48
  true
32
49
  end
33
50
 
34
- def can_match_class_method_for?(item)
35
- attribute_item = attribute_item_for(item)
36
-
37
- return false unless attribute_item
38
- return false unless attribute_item.class
39
- return false unless attribute_item.class.respond_to?(@attribute_instruction.attribute)
40
- return false if attribute_item.class.method(@attribute_instruction.attribute).arity.zero?
51
+ def execute_intersect_operation?
52
+ return false unless attribute_class
53
+ return false unless attribute_class.respond_to?(attribute)
54
+ return false if attribute_class.method(attribute).arity.zero?
41
55
 
42
56
  true
43
57
  end
44
58
 
45
- def attribute_matches_for?(item)
46
- @attribute_instruction
47
- .value_for(item: item)
48
- .public_send(
49
- @attribute_instruction.operator,
50
- @attribute_instruction.value
51
- )
52
- end
53
-
54
- # rubocop:disable Metrics/MethodLength
55
- def class_method_matches_for?(item)
56
- maybe_item_or_collection_or_nil = attribute_item_for(item)
57
- .class
58
- .public_send(
59
- @attribute_instruction.attribute,
60
- @attribute_instruction.value
61
- )
62
- if maybe_item_or_collection_or_nil.nil?
63
- false
64
- elsif maybe_item_or_collection_or_nil.respond_to?(:each)
65
- maybe_item_or_collection_or_nil.include? attribute_item_for(item)
66
- else
67
- maybe_item_or_collection_or_nil == attribute_item_for(item)
59
+ def filter_operation
60
+ @set.select do |item|
61
+ attribute_value_for(item)
62
+ .public_send(
63
+ operator,
64
+ attribute_value
65
+ )
68
66
  end
69
67
  end
70
- # rubocop:enable Metrics/MethodLength
71
68
 
72
- def attribute_item_for(item)
73
- @attribute_instruction
74
- .resource_for(item: item)
69
+ def intersect_operation
70
+ other_set = attribute_class
71
+ .public_send(
72
+ attribute,
73
+ attribute_value
74
+ )
75
+ @set & other_set
75
76
  end
76
77
  end
77
78
  end
@@ -1,22 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../active_record_set_instruction'
4
+
3
5
  class ActiveSet
4
6
  module Sorting
5
7
  class ActiveRecordStrategy
6
8
  def initialize(set, attribute_instructions)
7
9
  @set = set
8
10
  @attribute_instructions = attribute_instructions
11
+ @set_instructions = attribute_instructions.map do |attribute_instruction|
12
+ ActiveRecordSetInstruction.new(attribute_instruction, set)
13
+ end
9
14
  end
10
15
 
11
16
  def execute
12
17
  return false unless @set.respond_to? :to_sql
13
18
 
14
- executable_instructions.reduce(set_with_eager_loaded_associations) do |set, attribute_instruction|
15
- statement = set.merge(order_operation_for(attribute_instruction))
16
-
17
- return false if throws?(ActiveRecord::StatementInvalid) { statement.load }
19
+ executable_instructions.reduce(@set) do |set, set_instruction|
20
+ statement = set.merge(set_instruction.initial_relation)
21
+ statement = statement.merge(order_operation_for(set_instruction))
18
22
 
19
- attribute_instruction.processed = true
23
+ set_instruction.processed = true
20
24
  statement
21
25
  end
22
26
  end
@@ -24,11 +28,11 @@ class ActiveSet
24
28
  def executable_instructions
25
29
  return {} unless @set.respond_to? :to_sql
26
30
 
27
- @attribute_instructions.select do |attribute_instruction|
28
- attribute_model = attribute_model_for(attribute_instruction)
31
+ @set_instructions.select do |set_instruction|
32
+ attribute_model = set_instruction.attribute_model
29
33
  next false unless attribute_model
30
34
  next false unless attribute_model.respond_to?(:attribute_names)
31
- next false unless attribute_model.attribute_names.include?(attribute_instruction.attribute)
35
+ next false unless attribute_model.attribute_names.include?(set_instruction.attribute)
32
36
 
33
37
  true
34
38
  end
@@ -36,40 +40,20 @@ class ActiveSet
36
40
 
37
41
  private
38
42
 
39
- def set_with_eager_loaded_associations
40
- associations_hash = @attribute_instructions.reduce({}) { |h, i| h.merge(i.associations_hash) }
41
- @set.eager_load(associations_hash)
42
- end
43
-
44
43
  # https://stackoverflow.com/a/44912964/2884386
45
44
  # Force null values to be sorted as if larger than any non-null value
46
45
  # ASC => [-2, -1, 1, 2, nil]
47
46
  # DESC => [nil, 2, 1, -1, -2]
48
- def order_operation_for(attribute_instruction)
49
- attribute_model = attribute_model_for(attribute_instruction)
47
+ def order_operation_for(set_instruction)
48
+ attribute_model = set_instruction.attribute_model
50
49
 
51
- arel_column = Arel::Table.new(attribute_model.table_name)[attribute_instruction.attribute]
52
- arel_column = case_insensitive?(attribute_instruction) ? arel_column.lower : arel_column
53
- arel_direction = direction_operator(attribute_instruction.value)
50
+ arel_column = set_instruction.arel_column
51
+ arel_direction = direction_operator(set_instruction.value)
54
52
  nil_sorter = arel_column.send(arel_direction == :asc ? :eq : :not_eq, nil)
55
53
 
56
54
  attribute_model.order(nil_sorter).order(arel_column.send(arel_direction))
57
55
  end
58
56
 
59
- def attribute_model_for(attribute_instruction)
60
- return @set.klass if attribute_instruction.associations_array.empty?
61
-
62
- attribute_instruction
63
- .associations_array
64
- .reduce(@set) do |obj, assoc|
65
- obj.reflections[assoc.to_s]&.klass
66
- end
67
- end
68
-
69
- def case_insensitive?(attribute_instruction)
70
- attribute_instruction.operator.to_s.casecmp('i').zero?
71
- end
72
-
73
57
  def direction_operator(direction)
74
58
  return :desc if direction.to_s.downcase.start_with? 'desc'
75
59
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../../helpers/transform_to_sortable_numeric'
4
+ require_relative '../enumerable_set_instruction'
4
5
 
5
6
  class ActiveSet
6
7
  module Sorting
@@ -8,14 +9,17 @@ class ActiveSet
8
9
  def initialize(set, attribute_instructions)
9
10
  @set = set
10
11
  @attribute_instructions = attribute_instructions
12
+ @set_instructions = attribute_instructions.map do |attribute_instruction|
13
+ EnumerableSetInstruction.new(attribute_instruction, set)
14
+ end
11
15
  end
12
16
 
13
17
  def execute
14
18
  # http://brandon.dimcheff.com/2009/11/18/rubys-sort-vs-sort-by/
15
19
  @set.sort_by do |item|
16
- @attribute_instructions.map do |instruction|
17
- value_for_comparison = sortable_numeric_for(instruction, item)
18
- direction_multiplier = direction_multiplier(instruction.value)
20
+ @set_instructions.map do |set_instruction|
21
+ value_for_comparison = sortable_numeric_for(set_instruction, item)
22
+ direction_multiplier = direction_multiplier(set_instruction.value)
19
23
 
20
24
  # Force null values to be sorted as if larger than any non-null value
21
25
  # ASC => [-2, -1, 1, 2, nil]
@@ -29,23 +33,12 @@ class ActiveSet
29
33
  end
30
34
  end
31
35
 
32
- def sortable_numeric_for(instruction, item)
33
- value = instruction.value_for(item: item)
34
- if value.is_a?(String) || value.is_a?(Symbol)
35
- value = if case_insensitive?(instruction, value)
36
- value.to_s.downcase
37
- else
38
- value.to_s
39
- end
40
- end
36
+ def sortable_numeric_for(set_instruction, item)
37
+ value = set_instruction.attribute_value_for(item)
41
38
 
42
39
  transform_to_sortable_numeric(value)
43
40
  end
44
41
 
45
- def case_insensitive?(instruction, _value)
46
- instruction.operator.to_s.casecmp('i').zero?
47
- end
48
-
49
42
  def direction_multiplier(direction)
50
43
  return -1 if direction.to_s.downcase.start_with? 'desc'
51
44
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actionset
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Margheim
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-06 00:00:00.000000000 Z
11
+ date: 2019-05-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -280,8 +280,10 @@ files:
280
280
  - lib/action_set/helpers/sort/next_direction_for_helper.rb
281
281
  - lib/action_set/helpers/sort/path_for_helper.rb
282
282
  - lib/active_set.rb
283
+ - lib/active_set/active_record_set_instruction.rb
283
284
  - lib/active_set/attribute_instruction.rb
284
285
  - lib/active_set/column_instruction.rb
286
+ - lib/active_set/enumerable_set_instruction.rb
285
287
  - lib/active_set/exporting/csv_strategy.rb
286
288
  - lib/active_set/exporting/operation.rb
287
289
  - lib/active_set/filtering/active_record_strategy.rb