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