actionset 0.8.2 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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