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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 73b9e07ebc32c17686183161804a2a77bd2ec042
4
- data.tar.gz: 1f6bd75368db46b4a756e9bfa50c9801f9cd0f51
3
+ metadata.gz: 702c887ea4be4ff151f1534bf9ff440843e018b7
4
+ data.tar.gz: 198cd7d817b312e55f8a1ac7cf162b8fa5551b27
5
5
  SHA512:
6
- metadata.gz: 791855b210100bffcfd01295aee91dd6c72c34bfd3e33eb0461252d2efe58b79b9ac32a3387fa7831a6048ffb0c5e4cbc0650601670fb33d4a73af51c65230bf
7
- data.tar.gz: c8ce69c65608139e3f9cb36883a5e279baf4fa70795c896ddcbaeac14c326fac0ff213cd56203c24e3e327f32ff5b899f0d7a4528eff971ab786c1c8ae7aa523
6
+ metadata.gz: 161afab0ca5e7277ce69adffc2ff593fe9f980418991f4b42514750c5eddc40eb09f57a5cc47d89c2a2a7e07125ab1d8619c6ab60c0a9665ebe5a08fe582f865
7
+ data.tar.gz: d90f23599901d6d9ab7c79633b16abb6efa5dcf03407509697383dfa84b0d6337f2bf130874ad4ce1319939e3d63051b79cdcbdc082efd49a6992e53900dc2d6
data/CHANGELOG ADDED
@@ -0,0 +1,6 @@
1
+ v 0.1.0
2
+ - make ActionSet a Railtie
3
+ - add a view helper method for building sort links (`sort_link`)
4
+ - typecast all params values before passing to ActiveSet
5
+ - reject any blank param values
6
+ - instantiate a `@filters` ivar for use in building the filters form
data/Gemfile CHANGED
@@ -6,3 +6,9 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
6
 
7
7
  # Specify your gem's dependencies in actionset.gemspec
8
8
  gemspec
9
+
10
+ group :test do
11
+ gem 'activerecord'
12
+ gem 'rspec-rails'
13
+ gem 'sqlite3'
14
+ end
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@mail.med.upenn.edu']
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,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+
6
+ Bundler.require :default, :development
7
+
8
+ Combustion.initialize! :all
9
+ run Combustion::Application
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionSet
4
- VERSION = '0.0.1'
4
+ VERSION = '0.1.0'
5
5
  end
@@ -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
- # Your code goes here...
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.1
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-09-15 00:00:00.000000000 Z
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@mail.med.upenn.edu
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