actionset 0.8.2 → 0.9.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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +24 -0
  3. data/Gemfile.lock +1 -1
  4. data/Rakefile +7 -1
  5. data/actionset.gemspec +1 -1
  6. data/lib/action_set.rb +36 -7
  7. data/lib/action_set/attribute_value.rb +7 -1
  8. data/lib/action_set/helpers/helper_methods.rb +2 -1
  9. data/lib/action_set/helpers/pagination/record_description_for_helper.rb +20 -0
  10. data/lib/action_set/helpers/pagination/record_first_for_helper.rb +20 -0
  11. data/lib/action_set/helpers/pagination/record_last_for_helper.rb +26 -0
  12. data/lib/action_set/helpers/pagination/record_range_for_helper.rb +25 -0
  13. data/lib/action_set/helpers/pagination/record_size_for_helper.rb +9 -0
  14. data/lib/action_set/helpers/pagination/total_pages_for_helper.rb +3 -1
  15. data/lib/active_set/active_record_set_instruction.rb +30 -37
  16. data/lib/active_set/attribute_instruction.rb +2 -2
  17. data/lib/active_set/enumerable_set_instruction.rb +13 -26
  18. data/lib/active_set/filtering/active_record/operators.rb +277 -0
  19. data/lib/active_set/filtering/active_record/query_column.rb +35 -0
  20. data/lib/active_set/filtering/active_record/query_value.rb +47 -0
  21. data/lib/active_set/filtering/active_record/set_instruction.rb +29 -0
  22. data/lib/active_set/filtering/active_record/strategy.rb +87 -0
  23. data/lib/active_set/filtering/constants.rb +349 -0
  24. data/lib/active_set/filtering/enumerable/operators.rb +308 -0
  25. data/lib/active_set/filtering/enumerable/set_instruction.rb +98 -0
  26. data/lib/active_set/filtering/enumerable/strategy.rb +90 -0
  27. data/lib/active_set/filtering/operation.rb +4 -4
  28. data/lib/helpers/flatten_keys_of.rb +1 -1
  29. metadata +16 -4
  30. data/lib/active_set/filtering/active_record_strategy.rb +0 -85
  31. data/lib/active_set/filtering/enumerable_strategy.rb +0 -79
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/array/wrap'
4
+ require_relative '../../enumerable_set_instruction'
5
+ require_relative './operators'
6
+
7
+ class ActiveSet
8
+ module Filtering
9
+ module Enumerable
10
+ class SetInstruction < EnumerableSetInstruction
11
+ def item_matches_query?(item)
12
+ return query_result_for(item, query_attribute_for(instruction_value)) unless operator_hash.key?(:reducer)
13
+
14
+ Array.wrap(instruction_value).public_send(operator_hash[:reducer]) do |value|
15
+ query_result_for(item, query_attribute_for(value))
16
+ end
17
+ end
18
+
19
+ def set_item
20
+ return @set_item if defined? @set_item
21
+
22
+ @set_item = @set.find(&:present?)
23
+ end
24
+
25
+ def attribute_instance
26
+ return set_item if @attribute_instruction.associations_array.empty?
27
+ return @attribute_model if defined? @attribute_model
28
+
29
+ @attribute_model = @attribute_instruction
30
+ .associations_array
31
+ .reduce(set_item) do |obj, assoc|
32
+ obj.public_send(assoc)
33
+ end
34
+ end
35
+
36
+ def attribute_class
37
+ attribute_instance&.class
38
+ end
39
+
40
+ private
41
+
42
+ def query_result_for(item, value)
43
+ result = if operator_method == :cover? && value.is_a?(Range)
44
+ value
45
+ .public_send(
46
+ operator_method,
47
+ object_attribute_for(item)
48
+ )
49
+ else
50
+ object_attribute_for(item)
51
+ .public_send(
52
+ operator_method,
53
+ value
54
+ )
55
+ end
56
+
57
+ return result unless operator_hash.key?(:result_transformer)
58
+
59
+ operator_hash[:result_transformer].call(result)
60
+ end
61
+
62
+ def object_attribute_for(item)
63
+ attribute = guarantee_attribute_type(attribute_value_for(item))
64
+ return attribute unless operator_hash.key?(:object_attribute_transformer)
65
+
66
+ operator_hash[:object_attribute_transformer].call(attribute)
67
+ end
68
+
69
+ def query_attribute_for(value)
70
+ attribute = guarantee_attribute_type(value)
71
+ return attribute unless operator_hash.key?(:query_attribute_transformer)
72
+
73
+ operator_hash[:query_attribute_transformer].call(attribute)
74
+ end
75
+
76
+ def operator_method
77
+ operator_hash.dig(:operator) || :'=='
78
+ end
79
+
80
+ def operator_hash
81
+ instruction_operator = @attribute_instruction.operator
82
+
83
+ Operators.get(instruction_operator)
84
+ end
85
+
86
+ def guarantee_attribute_type(attribute)
87
+ # Booleans don't respond to many operator methods,
88
+ # so we cast them to integers
89
+ return 1 if attribute == true
90
+ return 0 if attribute == false
91
+ return attribute.map { |a| guarantee_attribute_type(a) } if attribute.respond_to?(:each)
92
+
93
+ attribute
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './set_instruction'
4
+ require 'active_support/core_ext/module/delegation'
5
+
6
+ class ActiveSet
7
+ module Filtering
8
+ module Enumerable
9
+ class Strategy
10
+ delegate :attribute_instance,
11
+ :attribute_class,
12
+ :instruction_value,
13
+ :attribute_value_for,
14
+ :operator,
15
+ :attribute,
16
+ :set_item,
17
+ :resource_for,
18
+ to: :@set_instruction
19
+
20
+ def initialize(set, attribute_instruction)
21
+ @set = set
22
+ @attribute_instruction = attribute_instruction
23
+ @set_instruction = SetInstruction.new(attribute_instruction, set)
24
+ end
25
+
26
+ def execute
27
+ return false unless @set.respond_to? :select
28
+
29
+ if execute_filter_operation?
30
+ set = filter_operation
31
+ elsif execute_intersect_operation?
32
+ begin
33
+ set = intersect_operation
34
+ rescue TypeError # thrown if intersecting with a non-Array
35
+ return false
36
+ end
37
+ else
38
+ return false
39
+ end
40
+
41
+ set
42
+ end
43
+
44
+ private
45
+
46
+ def execute_filter_operation?
47
+ return false unless attribute_instance
48
+ return false unless attribute_instance.respond_to?(attribute)
49
+ return false if attribute_instance.method(attribute).arity.positive?
50
+
51
+ true
52
+ end
53
+
54
+ def execute_intersect_operation?
55
+ return false unless attribute_class
56
+ return false unless attribute_class.respond_to?(attribute)
57
+ return false if attribute_class.method(attribute).arity.zero?
58
+
59
+ true
60
+ end
61
+
62
+ def filter_operation
63
+ @set.select do |item|
64
+ @set_instruction.item_matches_query?(item)
65
+ end
66
+ end
67
+
68
+ def intersect_operation
69
+ @set & other_set
70
+ end
71
+
72
+ def other_set
73
+ other_set = attribute_class.public_send(
74
+ attribute,
75
+ instruction_value
76
+ )
77
+ if attribute_class != set_item.class
78
+ other_set = begin
79
+ @set.select { |item| resource_for(item: item)&.presence_in other_set }
80
+ rescue ArgumentError # thrown if other_set is doesn't respond to #include?, like when nil
81
+ nil
82
+ end
83
+ end
84
+
85
+ other_set
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../attribute_instruction'
4
- require_relative './enumerable_strategy'
5
- require_relative './active_record_strategy'
4
+ require_relative './enumerable/strategy'
5
+ require_relative './active_record/strategy'
6
6
 
7
7
  class ActiveSet
8
8
  module Filtering
@@ -17,7 +17,7 @@ class ActiveSet
17
17
  .map { |k, v| AttributeInstruction.new(k, v) }
18
18
 
19
19
  activerecord_filtered_set = attribute_instructions.reduce(@set) do |set, attribute_instruction|
20
- maybe_set_or_false = ActiveRecordStrategy.new(set, attribute_instruction).execute
20
+ maybe_set_or_false = ActiveRecord::Strategy.new(set, attribute_instruction).execute
21
21
  next set unless maybe_set_or_false
22
22
 
23
23
  attribute_instruction.processed = true
@@ -27,7 +27,7 @@ class ActiveSet
27
27
  return activerecord_filtered_set if attribute_instructions.all?(&:processed?)
28
28
 
29
29
  attribute_instructions.reject(&:processed?).reduce(activerecord_filtered_set) do |set, attribute_instruction|
30
- maybe_set_or_false = EnumerableStrategy.new(set, attribute_instruction).execute
30
+ maybe_set_or_false = Enumerable::Strategy.new(set, attribute_instruction).execute
31
31
  next set unless maybe_set_or_false
32
32
 
33
33
  attribute_instruction.processed = true
@@ -24,7 +24,7 @@ require 'active_support/core_ext/array/wrap'
24
24
  # => { "key"=>"value", "nested.key"=>"nested_value", "array.0"=>0, "array.1"=>1, "array.2"=>2 }
25
25
 
26
26
  # refactored from https://stackoverflow.com/a/23861946/2884386
27
- def flatten_keys_of(input, keys = [], output = {}, flattener: ->(*keys) { keys }, flatten_arrays: false)
27
+ def flatten_keys_of(input, keys = [], output = {}, flattener: ->(*k) { k }, flatten_arrays: false)
28
28
  if input.is_a?(Hash)
29
29
  input.each do |key, value|
30
30
  flatten_keys_of(
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.2
4
+ version: 0.9.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-06-15 00:00:00.000000000 Z
11
+ date: 2019-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -270,6 +270,11 @@ files:
270
270
  - lib/action_set/helpers/pagination/page_size_for_helper.rb
271
271
  - lib/action_set/helpers/pagination/path_for_helper.rb
272
272
  - lib/action_set/helpers/pagination/prev_page_link_for_helper.rb
273
+ - lib/action_set/helpers/pagination/record_description_for_helper.rb
274
+ - lib/action_set/helpers/pagination/record_first_for_helper.rb
275
+ - lib/action_set/helpers/pagination/record_last_for_helper.rb
276
+ - lib/action_set/helpers/pagination/record_range_for_helper.rb
277
+ - lib/action_set/helpers/pagination/record_size_for_helper.rb
273
278
  - lib/action_set/helpers/pagination/total_pages_for_helper.rb
274
279
  - lib/action_set/helpers/params/current_helper.rb
275
280
  - lib/action_set/helpers/params/form_for_object_helper.rb
@@ -286,8 +291,15 @@ files:
286
291
  - lib/active_set/enumerable_set_instruction.rb
287
292
  - lib/active_set/exporting/csv_strategy.rb
288
293
  - lib/active_set/exporting/operation.rb
289
- - lib/active_set/filtering/active_record_strategy.rb
290
- - lib/active_set/filtering/enumerable_strategy.rb
294
+ - lib/active_set/filtering/active_record/operators.rb
295
+ - lib/active_set/filtering/active_record/query_column.rb
296
+ - lib/active_set/filtering/active_record/query_value.rb
297
+ - lib/active_set/filtering/active_record/set_instruction.rb
298
+ - lib/active_set/filtering/active_record/strategy.rb
299
+ - lib/active_set/filtering/constants.rb
300
+ - lib/active_set/filtering/enumerable/operators.rb
301
+ - lib/active_set/filtering/enumerable/set_instruction.rb
302
+ - lib/active_set/filtering/enumerable/strategy.rb
291
303
  - lib/active_set/filtering/operation.rb
292
304
  - lib/active_set/paginating/active_record_strategy.rb
293
305
  - lib/active_set/paginating/enumerable_strategy.rb
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../active_record_set_instruction'
4
- require 'active_support/core_ext/module/delegation'
5
-
6
- class ActiveSet
7
- module Filtering
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
-
18
- def initialize(set, attribute_instruction)
19
- @set = set
20
- @attribute_instruction = attribute_instruction
21
- @set_instruction = ActiveRecordSetInstruction.new(attribute_instruction, set)
22
- end
23
-
24
- def execute
25
- return false unless @set.respond_to? :to_sql
26
-
27
- if execute_filter_operation?
28
- statement = filter_operation
29
- elsif execute_intersect_operation?
30
- begin
31
- statement = intersect_operation
32
- rescue ArgumentError # thrown if merging a non-ActiveRecord::Relation
33
- return false
34
- end
35
- else
36
- return false
37
- end
38
-
39
- statement
40
- end
41
-
42
- private
43
-
44
- def execute_filter_operation?
45
- return false unless attribute_model
46
- return false unless attribute_model.respond_to?(:attribute_names)
47
- return false unless attribute_model.attribute_names.include?(attribute)
48
-
49
- true
50
- end
51
-
52
- def execute_intersect_operation?
53
- return false unless attribute_model
54
- return false unless attribute_model.respond_to?(attribute)
55
- return false if attribute_model.method(attribute).arity.zero?
56
-
57
- true
58
- end
59
-
60
- def filter_operation
61
- initial_relation
62
- .where(
63
- arel_column.send(
64
- arel_operator,
65
- arel_value
66
- )
67
- )
68
- end
69
-
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>]
75
- initial_relation
76
- .merge(
77
- attribute_model.public_send(
78
- attribute,
79
- arel_value
80
- )
81
- )
82
- end
83
- end
84
- end
85
- end
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../enumerable_set_instruction'
4
- require 'active_support/core_ext/module/delegation'
5
-
6
- class ActiveSet
7
- module Filtering
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
-
17
- def initialize(set, attribute_instruction)
18
- @set = set
19
- @attribute_instruction = attribute_instruction
20
- @set_instruction = EnumerableSetInstruction.new(attribute_instruction, set)
21
- end
22
-
23
- def execute
24
- return false unless @set.respond_to? :select
25
-
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
36
- end
37
-
38
- set
39
- end
40
-
41
- private
42
-
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?
47
-
48
- true
49
- end
50
-
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?
55
-
56
- true
57
- end
58
-
59
- def filter_operation
60
- @set.select do |item|
61
- attribute_value_for(item)
62
- .public_send(
63
- operator,
64
- attribute_value
65
- )
66
- end
67
- end
68
-
69
- def intersect_operation
70
- other_set = attribute_class
71
- .public_send(
72
- attribute,
73
- attribute_value
74
- )
75
- @set & other_set
76
- end
77
- end
78
- end
79
- end