actionset 0.8.1 → 0.11.0

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -5
  3. data/.ruby-version +1 -1
  4. data/.travis.yml +1 -1
  5. data/CHANGELOG +67 -0
  6. data/Gemfile.lock +134 -126
  7. data/README.md +26 -0
  8. data/Rakefile +8 -1
  9. data/actionset.gemspec +2 -2
  10. data/lib/action_set.rb +8 -23
  11. data/lib/action_set/attribute_value.rb +7 -1
  12. data/lib/action_set/filter_instructions.rb +72 -0
  13. data/lib/action_set/helpers/helper_methods.rb +2 -1
  14. data/lib/action_set/helpers/pagination/record_description_for_helper.rb +20 -0
  15. data/lib/action_set/helpers/pagination/record_first_for_helper.rb +20 -0
  16. data/lib/action_set/helpers/pagination/record_last_for_helper.rb +26 -0
  17. data/lib/action_set/helpers/pagination/record_range_for_helper.rb +25 -0
  18. data/lib/action_set/helpers/pagination/record_size_for_helper.rb +9 -0
  19. data/lib/action_set/helpers/pagination/total_pages_for_helper.rb +3 -1
  20. data/lib/action_set/sort_instructions.rb +44 -0
  21. data/lib/active_set.rb +25 -2
  22. data/lib/active_set/active_record_set_instruction.rb +33 -32
  23. data/lib/active_set/attribute_instruction.rb +3 -3
  24. data/lib/active_set/enumerable_set_instruction.rb +13 -24
  25. data/lib/active_set/filtering/active_record/operators.rb +280 -0
  26. data/lib/active_set/filtering/active_record/query_column.rb +35 -0
  27. data/lib/active_set/filtering/active_record/query_value.rb +47 -0
  28. data/lib/active_set/filtering/active_record/set_instruction.rb +29 -0
  29. data/lib/active_set/filtering/active_record/strategy.rb +87 -0
  30. data/lib/active_set/filtering/constants.rb +349 -0
  31. data/lib/active_set/filtering/enumerable/operators.rb +308 -0
  32. data/lib/active_set/filtering/enumerable/set_instruction.rb +98 -0
  33. data/lib/active_set/filtering/enumerable/strategy.rb +90 -0
  34. data/lib/active_set/filtering/operation.rb +5 -8
  35. data/lib/active_set/paginating/active_record_strategy.rb +0 -2
  36. data/lib/active_set/sorting/active_record_strategy.rb +27 -3
  37. data/lib/active_set/sorting/enumerable_strategy.rb +12 -2
  38. data/lib/active_set/sorting/operation.rb +1 -2
  39. data/lib/helpers/flatten_keys_of.rb +53 -0
  40. data/lib/helpers/transform_to_sortable_numeric.rb +3 -3
  41. metadata +26 -13
  42. data/lib/active_set/filtering/active_record_strategy.rb +0 -85
  43. data/lib/active_set/filtering/enumerable_strategy.rb +0 -79
  44. data/lib/helpers/throws.rb +0 -19
  45. data/lib/patches/core_ext/hash/flatten_keys.rb +0 -58
data/README.md CHANGED
@@ -108,6 +108,32 @@ For pagination, like filtering, we don't enforce any view-layer specifics. You s
108
108
  </nav>
109
109
  ```
110
110
 
111
+ ## Configuration
112
+
113
+ ### ActiveSet.configuration.on_asc_sort_nils_come
114
+
115
+ Example usage in an initializer:
116
+
117
+ ```
118
+ ActiveSet.configure do |c|
119
+ c.on_asc_sort_nils_come = :first
120
+ end
121
+ ```
122
+
123
+ When `ActiveSet.configuration.on_asc_sort_nils_come == :last` (this is the default), null values will be sorted as if larger than any non-null value.
124
+
125
+ ```
126
+ ASC => [-2, -1, 1, 2, nil]
127
+ DESC => [nil, 2, 1, -1, -2]
128
+ ```
129
+
130
+ Otherwise sort nulls as if smaller than any non-null value.
131
+
132
+ ```
133
+ ASC => [nil, -2, -1, 1, 2]
134
+ DESC => [2, 1, -1, -2, nil]
135
+ ```
136
+
111
137
  ## Development
112
138
 
113
139
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/Rakefile CHANGED
@@ -5,4 +5,11 @@ 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
+ ENV['LOGICALLY_EXHAUSTIVE_REQUEST_SPECS'] = 'true'
12
+ Rake::Task['spec'].invoke
13
+ end
14
+
15
+ task default: :full_spec
data/actionset.gemspec CHANGED
@@ -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.1'
9
+ spec.version = '0.11.0'
10
10
  spec.authors = ['Stephen Margheim']
11
11
  spec.email = ['stephen.margheim@gmail.com']
12
12
 
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
32
32
  spec.add_development_dependency 'database_cleaner'
33
33
  spec.add_development_dependency 'factory_bot'
34
34
  spec.add_development_dependency 'faker'
35
- spec.add_development_dependency 'rails', '~> 5.1.0'
35
+ spec.add_development_dependency 'rails', '~> 5.2.0'
36
36
  spec.add_development_dependency 'rake'
37
37
  spec.add_development_dependency 'ransack'
38
38
  spec.add_development_dependency 'rspec'
data/lib/action_set.rb CHANGED
@@ -5,7 +5,8 @@ require 'active_support/core_ext/object/blank'
5
5
  require 'active_support/lazy_load_hooks'
6
6
  require 'active_set'
7
7
 
8
- require_relative './action_set/attribute_value'
8
+ require_relative './action_set/filter_instructions'
9
+ require_relative './action_set/sort_instructions'
9
10
  require_relative './action_set/helpers/helper_methods'
10
11
 
11
12
  module ActionSet
@@ -24,13 +25,13 @@ module ActionSet
24
25
 
25
26
  def filter_set(set)
26
27
  active_set = ensure_active_set(set)
27
- active_set = active_set.filter(filter_instructions_for(set)) if filter_params.any?
28
+ active_set = active_set.filter(FilterInstructions.new(filter_params, set, self).get) if filter_params.any?
28
29
  active_set
29
30
  end
30
31
 
31
32
  def sort_set(set)
32
33
  active_set = ensure_active_set(set)
33
- active_set = active_set.sort(sort_params) if sort_params.any?
34
+ active_set = active_set.sort(SortInstructions.new(sort_params, set, self).get) if sort_params.any?
34
35
  active_set
35
36
  end
36
37
 
@@ -52,30 +53,11 @@ module ActionSet
52
53
 
53
54
  private
54
55
 
55
- def filter_instructions_for(set)
56
- filter_params.flatten_keys.reject { |_, v| v.try(:empty?) }.each_with_object({}) do |(keypath, value), memo|
57
- typecast_value = if value.respond_to?(:each)
58
- value.map { |v| filter_typecasted_value_for(keypath, v, set) }
59
- else
60
- filter_typecasted_value_for(keypath, value, set)
61
- end
62
-
63
- memo[keypath] = typecast_value
64
- end
65
- end
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)
73
- end
74
-
75
56
  def paginate_instructions
76
57
  paginate_params.transform_values(&:to_i)
77
58
  end
78
59
 
60
+ # rubocop:disable Metrics/AbcSize
79
61
  def export_instructions
80
62
  {}.tap do |struct|
81
63
  struct[:format] = export_params[:format] || request.format.symbol
@@ -93,10 +75,13 @@ module ActionSet
93
75
  elsif respond_to?(:export_set_columns, true)
94
76
  send(:export_set_columns)
95
77
  else
78
+ # :nocov:
96
79
  [{}]
80
+ # :nocov:
97
81
  end
98
82
  end
99
83
  end
84
+ # rubocop:enable Metrics/AbcSize
100
85
 
101
86
  def filter_params
102
87
  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
 
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './attribute_value'
4
+
5
+ module ActionSet
6
+ class FilterInstructions
7
+ def initialize(params, set, controller)
8
+ @params = params
9
+ @set = set
10
+ @controller = controller
11
+ end
12
+
13
+ def get
14
+ instructions_hash = if form_friendly_complex_params?
15
+ form_friendly_complex_params_to_hash
16
+ elsif form_friendly_simple_params?
17
+ form_friendly_simple_params_to_hash
18
+ else
19
+ @params
20
+ end
21
+ flattened_instructions = flatten_keys_of(instructions_hash).reject { |_, v| v.try(:empty?) }
22
+ flattened_instructions.each_with_object({}) do |(keypath, value), memo|
23
+ memo[keypath] = if value.respond_to?(:map)
24
+ value.map { |v| AttributeValue.new(v).cast(to: klass_for_keypath(keypath, v, @set)) }
25
+ else
26
+ AttributeValue.new(value).cast(to: klass_for_keypath(keypath, value, @set))
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def form_friendly_complex_params?
34
+ @params.key?(:'0')
35
+ end
36
+
37
+ def form_friendly_simple_params?
38
+ @params.key?(:attribute) &&
39
+ @params.key?(:operator) &&
40
+ @params.key?(:query)
41
+ end
42
+
43
+ def form_friendly_complex_params_to_hash
44
+ ordered_instructions = @params.sort_by(&:first)
45
+ array_of_instructions = ordered_instructions.map { |_, h| ["#{h[:attribute]}(#{h[:operator]})", h[:query]] }
46
+ Hash[array_of_instructions]
47
+ end
48
+
49
+ def form_friendly_simple_params_to_hash
50
+ { "#{@params[:attribute]}(#{@params[:operator]})" => @params[:query] }
51
+ end
52
+
53
+ def klass_for_keypath(keypath, value, set)
54
+ if @controller.respond_to?(:filter_set_types, true)
55
+ type_declarations = @controller.public_send(:filter_set_types)
56
+ types = type_declarations['types'] || type_declarations[:types]
57
+ klass = types[keypath.join('.')]
58
+ return klass if klass
59
+ end
60
+
61
+ if set.is_a?(ActiveRecord::Relation) || set.view.is_a?(ActiveRecord::Relation)
62
+ klass_type = set.model.columns_hash.fetch(keypath, nil)&.type
63
+ return klass_type.class if klass_type
64
+ end
65
+
66
+ instruction = ActiveSet::AttributeInstruction.new(keypath, value)
67
+ item_with_value = set.find { |i| !instruction.value_for(item: i).nil? }
68
+ item_value = instruction.value_for(item: item_with_value)
69
+ item_value.class
70
+ end
71
+ end
72
+ end
@@ -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
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionSet
4
+ class SortInstructions
5
+ def initialize(params, set, controller)
6
+ @params = params
7
+ @set = set
8
+ @controller = controller
9
+ end
10
+
11
+ def get
12
+ instructions_hash = if form_friendly_complex_params?
13
+ form_friendly_complex_params_to_hash
14
+ elsif form_friendly_simple_params?
15
+ form_friendly_simple_params_to_hash
16
+ else
17
+ @params
18
+ end
19
+
20
+ instructions_hash.transform_values { |v| v.remove('ending') }
21
+ end
22
+
23
+ private
24
+
25
+ def form_friendly_complex_params?
26
+ @params.key?(:'0')
27
+ end
28
+
29
+ def form_friendly_simple_params?
30
+ @params.key?(:attribute) &&
31
+ @params.key?(:direction)
32
+ end
33
+
34
+ def form_friendly_complex_params_to_hash
35
+ ordered_instructions = @params.sort_by(&:first)
36
+ array_of_instructions = ordered_instructions.map { |_, h| [h[:attribute], h[:direction]] }
37
+ Hash[array_of_instructions]
38
+ end
39
+
40
+ def form_friendly_simple_params_to_hash
41
+ { @params[:attribute] => @params[:direction] }
42
+ end
43
+ end
44
+ end
data/lib/active_set.rb CHANGED
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/core_ext/hash/reverse_merge'
4
- require 'patches/core_ext/hash/flatten_keys'
5
- require 'helpers/throws'
4
+ require 'helpers/flatten_keys_of'
6
5
  require 'active_set/attribute_instruction'
7
6
  require 'active_set/filtering/operation'
8
7
  require 'active_set/sorting/operation'
@@ -12,6 +11,30 @@ require 'active_set/exporting/operation'
12
11
  class ActiveSet
13
12
  include Enumerable
14
13
 
14
+ class Configuration
15
+ attr_accessor :on_asc_sort_nils_come
16
+
17
+ def initialize
18
+ @on_asc_sort_nils_come = :last
19
+ end
20
+ end
21
+
22
+ class << self
23
+ attr_writer :configuration
24
+ end
25
+
26
+ def self.configuration
27
+ @configuration ||= Configuration.new
28
+ end
29
+
30
+ def self.configure
31
+ yield(configuration)
32
+ end
33
+
34
+ def self.reset_configuration
35
+ @configuration = Configuration.new
36
+ end
37
+
15
38
  attr_reader :set, :view, :instructions
16
39
 
17
40
  def initialize(set, view: nil, instructions: {})