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 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