actionset 0.8.0 → 0.8.1

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
  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