actionset 0.0.1 → 0.1.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/CHANGELOG +6 -0
- data/Gemfile +6 -0
- data/actionset.gemspec +12 -2
- data/config.ru +9 -0
- data/lib/action_set/instructions/entry_value.rb +142 -0
- data/lib/action_set/version.rb +1 -1
- data/lib/action_set/view_helpers.rb +39 -0
- data/lib/action_set.rb +60 -1
- metadata +147 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 702c887ea4be4ff151f1534bf9ff440843e018b7
|
4
|
+
data.tar.gz: 198cd7d817b312e55f8a1ac7cf162b8fa5551b27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 161afab0ca5e7277ce69adffc2ff593fe9f980418991f4b42514750c5eddc40eb09f57a5cc47d89c2a2a7e07125ab1d8619c6ab60c0a9665ebe5a08fe582f865
|
7
|
+
data.tar.gz: d90f23599901d6d9ab7c79633b16abb6efa5dcf03407509697383dfa84b0d6337f2bf130874ad4ce1319939e3d63051b79cdcbdc082efd49a6992e53900dc2d6
|
data/CHANGELOG
ADDED
data/Gemfile
CHANGED
data/actionset.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.name = 'actionset'
|
9
9
|
spec.version = ActionSet::VERSION
|
10
10
|
spec.authors = ['Stephen Margheim']
|
11
|
-
spec.email = ['margheim@
|
11
|
+
spec.email = ['stephen.margheim@gmail.com']
|
12
12
|
|
13
13
|
spec.summary = 'A toolkit to wire-up ActiveSet collections with a Rails controller.'
|
14
14
|
spec.description = 'Easily filter, sort, and paginate enumerable sets via web requests.'
|
@@ -22,9 +22,19 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
23
|
spec.require_paths = ['lib']
|
24
24
|
|
25
|
-
spec.add_dependency 'activeset'
|
25
|
+
spec.add_dependency 'activeset', '~> 0.4.0'
|
26
|
+
spec.add_dependency 'activesupport', '~> 5.0'
|
27
|
+
spec.add_dependency 'railties'
|
26
28
|
|
27
29
|
spec.add_development_dependency 'bundler', '~> 1.15'
|
28
30
|
spec.add_development_dependency 'rake', '~> 10.0'
|
29
31
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
32
|
+
spec.add_development_dependency 'rspec-rails', '~> 3.6'
|
33
|
+
spec.add_development_dependency 'database_cleaner', '~> 1.6.1'
|
34
|
+
spec.add_development_dependency 'capybara', '~> 2.15.1'
|
35
|
+
spec.add_development_dependency 'combustion', '~> 0.7.0'
|
36
|
+
spec.add_development_dependency 'factory_girl', '~> 4.8.0'
|
37
|
+
spec.add_development_dependency 'faker', '~> 1.8.4'
|
38
|
+
spec.add_development_dependency 'simplecov', '~> 0.15.0'
|
39
|
+
spec.add_development_dependency 'simplecov-console', '~> 0.4.2'
|
30
40
|
end
|
data/config.ru
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionSet
|
4
|
+
module Instructions
|
5
|
+
class EntryValue
|
6
|
+
attr_reader :raw
|
7
|
+
|
8
|
+
def initialize(value)
|
9
|
+
@raw = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def cast(to:)
|
13
|
+
adapters.map do |adapter|
|
14
|
+
value = adapter.new(@raw, to).process
|
15
|
+
return value unless value.nil?
|
16
|
+
end.compact.first || @raw
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def adapters
|
22
|
+
[ActiveModelAdapter, PlainRubyAdapter, BooleanAdapter, TimeWithZoneAdapter]
|
23
|
+
end
|
24
|
+
|
25
|
+
class PlainRubyAdapter
|
26
|
+
def initialize(raw, target)
|
27
|
+
@raw = raw
|
28
|
+
@target = target
|
29
|
+
end
|
30
|
+
|
31
|
+
def process
|
32
|
+
return @raw if @raw.is_a? @target
|
33
|
+
possible_values.find { |v| v.is_a? @target }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def possible_values
|
39
|
+
possible_typecasters.map { |m| typecast(m) }
|
40
|
+
.compact
|
41
|
+
end
|
42
|
+
|
43
|
+
def possible_typecasters
|
44
|
+
@possible_typecasters ||= String.instance_methods
|
45
|
+
.map(&:to_s)
|
46
|
+
.select { |m| m.starts_with? 'to_' }
|
47
|
+
.reject { |m| %[to_v8].include? m }
|
48
|
+
end
|
49
|
+
|
50
|
+
def typecast(method_name)
|
51
|
+
@raw.send(method_name)
|
52
|
+
rescue
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ActiveModelAdapter
|
58
|
+
require 'active_model/type'
|
59
|
+
|
60
|
+
def initialize(raw, target)
|
61
|
+
@raw = raw
|
62
|
+
@target = target
|
63
|
+
end
|
64
|
+
|
65
|
+
def process
|
66
|
+
return @raw if @raw.is_a? @target
|
67
|
+
possible_values.find { |v| v.is_a? @target }
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def possible_values
|
73
|
+
possible_typecasters.map { |m| typecast(m, @raw) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def possible_typecasters
|
77
|
+
@possible_typecasters ||= ActiveModel::Type.constants
|
78
|
+
.map(&:to_s)
|
79
|
+
.select { |t| can_typecast?(t) }
|
80
|
+
.map { |t| init_typecaster(t) }
|
81
|
+
.compact
|
82
|
+
end
|
83
|
+
|
84
|
+
def typecast(to_type, value)
|
85
|
+
return to_type.type_cast(value) if to_type.respond_to? :type_cast
|
86
|
+
to_type.cast(value)
|
87
|
+
end
|
88
|
+
|
89
|
+
def can_typecast?(const_name)
|
90
|
+
typecasting_class = ActiveModel::Type.const_get(const_name)
|
91
|
+
typecasting_class.instance_methods.include?(:cast) ||
|
92
|
+
typecasting_class.instance_methods.include?(:type_cast)
|
93
|
+
end
|
94
|
+
|
95
|
+
def init_typecaster(const_name)
|
96
|
+
ActiveModel::Type.const_get(const_name).new
|
97
|
+
rescue
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class BooleanAdapter
|
103
|
+
def initialize(raw, target)
|
104
|
+
@raw = raw
|
105
|
+
@target = target
|
106
|
+
end
|
107
|
+
|
108
|
+
def process
|
109
|
+
return if @raw.is_a? @target
|
110
|
+
return unless @target.eql?(TrueClass) || @target.eql?(FalseClass)
|
111
|
+
# ActiveModel::Type::Boolean is too expansive in its casting; will get false positives
|
112
|
+
to_bool
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def to_bool
|
118
|
+
return @raw if @raw.is_a?(TrueClass) || @raw.is_a?(FalseClass)
|
119
|
+
return true if %w[true yes 1 t].include? @raw.to_s.downcase
|
120
|
+
return false if %w[false no 0 f].include? @raw.to_s.downcase
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class TimeWithZoneAdapter
|
126
|
+
def initialize(raw, target)
|
127
|
+
@raw = raw
|
128
|
+
@target = target
|
129
|
+
end
|
130
|
+
|
131
|
+
def process
|
132
|
+
return if @raw.is_a? @target
|
133
|
+
return unless @target.eql?(ActiveSupport::TimeWithZone)
|
134
|
+
time_value = ActiveModelAdapter.new(@raw, Time).process
|
135
|
+
return unless time_value.is_a?(Time)
|
136
|
+
return time_value unless time_value.respond_to?(:in_time_zone)
|
137
|
+
time_value.in_time_zone
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/action_set/version.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionSet
|
4
|
+
module ViewHelpers
|
5
|
+
def sort_link(column, title = nil)
|
6
|
+
column = column.to_s
|
7
|
+
title ||= sort_link_title(column)
|
8
|
+
|
9
|
+
link_to title, sort_link_url(column)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def sort_link_url(column)
|
15
|
+
request.query_parameters.merge(controller: controller.controller_path,
|
16
|
+
action: controller.action_name,
|
17
|
+
sort: {
|
18
|
+
column => sort_link_direction(column)
|
19
|
+
})
|
20
|
+
end
|
21
|
+
|
22
|
+
def sort_link_title(column)
|
23
|
+
I18n.t(column,
|
24
|
+
scope: :attributes,
|
25
|
+
default: default_sort_link_title(column))
|
26
|
+
end
|
27
|
+
|
28
|
+
def sort_link_direction(column)
|
29
|
+
return 'asc' unless params[:sort].keys.include?(column.to_s)
|
30
|
+
|
31
|
+
sort_direction_inverses = { 'asc' => 'desc', 'desc' => 'asc' }
|
32
|
+
sort_direction_inverses[params.dig(:sort, column)]
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_sort_link_title(column)
|
36
|
+
ActiveSet::Instructions::Entry::KeyPath.new(column).titleized
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/action_set.rb
CHANGED
@@ -1,7 +1,66 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'rails/railtie'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
require 'active_set'
|
6
|
+
|
3
7
|
require 'action_set/version'
|
8
|
+
require_relative './action_set/instructions/entry_value'
|
9
|
+
require_relative './action_set/view_helpers'
|
4
10
|
|
5
11
|
module ActionSet
|
6
|
-
|
12
|
+
class Railtie < ::Rails::Railtie
|
13
|
+
initializer 'action_set.view_helpers' do
|
14
|
+
ActiveSupport.on_load :action_view do
|
15
|
+
include ViewHelpers
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Filter < OpenStruct
|
21
|
+
def model_name
|
22
|
+
OpenStruct.new(param_key: 'filter')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceMethods
|
30
|
+
def process_set(set)
|
31
|
+
@set = set
|
32
|
+
@filters = JSON.parse(filter_params.to_json,
|
33
|
+
object_class: Filter)
|
34
|
+
active_set = ActiveSet.new(set)
|
35
|
+
active_set = active_set.filter(filter_structure) if filter_params.any?
|
36
|
+
active_set = active_set.sort(sort_params) if sort_params.any?
|
37
|
+
active_set
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def filter_params
|
43
|
+
params.fetch(:filter, {}).to_unsafe_hash
|
44
|
+
end
|
45
|
+
|
46
|
+
def filter_structure
|
47
|
+
filter_params.flatten_keys.reject { |_, v| v.blank? }.each_with_object({}) do |(keypath, value), memo|
|
48
|
+
instruction = ActiveSet::Instructions::Entry.new(keypath, value)
|
49
|
+
item_with_value = @set.find { |i| !instruction.value_for(item: i).nil? }
|
50
|
+
item_value = instruction.value_for(item: item_with_value)
|
51
|
+
typecast_value = ActionSet::Instructions::EntryValue.new(value)
|
52
|
+
.cast(to: item_value.class)
|
53
|
+
memo[keypath] = typecast_value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def sort_params
|
58
|
+
params.fetch(:sort, {}).to_unsafe_hash
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.included(receiver)
|
63
|
+
receiver.extend ClassMethods
|
64
|
+
receiver.send :include, InstanceMethods
|
65
|
+
end
|
7
66
|
end
|
metadata
CHANGED
@@ -1,17 +1,45 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: actionset
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Margheim
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activeset
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.4.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.4.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: railties
|
15
43
|
requirement: !ruby/object:Gem::Requirement
|
16
44
|
requirements:
|
17
45
|
- - ">="
|
@@ -66,9 +94,121 @@ dependencies:
|
|
66
94
|
- - "~>"
|
67
95
|
- !ruby/object:Gem::Version
|
68
96
|
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec-rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.6'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.6'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: database_cleaner
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.6.1
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.6.1
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: capybara
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 2.15.1
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 2.15.1
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: combustion
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 0.7.0
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 0.7.0
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: factory_girl
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 4.8.0
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 4.8.0
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: faker
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 1.8.4
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: 1.8.4
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: simplecov
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 0.15.0
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - "~>"
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: 0.15.0
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: simplecov-console
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - "~>"
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: 0.4.2
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: 0.4.2
|
69
209
|
description: Easily filter, sort, and paginate enumerable sets via web requests.
|
70
210
|
email:
|
71
|
-
- margheim@
|
211
|
+
- stephen.margheim@gmail.com
|
72
212
|
executables: []
|
73
213
|
extensions: []
|
74
214
|
extra_rdoc_files: []
|
@@ -77,6 +217,7 @@ files:
|
|
77
217
|
- ".rspec"
|
78
218
|
- ".rubocop.yml"
|
79
219
|
- ".travis.yml"
|
220
|
+
- CHANGELOG
|
80
221
|
- Gemfile
|
81
222
|
- LICENSE.txt
|
82
223
|
- README.md
|
@@ -84,8 +225,11 @@ files:
|
|
84
225
|
- actionset.gemspec
|
85
226
|
- bin/console
|
86
227
|
- bin/setup
|
228
|
+
- config.ru
|
87
229
|
- lib/action_set.rb
|
230
|
+
- lib/action_set/instructions/entry_value.rb
|
88
231
|
- lib/action_set/version.rb
|
232
|
+
- lib/action_set/view_helpers.rb
|
89
233
|
homepage: https://github.com/fractaledmind/actionset
|
90
234
|
licenses:
|
91
235
|
- MIT
|