subroutine 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/subroutine/op.rb +35 -61
- data/lib/subroutine/type_caster.rb +74 -105
- data/lib/subroutine/version.rb +2 -2
- data/test/support/ops.rb +0 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 388f424f1515f93ce602dd4b6578288733a0c7e8
|
4
|
+
data.tar.gz: 8514ac581bf6afa707d12801a45f017833cd3e2b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e008b4415a368b7df088f4049cf08714951fdce842c53c9ddd11c31401ded7e2cc42997325a62012f753adeca942765d86c0f33df6fa1fc4534471a9512b432a
|
7
|
+
data.tar.gz: cf77b24680e08978e7b927c7b4999da80b72bbcd9247811052ed4d7eca49ec32f79c7db3750159dcb3718d7cab9b8ac37b9ab7647f6e04e85d306ab98016a91b
|
data/lib/subroutine/op.rb
CHANGED
@@ -3,16 +3,14 @@ require 'active_support/core_ext/object/duplicable'
|
|
3
3
|
require 'active_support/core_ext/object/deep_dup'
|
4
4
|
require 'active_model'
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
6
|
+
require 'subroutine/failure'
|
7
|
+
require 'subroutine/type_caster'
|
8
|
+
require 'subroutine/filtered_errors'
|
9
|
+
require 'subroutine/output_not_set_error'
|
10
|
+
require 'subroutine/unknown_output_error'
|
11
11
|
|
12
12
|
module Subroutine
|
13
|
-
|
14
13
|
class Op
|
15
|
-
|
16
14
|
include ::ActiveModel::Model
|
17
15
|
include ::ActiveModel::Validations::Callbacks
|
18
16
|
|
@@ -21,9 +19,7 @@ module Subroutine
|
|
21
19
|
}.freeze
|
22
20
|
|
23
21
|
class << self
|
24
|
-
|
25
|
-
::Subroutine::TypeCaster::TYPES.values.flatten.each do |caster|
|
26
|
-
|
22
|
+
::Subroutine::TypeCaster.casters.each_key do |caster|
|
27
23
|
next if method_defined?(caster)
|
28
24
|
|
29
25
|
class_eval <<-EV, __FILE__, __LINE__ + 1
|
@@ -46,15 +42,12 @@ module Subroutine
|
|
46
42
|
_field(f, options)
|
47
43
|
end
|
48
44
|
end
|
49
|
-
|
50
|
-
alias_method :fields, :field
|
45
|
+
alias fields field
|
51
46
|
|
52
47
|
def outputs(*names)
|
53
48
|
options = names.extract_options!
|
54
49
|
names.each do |name|
|
55
|
-
self._outputs =
|
56
|
-
name.to_sym => DEFAULT_OUTPUT_OPTIONS.merge(options)
|
57
|
-
})
|
50
|
+
self._outputs = _outputs.merge(name.to_sym => DEFAULT_OUTPUT_OPTIONS.merge(options))
|
58
51
|
|
59
52
|
class_eval <<-EV, __FILE__, __LINE__ + 1
|
60
53
|
def #{name}
|
@@ -69,7 +62,7 @@ module Subroutine
|
|
69
62
|
_ignore_errors(f)
|
70
63
|
end
|
71
64
|
end
|
72
|
-
|
65
|
+
alias ignore_errors ignore_error
|
73
66
|
|
74
67
|
def inputs_from(*ops)
|
75
68
|
options = ops.extract_options!
|
@@ -98,7 +91,6 @@ module Subroutine
|
|
98
91
|
child._error_ignores = self._error_ignores.dup
|
99
92
|
end
|
100
93
|
|
101
|
-
|
102
94
|
def submit!(*args)
|
103
95
|
op = new(*args)
|
104
96
|
op.submit!
|
@@ -115,23 +107,21 @@ module Subroutine
|
|
115
107
|
protected
|
116
108
|
|
117
109
|
def _field(field_name, options = {})
|
118
|
-
|
110
|
+
_fields[field_name.to_sym] = options
|
119
111
|
|
120
112
|
if options[:aka]
|
121
113
|
Array(options[:aka]).each do |as|
|
122
|
-
|
114
|
+
_error_map[as.to_sym] = field_name.to_sym
|
123
115
|
end
|
124
116
|
end
|
125
117
|
|
126
|
-
if options[:ignore_errors]
|
127
|
-
_ignore_errors(field_name)
|
128
|
-
end
|
118
|
+
_ignore_errors(field_name) if options[:ignore_errors]
|
129
119
|
|
130
120
|
class_eval <<-EV, __FILE__, __LINE__ + 1
|
131
121
|
|
132
122
|
def #{field_name}=(v)
|
133
123
|
config = #{field_name}_config
|
134
|
-
v =
|
124
|
+
v = ::Subroutine::TypeCaster.cast(v, config[:type])
|
135
125
|
@params["#{field_name}"] = v
|
136
126
|
end
|
137
127
|
|
@@ -140,7 +130,7 @@ module Subroutine
|
|
140
130
|
end
|
141
131
|
|
142
132
|
def #{field_name}_config
|
143
|
-
|
133
|
+
_fields[:#{field_name}]
|
144
134
|
end
|
145
135
|
|
146
136
|
EV
|
@@ -148,7 +138,7 @@ module Subroutine
|
|
148
138
|
end
|
149
139
|
|
150
140
|
def _ignore_errors(field_name)
|
151
|
-
|
141
|
+
_error_ignores[field_name.to_sym] = true
|
152
142
|
end
|
153
143
|
|
154
144
|
end
|
@@ -234,10 +224,6 @@ module Subroutine
|
|
234
224
|
|
235
225
|
protected
|
236
226
|
|
237
|
-
def type_caster
|
238
|
-
@type_caster ||= ::Subroutine::TypeCaster.new
|
239
|
-
end
|
240
|
-
|
241
227
|
# these enable you to 1) add log output or 2) add performance monitoring such as skylight.
|
242
228
|
def observe_submission
|
243
229
|
yield
|
@@ -251,17 +237,11 @@ module Subroutine
|
|
251
237
|
yield
|
252
238
|
end
|
253
239
|
|
254
|
-
|
255
240
|
def validate_and_perform
|
256
|
-
bool = observe_validation
|
257
|
-
valid?
|
258
|
-
end
|
259
|
-
|
241
|
+
bool = observe_validation{ valid? }
|
260
242
|
return false unless bool
|
261
243
|
|
262
|
-
observe_perform
|
263
|
-
perform
|
264
|
-
end
|
244
|
+
observe_perform{ perform }
|
265
245
|
end
|
266
246
|
|
267
247
|
# implement this in your concrete class.
|
@@ -271,7 +251,7 @@ module Subroutine
|
|
271
251
|
|
272
252
|
# check if a specific field was provided
|
273
253
|
def field_provided?(key)
|
274
|
-
@params.
|
254
|
+
@params.key?(key)
|
275
255
|
end
|
276
256
|
|
277
257
|
# applies the errors in error_object to self
|
@@ -281,30 +261,27 @@ module Subroutine
|
|
281
261
|
|
282
262
|
error_object.each do |k,v|
|
283
263
|
|
284
|
-
next if
|
264
|
+
next if _error_ignores[k.to_sym]
|
285
265
|
|
286
|
-
if respond_to?(
|
266
|
+
if respond_to?(k)
|
287
267
|
errors.add(k, v)
|
288
|
-
elsif
|
289
|
-
errors.add(
|
268
|
+
elsif _error_map[k.to_sym]
|
269
|
+
errors.add(_error_map[k.to_sym], v)
|
290
270
|
else
|
291
271
|
errors.add(:base, error_object.full_message(k,v))
|
292
272
|
end
|
293
|
-
|
294
273
|
end
|
295
274
|
|
296
275
|
false
|
297
276
|
end
|
298
277
|
|
299
|
-
|
300
278
|
# if you want to use strong parameters or something in your form object you can do so here.
|
301
279
|
# by default we just slice the inputs to the defined fields
|
302
280
|
def sanitize_params(inputs)
|
303
281
|
out = {}.with_indifferent_access
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
end
|
282
|
+
_fields.each_pair do |field, config|
|
283
|
+
next unless inputs.key?(field)
|
284
|
+
out[field] = ::Subroutine::TypeCaster.cast(inputs[field], config[:type])
|
308
285
|
end
|
309
286
|
|
310
287
|
out
|
@@ -313,23 +290,20 @@ module Subroutine
|
|
313
290
|
def sanitize_defaults
|
314
291
|
defaults = {}.with_indifferent_access
|
315
292
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
end
|
326
|
-
defaults[field] = type_caster.cast(deflt, config[:type])
|
293
|
+
_fields.each_pair do |field, config|
|
294
|
+
next if config[:default].nil?
|
295
|
+
deflt = config[:default]
|
296
|
+
if deflt.respond_to?(:call)
|
297
|
+
deflt = deflt.call
|
298
|
+
elsif deflt.duplicable? # from active_support
|
299
|
+
# Some classes of default values need to be duplicated, or the instance field value will end up referencing
|
300
|
+
# the class global default value, and potentially modify it.
|
301
|
+
deflt = deflt.deep_dup # from active_support
|
327
302
|
end
|
303
|
+
defaults[field] = ::Subroutine::TypeCaster.cast(deflt, config[:type])
|
328
304
|
end
|
329
305
|
|
330
306
|
defaults
|
331
307
|
end
|
332
|
-
|
333
308
|
end
|
334
|
-
|
335
309
|
end
|
@@ -6,128 +6,97 @@ require 'active_support/core_ext/object/try'
|
|
6
6
|
require 'active_support/core_ext/array/wrap'
|
7
7
|
|
8
8
|
module Subroutine
|
9
|
-
|
9
|
+
module TypeCaster
|
10
10
|
|
11
|
+
def casters
|
12
|
+
@casters ||= {}
|
13
|
+
end
|
14
|
+
module_function :casters
|
11
15
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
:decimal => [:decimal, :big_decimal],
|
16
|
-
:string => [:string, :text],
|
17
|
-
:boolean => [:bool, :boolean],
|
18
|
-
:iso_date => [:iso_date],
|
19
|
-
:iso_time => [:iso_time],
|
20
|
-
:date => [:date],
|
21
|
-
:time => [:time, :timestamp],
|
22
|
-
:hash => [:object, :hashmap, :dict],
|
23
|
-
:array => [:array]
|
24
|
-
}
|
25
|
-
|
26
|
-
|
27
|
-
def cast(value, type)
|
28
|
-
return value if value.nil? || type.nil?
|
29
|
-
|
30
|
-
case type.to_sym
|
31
|
-
when *TYPES[:integer]
|
32
|
-
cast_number(value, :to_i)
|
33
|
-
when *TYPES[:number]
|
34
|
-
cast_number(value, :to_f)
|
35
|
-
when *TYPES[:decimal]
|
36
|
-
cast_number(value, :to_d, :to_f)
|
37
|
-
when *TYPES[:string]
|
38
|
-
cast_string(value)
|
39
|
-
when *TYPES[:boolean]
|
40
|
-
cast_boolean(value)
|
41
|
-
when *TYPES[:iso_date]
|
42
|
-
t = cast_iso_time(value)
|
43
|
-
t ? t.split('T')[0] : t
|
44
|
-
when *TYPES[:date]
|
45
|
-
cast_date(value).try(:to_date)
|
46
|
-
when *TYPES[:iso_time]
|
47
|
-
cast_iso_time(value)
|
48
|
-
when *TYPES[:time]
|
49
|
-
cast_time(value)
|
50
|
-
when *TYPES[:hash]
|
51
|
-
cast_hash(value)
|
52
|
-
when *TYPES[:array]
|
53
|
-
cast_array(value)
|
54
|
-
else
|
55
|
-
value
|
16
|
+
def register(*names, &block)
|
17
|
+
names.each do |n|
|
18
|
+
casters[n] = block
|
56
19
|
end
|
57
20
|
end
|
21
|
+
module_function :register
|
58
22
|
|
59
|
-
|
60
|
-
|
61
|
-
def cast_number(value, *meths)
|
62
|
-
return nil if value.blank?
|
63
|
-
meth = meths.detect{|m| value.respond_to?(m) }
|
64
|
-
meth ? value.send(meth) : value
|
65
|
-
end
|
23
|
+
def cast(value, type, *args)
|
24
|
+
return value if value.nil? || type.nil?
|
66
25
|
|
67
|
-
|
68
|
-
|
26
|
+
caster = casters[type]
|
27
|
+
caster ? caster.call(value, *args) : value
|
69
28
|
end
|
29
|
+
module_function :cast
|
70
30
|
|
71
|
-
|
72
|
-
|
73
|
-
end
|
31
|
+
end
|
32
|
+
end
|
74
33
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
34
|
+
Subroutine::TypeCaster.register :number do |value, *meths|
|
35
|
+
next nil if value.blank?
|
36
|
+
meth = meths.detect{|m| value.respond_to?(m) }
|
37
|
+
meth ? value.send(meth) : value.to_f
|
38
|
+
end
|
79
39
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
40
|
+
Subroutine::TypeCaster.register :integer, :int, :epoch do |value|
|
41
|
+
Subroutine::TypeCaster.cast(value, :number, :to_i)
|
42
|
+
end
|
84
43
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
t ||= value if value.is_a?(::Time)
|
89
|
-
t ||= value if value.try(:acts_like?, :time)
|
90
|
-
t ||= ::Time.parse(cast_string(value))
|
91
|
-
t.utc.iso8601
|
92
|
-
end
|
44
|
+
Subroutine::TypeCaster.register :decimal, :big_decimal do |value|
|
45
|
+
Subroutine::TypeCaster.cast(value, :number, :to_d, :to_f)
|
46
|
+
end
|
93
47
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
d ||= value if value.is_a?(::Date)
|
98
|
-
d ||= value if value.try(:acts_like?, :date)
|
99
|
-
d ||= ::Date.parse(cast_string(value))
|
100
|
-
d.iso8601
|
101
|
-
end
|
48
|
+
Subroutine::TypeCaster.register :string, :text do |value|
|
49
|
+
String(value)
|
50
|
+
end
|
102
51
|
|
103
|
-
|
104
|
-
|
105
|
-
|
52
|
+
Subroutine::TypeCaster.register :boolean, :bool do |value|
|
53
|
+
!!(String(value) =~ /^(yes|true|1|ok)$/)
|
54
|
+
end
|
106
55
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
end
|
56
|
+
Subroutine::TypeCaster.register :iso_date do |value|
|
57
|
+
next nil unless value.present?
|
58
|
+
d = nil
|
59
|
+
d ||= value if value.is_a?(::Date)
|
60
|
+
d ||= value if value.try(:acts_like?, :date)
|
61
|
+
d ||= ::Date.parse(String(value))
|
62
|
+
d.iso8601
|
63
|
+
end
|
116
64
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
65
|
+
Subroutine::TypeCaster.register :iso_time do |value|
|
66
|
+
next nil unless value.present?
|
67
|
+
t = nil
|
68
|
+
t ||= value if value.is_a?(::Time)
|
69
|
+
t ||= value if value.try(:acts_like?, :time)
|
70
|
+
t ||= ::Time.parse(String(value))
|
71
|
+
t.utc.iso8601
|
72
|
+
end
|
122
73
|
|
123
|
-
|
124
|
-
|
125
|
-
|
74
|
+
Subroutine::TypeCaster.register :date do |value|
|
75
|
+
next nil unless value.present?
|
76
|
+
::Date.parse(String(value))
|
77
|
+
end
|
126
78
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
79
|
+
Subroutine::TypeCaster.register :time, :timestamp, :datetime do |value|
|
80
|
+
next nil unless value.present?
|
81
|
+
::Time.parse(String(value))
|
82
|
+
end
|
131
83
|
|
84
|
+
Subroutine::TypeCaster.register :hash, :object, :hashmap, :dict do |value|
|
85
|
+
if value.class.name == 'ActionController::Parameters'
|
86
|
+
value = value.to_hash
|
87
|
+
value.each_pair { |k, v| value[k] = Subroutine::TypeCaster.cast(v, :hash) if v.class.name == 'ActionController::Parameters' }
|
88
|
+
next value
|
132
89
|
end
|
90
|
+
|
91
|
+
next value if value.is_a?(Hash)
|
92
|
+
next {} if value.blank?
|
93
|
+
next value.to_hash if value.respond_to?(:to_hash)
|
94
|
+
next value.to_h if value.respond_to?(:to_h)
|
95
|
+
next ::Hash[value.to_a] if value.respond_to?(:to_a)
|
96
|
+
{}
|
97
|
+
end
|
98
|
+
|
99
|
+
Subroutine::TypeCaster.register :array do |value|
|
100
|
+
next [] if value.blank?
|
101
|
+
::Array.wrap(value)
|
133
102
|
end
|
data/lib/subroutine/version.rb
CHANGED
data/test/support/ops.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: subroutine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Nelson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|