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.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -5
- data/.ruby-version +1 -1
- data/.travis.yml +1 -1
- data/CHANGELOG +67 -0
- data/Gemfile.lock +134 -126
- data/README.md +26 -0
- data/Rakefile +8 -1
- data/actionset.gemspec +2 -2
- data/lib/action_set.rb +8 -23
- data/lib/action_set/attribute_value.rb +7 -1
- data/lib/action_set/filter_instructions.rb +72 -0
- data/lib/action_set/helpers/helper_methods.rb +2 -1
- data/lib/action_set/helpers/pagination/record_description_for_helper.rb +20 -0
- data/lib/action_set/helpers/pagination/record_first_for_helper.rb +20 -0
- data/lib/action_set/helpers/pagination/record_last_for_helper.rb +26 -0
- data/lib/action_set/helpers/pagination/record_range_for_helper.rb +25 -0
- data/lib/action_set/helpers/pagination/record_size_for_helper.rb +9 -0
- data/lib/action_set/helpers/pagination/total_pages_for_helper.rb +3 -1
- data/lib/action_set/sort_instructions.rb +44 -0
- data/lib/active_set.rb +25 -2
- data/lib/active_set/active_record_set_instruction.rb +33 -32
- data/lib/active_set/attribute_instruction.rb +3 -3
- data/lib/active_set/enumerable_set_instruction.rb +13 -24
- data/lib/active_set/filtering/active_record/operators.rb +280 -0
- data/lib/active_set/filtering/active_record/query_column.rb +35 -0
- data/lib/active_set/filtering/active_record/query_value.rb +47 -0
- data/lib/active_set/filtering/active_record/set_instruction.rb +29 -0
- data/lib/active_set/filtering/active_record/strategy.rb +87 -0
- data/lib/active_set/filtering/constants.rb +349 -0
- data/lib/active_set/filtering/enumerable/operators.rb +308 -0
- data/lib/active_set/filtering/enumerable/set_instruction.rb +98 -0
- data/lib/active_set/filtering/enumerable/strategy.rb +90 -0
- data/lib/active_set/filtering/operation.rb +5 -8
- data/lib/active_set/paginating/active_record_strategy.rb +0 -2
- data/lib/active_set/sorting/active_record_strategy.rb +27 -3
- data/lib/active_set/sorting/enumerable_strategy.rb +12 -2
- data/lib/active_set/sorting/operation.rb +1 -2
- data/lib/helpers/flatten_keys_of.rb +53 -0
- data/lib/helpers/transform_to_sortable_numeric.rb +3 -3
- metadata +26 -13
- data/lib/active_set/filtering/active_record_strategy.rb +0 -85
- data/lib/active_set/filtering/enumerable_strategy.rb +0 -79
- data/lib/helpers/throws.rb +0 -19
- 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
|
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.
|
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.
|
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/
|
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(
|
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/
|
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(' ').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
|
+
'–',
|
21
|
+
pagination_record_last_for(set)
|
22
|
+
].join(' ').html_safe
|
23
|
+
end
|
24
|
+
end
|
25
|
+
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
|
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 '
|
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: {})
|