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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dffd9096dbb67844e4cee728dee2dbea434a32e2
4
- data.tar.gz: a6bcb0b49df525a8d3e6e74f06a067310674fd87
3
+ metadata.gz: 1eb88ba89dc96b42af254762a42011c32153b788
4
+ data.tar.gz: 174a5c1c743fff20cf343ca82abe3461a040d342
5
5
  SHA512:
6
- metadata.gz: f7400ae1649bde246c247752feabb37adc3c290ce464aee8271980c265965be920ac2cdae6c06b009fbbc59df371ec7e1aad075f4035a2e624ead3f6254e0011
7
- data.tar.gz: ca373cfc982b4455ec40c5df823c94ccc4a16e0c2fc1fafeb2eb3cc9ea65b8be664df7135d6ef52c9e1b5cca12e959ae23eda6815809cb62e93baae0acec5d96
6
+ metadata.gz: 5c39ddbc29032e67bb292ce87c79734a399dab2cf4467aa0e63168036a794908b840cc544394e24eb6da3c238293cded4a18b867591a0cfbef7e72b812990404
7
+ data.tar.gz: e982edf3262db42fcb15d871047a33d79a35344841e8a218d6b485d10315fc2cdf3b52d8f63303f9def653220a52ff318b05e69c94f9e8bf352dc59492ffb301
data/CHANGELOG CHANGED
@@ -1,3 +1,27 @@
1
+ v 0.9.1
2
+ - ensure that our RubyGems build is stable, since we had a TravisCI build problem in the last version
3
+ v 0.9.0
4
+ - Add Enumerable support for the base filtering operations
5
+ + EQ, NOT_EQ, EQ_ANY, EQ_ALL, NOT_EQ_ANY, NOT_EQ_ALL
6
+ + IN, NOT_IN, IN_ANY, IN_ALL, NOT_IN_ANY, NOT_IN_ALL
7
+ + MATCHES, DOES_NOT_MATCH, MATCHES_ANY, MATCHES_ALL, DOES_NOT_MATCH_ANY, DOES_NOT_MATCH_ALL
8
+ + LT, LTEQ, LT_ANY, LT_ALL, LTEQ_ANY, LTEQ_ALL
9
+ + GT, GTEQ, GT_ANY, GT_ALL, GTEQ_ANY, GTEQ_ALL
10
+ + BETWEEN, NOT_BETWEEN
11
+ - Add unary predicate filtering operators, for both ActiveRecord and Enumerable
12
+ + IS_TRUE, IS_FALSE
13
+ + IS_NULL, NOT_NULL
14
+ + IS_PRESENT, IS_BLANK
15
+ - Add computed matcher filtering operators, for both ActiveRecord and Enumerable
16
+ + MATCH_START, MATCH_START_ANY, MATCH_START_ALL, MATCH_NOT_START, MATCH_NOT_START_ANY, MATCH_NOT_START_ALL
17
+ + MATCH_END, MATCH_END_ANY, MATCH_END_ALL, MATCH_NOT_END, MATCH_NOT_END_ANY, MATCH_NOT_END_ALL
18
+ + MATCH_CONTAIN, MATCH_CONTAIN_ANY, MATCH_CONTAIN_ALL, MATCH_NOT_CONTAIN, MATCH_NOT_CONTAIN_ANY, MATCH_NOT_CONTAIN_ALL
19
+ - Add view helpers for the range of records shown on the current page
20
+ - Use ActiveRecord's data dictionary to look up database column types when converting filter params to filter instructions
21
+ - Allow app to define type hints for filter attributes when converting filter params to filter instructions
22
+ - Allow sort params to be passed in short or long form
23
+ + e.g. { attribute: x, direction: x } or { attribute: direction }
24
+ - Fix enumerable intersection filtering when working across associations
1
25
  v 0.8.2
2
26
  - add `ActiveSet.configuration.on_asc_sort_nils_come` configuration
3
27
  v 0.8.1
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- actionset (0.8.2)
4
+ actionset (0.9.1)
5
5
  activesupport (>= 4.0.2)
6
6
  railties
7
7
 
data/Rakefile CHANGED
@@ -5,4 +5,10 @@ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- task default: :spec
8
+ task :full_spec do
9
+ ENV['COVERAGE'] = 'true'
10
+ ENV['INSPECT_FAILURE'] = 'true'
11
+ Rake::Task['spec'].invoke
12
+ end
13
+
14
+ task default: :full_spec
@@ -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.2'
9
+ spec.version = '0.9.1'
10
10
  spec.authors = ['Stephen Margheim']
11
11
  spec.email = ['stephen.margheim@gmail.com']
12
12
 
@@ -30,7 +30,7 @@ module ActionSet
30
30
 
31
31
  def sort_set(set)
32
32
  active_set = ensure_active_set(set)
33
- active_set = active_set.sort(sort_params) if sort_params.any?
33
+ active_set = active_set.sort(sort_instructions) if sort_params.any?
34
34
  active_set
35
35
  end
36
36
 
@@ -64,18 +64,19 @@ module ActionSet
64
64
  end
65
65
  end
66
66
 
67
- def filter_typecasted_value_for(keypath, value, set)
68
- instruction = ActiveSet::AttributeInstruction.new(keypath, value)
69
- item_with_value = set.find { |i| !instruction.value_for(item: i).nil? }
70
- item_value = instruction.value_for(item: item_with_value)
71
- ActionSet::AttributeValue.new(value)
72
- .cast(to: item_value.class)
67
+ def sort_instructions
68
+ if sort_params.key?(:attribute) && sort_params.key?(:direction)
69
+ { sort_params[:attribute] => sort_params[:direction] }
70
+ else
71
+ sort_params.transform_values { |v| v.remove('ending') }
72
+ end
73
73
  end
74
74
 
75
75
  def paginate_instructions
76
76
  paginate_params.transform_values(&:to_i)
77
77
  end
78
78
 
79
+ # rubocop:disable Metrics/AbcSize
79
80
  def export_instructions
80
81
  {}.tap do |struct|
81
82
  struct[:format] = export_params[:format] || request.format.symbol
@@ -93,10 +94,38 @@ module ActionSet
93
94
  elsif respond_to?(:export_set_columns, true)
94
95
  send(:export_set_columns)
95
96
  else
97
+ # :nocov:
96
98
  [{}]
99
+ # :nocov:
97
100
  end
98
101
  end
99
102
  end
103
+ # rubocop:enable Metrics/AbcSize
104
+
105
+ def filter_typecasted_value_for(keypath, value, set)
106
+ klass = klass_for_keypath(keypath, value, set)
107
+ ActionSet::AttributeValue.new(value)
108
+ .cast(to: klass)
109
+ end
110
+
111
+ def klass_for_keypath(keypath, value, set)
112
+ if respond_to?(:filter_set_types, true)
113
+ type_declarations = public_send(:filter_set_types)
114
+ types = type_declarations['types'] || type_declarations[:types]
115
+ klass = types[keypath.join('.')]
116
+ return klass if klass
117
+ end
118
+
119
+ if set.is_a?(ActiveRecord::Relation) || set.view.is_a?(ActiveRecord::Relation)
120
+ klass_type = set.model.columns_hash.fetch(keypath, nil)&.type
121
+ return klass_type.class if klass_type
122
+ end
123
+
124
+ instruction = ActiveSet::AttributeInstruction.new(keypath, value)
125
+ item_with_value = set.find { |i| !instruction.value_for(item: i).nil? }
126
+ item_value = instruction.value_for(item: item_with_value)
127
+ item_value.class
128
+ end
100
129
 
101
130
  def filter_params
102
131
  params.fetch(:filter, {}).to_unsafe_hash
@@ -11,7 +11,7 @@ module ActionSet
11
11
  def cast(to:)
12
12
  adapters.reduce(nil) do |_, adapter|
13
13
  mayble_value_or_nil = adapter.new(@raw, to).process
14
- next if mayble_value_or_nil.nil?
14
+ next nil if mayble_value_or_nil.nil?
15
15
 
16
16
  return mayble_value_or_nil
17
17
  end
@@ -62,7 +62,9 @@ module ActionSet
62
62
  begin
63
63
  require 'active_model/type'
64
64
  rescue LoadError
65
+ # :nocov:
65
66
  require 'active_record/type'
67
+ # :nocov:
66
68
  end
67
69
 
68
70
  def initialize(raw, target)
@@ -106,13 +108,17 @@ module ActionSet
106
108
  def init_typecaster(const_name)
107
109
  type_class.const_get(const_name).new
108
110
  rescue StandardError
111
+ # :nocov:
109
112
  nil
113
+ # :nocov:
110
114
  end
111
115
 
112
116
  def type_class
113
117
  ActiveModel::Type
114
118
  rescue NameError
119
+ # :nocov:
115
120
  ActiveRecord::Type
121
+ # :nocov:
116
122
  end
117
123
  end
118
124
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative './sort/link_for_helper'
4
4
  require_relative './pagination/links_for_helper'
5
- require_relative './pagination/path_for_helper'
5
+ require_relative './pagination/record_description_for_helper'
6
6
  require_relative './params/form_for_object_helper'
7
7
  require_relative './export/path_for_helper'
8
8
 
@@ -11,6 +11,7 @@ module ActionSet
11
11
  module HelperMethods
12
12
  include Sort::LinkForHelper
13
13
  include Pagination::LinksForHelper
14
+ include Pagination::RecordDescriptionForHelper
14
15
  include Params::FormForObjectHelper
15
16
  include Export::PathForHelper
16
17
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './record_range_for_helper'
4
+ require_relative './record_size_for_helper'
5
+
6
+ module Pagination
7
+ module RecordDescriptionForHelper
8
+ include Pagination::RecordRangeForHelper
9
+ include Pagination::RecordSizeForHelper
10
+
11
+ def pagination_record_description_for(set)
12
+ [
13
+ pagination_record_range_for(set),
14
+ 'of',
15
+ "<strong>#{pagination_record_size_for(set)}</strong>",
16
+ 'records'
17
+ ].join('&nbsp;').html_safe
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './current_page_for_helper'
4
+ require_relative './page_size_for_helper'
5
+
6
+ module Pagination
7
+ module RecordFirstForHelper
8
+ include Pagination::CurrentPageForHelper
9
+ include Pagination::PageSizeForHelper
10
+
11
+ def pagination_record_first_for(set)
12
+ current_page = pagination_current_page_for(set)
13
+ page_size = pagination_page_size_for(set)
14
+
15
+ return 1 if current_page == 1
16
+
17
+ ((current_page - 1) * page_size) + 1
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './current_page_for_helper'
4
+ require_relative './record_size_for_helper'
5
+ require_relative './page_size_for_helper'
6
+ require_relative './total_pages_for_helper'
7
+
8
+ module Pagination
9
+ module RecordLastForHelper
10
+ include Pagination::RecordSizeForHelper
11
+ include Pagination::CurrentPageForHelper
12
+ include Pagination::PageSizeForHelper
13
+ include Pagination::TotalPagesForHelper
14
+
15
+ def pagination_record_last_for(set)
16
+ record_size = pagination_record_size_for(set)
17
+ current_page = pagination_current_page_for(set)
18
+ page_size = pagination_page_size_for(set)
19
+ total_pages = pagination_total_pages_for(set)
20
+
21
+ return record_size if current_page >= total_pages
22
+
23
+ current_page * page_size
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './record_first_for_helper'
4
+ require_relative './record_last_for_helper'
5
+
6
+ module Pagination
7
+ module RecordRangeForHelper
8
+ include Pagination::CurrentPageForHelper
9
+ include Pagination::TotalPagesForHelper
10
+ include Pagination::RecordFirstForHelper
11
+ include Pagination::RecordLastForHelper
12
+
13
+ def pagination_record_range_for(set)
14
+ current_page = pagination_current_page_for(set)
15
+ total_pages = pagination_total_pages_for(set)
16
+ return 'None' if current_page > total_pages
17
+
18
+ [
19
+ pagination_record_first_for(set),
20
+ '&ndash;',
21
+ pagination_record_last_for(set)
22
+ ].join('&nbsp;').html_safe
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pagination
4
+ module RecordSizeForHelper
5
+ def pagination_record_size_for(set)
6
+ set.instructions.dig(:paginate, :count)
7
+ end
8
+ end
9
+ end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './record_size_for_helper'
3
4
  require_relative './page_size_for_helper'
4
5
 
5
6
  module Pagination
6
7
  module TotalPagesForHelper
8
+ include Pagination::RecordSizeForHelper
7
9
  include Pagination::PageSizeForHelper
8
10
 
9
11
  def pagination_total_pages_for(set)
10
- total_set_size = set.instructions.dig(:paginate, :count)
12
+ total_set_size = pagination_record_size_for(set)
11
13
  return 1 if total_set_size.zero?
12
14
 
13
15
  (total_set_size.to_f / pagination_page_size_for(set)).ceil
@@ -9,15 +9,41 @@ class ActiveSet
9
9
  end
10
10
 
11
11
  def initial_relation
12
- return @set if @attribute_instruction.associations_array.empty?
12
+ return @initial_relation if defined? @initial_relation
13
13
 
14
- @set.eager_load(@attribute_instruction.associations_hash)
14
+ @initial_relation = if @attribute_instruction.associations_array.empty?
15
+ @set
16
+ else
17
+ @set.eager_load(@attribute_instruction.associations_hash)
18
+ end
15
19
  end
16
20
 
21
+ def arel_column
22
+ return @arel_column if defined? @arel_column
23
+
24
+ arel_column = arel_table[@attribute_instruction.attribute]
25
+ arel_column = arel_column.lower if case_insensitive_operation?
26
+
27
+ @arel_column = arel_column
28
+ end
29
+
30
+ def attribute_model
31
+ return @set.klass if @attribute_instruction.associations_array.empty?
32
+ return @attribute_model if defined? @attribute_model
33
+
34
+ @attribute_model = @attribute_instruction
35
+ .associations_array
36
+ .reduce(@set) do |obj, assoc|
37
+ obj.reflections[assoc.to_s]&.klass
38
+ end
39
+ end
40
+
41
+ private
42
+
17
43
  def arel_type
18
44
  attribute_model
19
- .columns_hash[@attribute_instruction.attribute]
20
- .type
45
+ &.columns_hash[@attribute_instruction.attribute]
46
+ &.type
21
47
  end
22
48
 
23
49
  def arel_table
@@ -31,41 +57,8 @@ class ActiveSet
31
57
  end
32
58
  end
33
59
 
34
- # rubocop:disable Lint/UnderscorePrefixedVariableName
35
- def arel_column
36
- _arel_column = arel_table[@attribute_instruction.attribute]
37
- return _arel_column.lower if case_insensitive_operation?
38
-
39
- _arel_column
40
- end
41
- # rubocop:enable Lint/UnderscorePrefixedVariableName
42
-
43
- def arel_operator
44
- @attribute_instruction.operator(default: :eq)
45
- end
46
-
47
- # rubocop:disable Lint/UnderscorePrefixedVariableName
48
- def arel_value
49
- _arel_value = @attribute_instruction.value
50
- return _arel_value.downcase if case_insensitive_operation?
51
-
52
- _arel_value
53
- end
54
- # rubocop:enable Lint/UnderscorePrefixedVariableName
55
-
56
60
  def case_insensitive_operation?
57
61
  @attribute_instruction.case_insensitive? && arel_type.presence_in(%i[string text])
58
62
  end
59
-
60
- def attribute_model
61
- return @set.klass if @attribute_instruction.associations_array.empty?
62
- return @attribute_model if defined? @attribute_model
63
-
64
- @attribute_model = @attribute_instruction
65
- .associations_array
66
- .reduce(@set) do |obj, assoc|
67
- obj.reflections[assoc.to_s]&.klass
68
- end
69
- end
70
63
  end
71
64
  end
@@ -32,11 +32,11 @@ class ActiveSet
32
32
  @attribute = attribute
33
33
  end
34
34
 
35
- def operator(default: '==')
35
+ def operator
36
36
  return @operator if defined? @operator
37
37
 
38
38
  attribute_instruction = @keypath.last
39
- @operator = (attribute_instruction[operator_regex, 1] || default).to_sym
39
+ @operator = attribute_instruction[operator_regex, 1]&.to_sym
40
40
  end
41
41
 
42
42
  def options
@@ -9,40 +9,27 @@ class ActiveSet
9
9
  end
10
10
 
11
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
12
+ @item_values ||= Hash.new do |h, key|
13
+ item_value = @attribute_instruction.value_for(item: key)
14
+ item_value = item_value.downcase if case_insensitive_operation_for?(item_value)
15
+ h[key] = item_value
16
+ end
17
+
18
+ @item_values[item]
16
19
  end
17
20
 
18
- # rubocop:disable Lint/UnderscorePrefixedVariableName
19
- def attribute_value
20
- _attribute_value = @attribute_instruction.value
21
- _attribute_value = _attribute_value.downcase if case_insensitive_operation_for?(_attribute_value)
22
- _attribute_value
21
+ def instruction_value
22
+ return @instruction_value if defined? @instruction_value
23
+
24
+ instruction_value = @attribute_instruction.value
25
+ instruction_value = instruction_value.downcase if case_insensitive_operation_for?(instruction_value)
26
+ @instruction_value = instruction_value
23
27
  end
24
- # rubocop:enable Lint/UnderscorePrefixedVariableName
25
28
 
26
29
  def case_insensitive_operation_for?(value)
27
30
  return false unless @attribute_instruction.case_insensitive?
28
31
 
29
32
  value.is_a?(String) || value.is_a?(Symbol)
30
33
  end
31
-
32
- def attribute_instance
33
- set_item = @set.find(&:present?)
34
- return set_item if @attribute_instruction.associations_array.empty?
35
- return @attribute_model if defined? @attribute_model
36
-
37
- @attribute_model = @attribute_instruction
38
- .associations_array
39
- .reduce(set_item) do |obj, assoc|
40
- obj.public_send(assoc)
41
- end
42
- end
43
-
44
- def attribute_class
45
- attribute_instance&.class
46
- end
47
34
  end
48
35
  end