actionset 0.8.1 → 0.11.0

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