actionset 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|