subroutine 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: ab045b5fde4a639699ed15240b6549dd909454da
4
- data.tar.gz: 56ac432123c26d31a5265c304fce0c6268dab892
3
+ metadata.gz: 55319d241f3bd11613cff48ead9bae0355dfe8fa
4
+ data.tar.gz: 147c53874dfa950fb987b31b4b9742f0f3a7e7f1
5
5
  SHA512:
6
- metadata.gz: bebe659c3d84427adbacbe3bc0bffecfcfef2155cd6c4497a530f820d0198c43e2f899933b32c82ec21f678d2d021f1a0fd4f50524ef1faf9793247185f2253a
7
- data.tar.gz: 6e561078a1f08f1159f25ee590ad5cdeff1d185aba711f36ec0ef4ac0263f8ae79715e557d84b7ee3ebe318cc74f93de65676ac82986913d11fb738a9092113d
6
+ metadata.gz: 697e9e97d7161ac52db1c226ffa7f4e665d90d3c4f1b1fe58c1ae6e6d98555ae1a82538985b6b85c8690a4eef7385e74530068f054417cd34c45ff40a9a4f46c
7
+ data.tar.gz: 72303c67318d9aba48903ed4cf6ea9595adf22004f5af775f59a1df9d0082d08af1ed6954cd19b165d288669e58802b3ab1a391c30c0f36272d0e50e55e08d0f
data/.gitignore CHANGED
@@ -12,3 +12,4 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
+ *.gem
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Subroutine
2
2
 
3
- A gem that provides an interface for creating feature-driven operations. It loosly implements the command pattern if you're interested in nerding out a bit. See the examples below, it'll be more clear.
3
+ A gem that provides an interface for creating feature-driven operations. It loosely implements the command pattern if you're interested in nerding out a bit. See the examples below, it'll be more clear.
4
4
 
5
5
  ## Examples
6
6
 
@@ -114,7 +114,7 @@ end
114
114
 
115
115
  ## Usage
116
116
 
117
- Both the `Subroutine::Op` class and it's instances provide `submit` and `submit!` methods with identical signatures. Here are ways to invoke an op:
117
+ The `Subroutine::Op` class' `submit` and `submit!` methods have the same signature as the class' constructor, enabling a few different ways to utilize an op. Here they are:
118
118
 
119
119
  #### Via the class' `submit` method
120
120
 
@@ -0,0 +1,12 @@
1
+ module Subroutine
2
+
3
+ class Failure < StandardError
4
+ attr_reader :record
5
+ def initialize(record)
6
+ @record = record
7
+ errors = @record.errors.full_messages.join(", ")
8
+ super(errors)
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,222 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+ require 'active_model'
3
+
4
+ require "subroutine/failure"
5
+ require "subroutine/type_caster"
6
+
7
+ module Subroutine
8
+
9
+ class Op
10
+
11
+ include ::ActiveModel::Model
12
+ include ::ActiveModel::Validations::Callbacks
13
+
14
+ class << self
15
+
16
+ ::Subroutine::TypeCaster::TYPES.values.flatten.each do |caster|
17
+
18
+ next if method_defined?(caster)
19
+
20
+ class_eval <<-EV, __FILE__, __LINE__ + 1
21
+ def #{caster}(*args)
22
+ options = args.extract_options!
23
+ options[:type] = #{caster.inspect}
24
+ args.push(options)
25
+ field(*args)
26
+ end
27
+ EV
28
+ end
29
+
30
+ # fields can be provided in the following way:
31
+ # field :field1, :field2
32
+ # field :field3, :field4, default: 'my default'
33
+ def field(*fields)
34
+ options = fields.extract_options!
35
+
36
+ fields.each do |f|
37
+ _field(f, options)
38
+ end
39
+ end
40
+
41
+ alias_method :fields, :field
42
+
43
+
44
+ def inputs_from(*ops)
45
+ ops.each do |op|
46
+ op._fields.each_pair do |field_name, options|
47
+ field(field_name, options)
48
+ end
49
+ end
50
+ end
51
+
52
+ def inherited(child)
53
+ super
54
+ child._fields = self._fields.dup
55
+ end
56
+
57
+
58
+ def submit!(*args)
59
+ op = new(*args)
60
+ op.submit!
61
+
62
+ op
63
+ end
64
+
65
+ def submit(*args)
66
+ op = new(*args)
67
+ op.submit
68
+ op
69
+ end
70
+
71
+ protected
72
+
73
+ def _field(field_name, options = {})
74
+ self._fields[field_name.to_sym] = options
75
+
76
+ class_eval <<-EV, __FILE__, __LINE__ + 1
77
+
78
+ def #{field_name}=(v)
79
+ config = #{field_name}_config
80
+ @#{field_name} = type_caster.cast(v, config[:type])
81
+ end
82
+
83
+ def #{field_name}
84
+ return @#{field_name} if defined?(@#{field_name})
85
+ config = #{field_name}_config
86
+ deflt = config[:default]
87
+ deflt = deflt.call if deflt.respond_to?(:call)
88
+ type_caster.cast(deflt, config[:type])
89
+ end
90
+
91
+ def #{field_name}_config
92
+ self._fields[:#{field_name}]
93
+ end
94
+
95
+ EV
96
+
97
+ end
98
+
99
+ end
100
+
101
+
102
+ class_attribute :_fields
103
+ self._fields = {}
104
+
105
+ attr_reader :original_params
106
+ attr_reader :params
107
+
108
+
109
+ def initialize(inputs = {})
110
+ @original_params = inputs.with_indifferent_access
111
+ @params = {}
112
+ end
113
+
114
+
115
+ def submit!
116
+ unless submit
117
+ raise ::Subroutine::Failure.new(self)
118
+ end
119
+ true
120
+ end
121
+
122
+ # the action which should be invoked upon form submission (from the controller)
123
+ def submit
124
+ observe_submission do
125
+ @params = filter_params(@original_params)
126
+
127
+ set_accessors(@params)
128
+
129
+ validate_and_perform
130
+ end
131
+
132
+ rescue Exception => e
133
+
134
+ if e.respond_to?(:record)
135
+ inherit_errors_from(e.record) unless e.record == self
136
+ false
137
+ else
138
+ raise e
139
+ end
140
+ end
141
+
142
+ protected
143
+
144
+ def type_caster
145
+ @type_caster ||= ::Subroutine::TypeCaster.new
146
+ end
147
+
148
+ # these enable you to 1) add log output or 2) add performance monitoring such as skylight.
149
+ def observe_submission
150
+ yield
151
+ end
152
+
153
+ def observe_validation
154
+ yield
155
+ end
156
+
157
+ def observe_perform
158
+ yield
159
+ end
160
+
161
+
162
+ def validate_and_perform
163
+ bool = observe_validation do
164
+ valid?
165
+ end
166
+
167
+ return false unless bool
168
+
169
+ observe_perform do
170
+ perform
171
+ end
172
+ end
173
+
174
+ # implement this in your concrete class.
175
+ def perform
176
+ raise NotImplementedError
177
+ end
178
+
179
+ # check if a specific field was provided
180
+ def field_provided?(key)
181
+ @params.has_key?(key)
182
+ end
183
+
184
+ # applies the errors to the form object from the child object
185
+ def inherit_errors_from(object)
186
+ inherit_errors(object.errors)
187
+ end
188
+
189
+
190
+ # applies the errors in error_object to self
191
+ # returns false so failure cases can end with this invocation
192
+ def inherit_errors(error_object)
193
+ error_object.each do |k,v|
194
+
195
+ if respond_to?("#{k}")
196
+ errors.add(k, v)
197
+ else
198
+ errors.add(:base, error_object.full_message(k,v))
199
+ end
200
+
201
+ end
202
+
203
+ false
204
+ end
205
+
206
+
207
+ # if you want to use strong parameters or something in your form object you can do so here.
208
+ # by default we just slice the inputs to the defined fields
209
+ def filter_params(inputs)
210
+ inputs.slice(*_fields.keys)
211
+ end
212
+
213
+
214
+ def set_accessors(inputs)
215
+ inputs.each do |key, value|
216
+ send("#{key}=", value) if respond_to?("#{key}=")
217
+ end
218
+ end
219
+
220
+ end
221
+
222
+ end
@@ -0,0 +1,117 @@
1
+ require 'date'
2
+ require 'time'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/object/try'
5
+ require 'active_support/core_ext/array/wrap'
6
+
7
+ module Subroutine
8
+ class TypeCaster
9
+
10
+
11
+ TYPES = {
12
+ :integer => [:int, :integer, :epoch],
13
+ :number => [:number, :float, :decimal],
14
+ :string => [:string, :text],
15
+ :boolean => [:bool, :boolean],
16
+ :iso_date => [:iso_date],
17
+ :iso_time => [:iso_time],
18
+ :date => [:date],
19
+ :time => [:time, :timestamp],
20
+ :hash => [:object, :hashmap, :dict],
21
+ :array => [:array]
22
+ }
23
+
24
+
25
+ def cast(value, type)
26
+ return value if value.nil? || type.nil?
27
+
28
+ case type.to_sym
29
+ when *TYPES[:integer]
30
+ cast_number(value).try(:to_i)
31
+ when *TYPES[:number]
32
+ cast_number(value)
33
+ when *TYPES[:string]
34
+ cast_string(value)
35
+ when *TYPES[:boolean]
36
+ cast_boolean(value)
37
+ when *TYPES[:iso_date]
38
+ t = cast_iso_time(value)
39
+ t ? t.split('T')[0] : t
40
+ when *TYPES[:date]
41
+ cast_date(value).try(:to_date)
42
+ when *TYPES[:iso_time]
43
+ cast_iso_time(value)
44
+ when *TYPES[:time]
45
+ cast_time(value)
46
+ when *TYPES[:hash]
47
+ cast_hash(value)
48
+ when *TYPES[:array]
49
+ cast_array(value)
50
+ else
51
+ value
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def cast_number(value)
58
+ val = cast_string(value).strip
59
+ return nil if val.blank?
60
+ val.to_f
61
+ end
62
+
63
+ def cast_string(value)
64
+ String(value)
65
+ end
66
+
67
+ def cast_boolean(value)
68
+ !!(cast_string(value) =~ /^(yes|true|1|ok)$/)
69
+ end
70
+
71
+ def cast_time(value)
72
+ return nil unless value.present?
73
+ ::Time.parse(cast_string(value))
74
+ end
75
+
76
+ def cast_date(value)
77
+ return nil unless value.present?
78
+ ::Date.parse(cast_string(value))
79
+ end
80
+
81
+ def cast_iso_time(value)
82
+ return nil unless value.present?
83
+ t = nil
84
+ t ||= value if value.is_a?(::Time)
85
+ t ||= value if value.try(:acts_like?, :time)
86
+ t ||= ::Time.parse(cast_string(value))
87
+ t.utc.iso8601
88
+ end
89
+
90
+ def cast_iso_date(value)
91
+ return nil unless value.present?
92
+ d = nil
93
+ d ||= value if value.is_a?(::Date)
94
+ d ||= value if value.try(:acts_like?, :date)
95
+ d ||= ::Date.parse(cast_string(value))
96
+ d.iso8601
97
+ end
98
+
99
+ def cast_hash(value)
100
+ _cast_hash(value).try(:stringify_keys)
101
+ end
102
+
103
+ def _cast_hash(value)
104
+ return value if value.is_a?(Hash)
105
+ return {} if value.blank?
106
+ return value.to_h if value.respond_to?(:to_h)
107
+ return ::Hash[value.to_a] if value.respond_to?(:to_a)
108
+ {}
109
+ end
110
+
111
+ def cast_array(value)
112
+ return [] if value.blank?
113
+ ::Array.wrap(value)
114
+ end
115
+
116
+ end
117
+ end
@@ -1,8 +1,8 @@
1
1
  module Subroutine
2
2
 
3
3
  MAJOR = 0
4
- MINOR = 0
5
- PATCH = 1
4
+ MINOR = 1
5
+ PATCH = 0
6
6
  PRE = nil
7
7
 
8
8
  VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join('.')
data/lib/subroutine.rb CHANGED
@@ -1,243 +1,2 @@
1
1
  require "subroutine/version"
2
- require 'active_support/core_ext/hash/indifferent_access'
3
- require 'active_model'
4
-
5
- module Subroutine
6
-
7
- class Failure < StandardError
8
- attr_reader :record
9
- def initialize(record)
10
- @record = record
11
- errors = @record.errors.full_messages.join(", ")
12
- super(errors)
13
- end
14
- end
15
-
16
- class Op
17
-
18
- include ::ActiveModel::Model
19
- include ::ActiveModel::Validations::Callbacks
20
-
21
- class << self
22
-
23
- # fields can be provided in the following way:
24
- # field :field1, :field2
25
- # field :field3, :field4, default: 'my default'
26
- # field field5: 'field5 default', field6: 'field6 default'
27
- def field(*fields)
28
- last_hash = fields.extract_options!
29
- options = last_hash.slice(:default, :scope)
30
-
31
- fields << last_hash.except(:default, :scope)
32
-
33
- fields.each do |f|
34
-
35
- if f.is_a?(Hash)
36
- f.each do |k,v|
37
- field(k, options.merge(:default => v))
38
- end
39
- else
40
-
41
- _field(f, options)
42
- end
43
- end
44
-
45
- end
46
- alias_method :fields, :field
47
-
48
-
49
- def inputs_from(*ops)
50
- ops.each do |op|
51
- field(*op._fields)
52
- defaults(op._defaults)
53
- error_map(op._error_map)
54
- end
55
- end
56
-
57
-
58
- def default(pairs)
59
- self._defaults.merge!(pairs.stringify_keys)
60
- end
61
- alias_method :defaults, :default
62
-
63
-
64
- def error_map(map)
65
- self._error_map.merge!(map)
66
- end
67
- alias_method :error_maps, :error_map
68
-
69
-
70
- def inherited(child)
71
- super
72
-
73
- child._fields = []
74
- child._defaults = {}
75
- child._error_map = {}
76
-
77
- child._fields |= self._fields
78
- child._defaults.merge!(self._defaults)
79
- child._error_map.merge!(self._error_map)
80
- end
81
-
82
-
83
- def submit!(*args)
84
- op = new(*args)
85
- op.submit!
86
-
87
- op
88
- end
89
-
90
- def submit(*args)
91
- op = new(*args)
92
- op.submit
93
- op
94
- end
95
-
96
- protected
97
-
98
- def _field(field_name, options = {})
99
- field = [options[:scope], field_name].compact.join('_')
100
- self._fields += [field]
101
-
102
- attr_accessor field
103
-
104
- default(field => options[:default]) if options[:default]
105
- end
106
-
107
- end
108
-
109
-
110
- class_attribute :_fields
111
- self._fields = []
112
- class_attribute :_defaults
113
- self._defaults = {}
114
- class_attribute :_error_map
115
- self._error_map = {}
116
-
117
- attr_reader :original_params
118
- attr_reader :params
119
-
120
-
121
- def initialize(inputs = {})
122
- @original_params = inputs.with_indifferent_access
123
- @params = {}
124
-
125
- self.class._defaults.each do |k,v|
126
- self.send("#{k}=", v.respond_to?(:call) ? v.call : v)
127
- end
128
- end
129
-
130
-
131
- def submit!
132
- unless submit
133
- raise ::Subroutine::Failure.new(self)
134
- end
135
- true
136
- end
137
-
138
- # the action which should be invoked upon form submission (from the controller)
139
- def submit
140
- observe_submission do
141
- @params = filter_params(@original_params)
142
-
143
- set_accessors(@params)
144
-
145
- validate_and_perform
146
- end
147
-
148
- rescue Exception => e
149
- if e.respond_to?(:record)
150
- inherit_errors_from(e.record) unless e.record == self
151
- false
152
- else
153
- raise e
154
- end
155
- end
156
-
157
- protected
158
-
159
- # these enable you to 1) add log output or 2) add performance monitoring such as skylight.
160
- def observe_submission
161
- yield
162
- end
163
-
164
- def observe_validation
165
- yield
166
- end
167
-
168
- def observe_perform
169
- yield
170
- end
171
-
172
-
173
- def validate_and_perform
174
- bool = observe_validation do
175
- valid?
176
- end
177
- return false unless bool
178
-
179
- observe_perform do
180
- perform
181
- end
182
- end
183
-
184
- # implement this in your concrete class.
185
- def perform
186
- raise NotImplementedError
187
- end
188
-
189
- def field_provided?(key)
190
- @params.has_key?(key)
191
- end
192
-
193
-
194
- # applies the errors to the form object from the child object, optionally at the namespace provided
195
- def inherit_errors_from(object, namespace = nil)
196
- inherit_errors(object.errors, namespace)
197
- end
198
-
199
-
200
- # applies the errors in error_object to self, optionally at the namespace provided
201
- # returns false so failure cases can end with this invocation
202
- def inherit_errors(error_object, namespace = nil)
203
- error_object.each do |k,v|
204
-
205
- keys = [k, [namespace, k].compact.join('_')].map(&:to_sym).uniq
206
- keys = keys.map{|key| _error_map[key] || key }
207
-
208
- match = keys.detect{|key| self.respond_to?(key) || @original_params.try(:has_key?, key) }
209
-
210
- if match
211
- errors.add(match, v)
212
- else
213
- errors.add(:base, error_object.full_message(k, v))
214
- end
215
-
216
- end
217
-
218
- false
219
- end
220
-
221
-
222
- # if you want to use strong parameters or something in your form object you can do so here.
223
- def filter_params(inputs)
224
- inputs.slice(*_fields)
225
- end
226
-
227
-
228
- def set_accessors(inputs, namespace = nil)
229
- inputs.each do |key, value|
230
-
231
- setter = [namespace, key].compact.join('_')
232
-
233
- if respond_to?("#{setter}=") && _fields.include?(setter)
234
- send("#{setter}=", value)
235
- elsif value.is_a?(Hash)
236
- set_accessors(value, setter)
237
- end
238
- end
239
- end
240
-
241
- end
242
-
243
- end
2
+ require "subroutine/op"
@@ -5,12 +5,12 @@ module Subroutine
5
5
 
6
6
  def test_simple_fields_definition
7
7
  op = ::SignupOp.new
8
- assert_equal ['email', 'password'], op._fields
8
+ assert_equal [:email, :password], op._fields.keys.sort
9
9
  end
10
10
 
11
11
  def test_inherited_fields
12
12
  op = ::AdminSignupOp.new
13
- assert_equal ['email', 'password', 'priveleges'], op._fields
13
+ assert_equal [:email, :password, :priveleges], op._fields.keys.sort
14
14
  end
15
15
 
16
16
  def test_class_attribute_usage
@@ -20,45 +20,28 @@ module Subroutine
20
20
  bid = ::AdminSignupOp._fields.object_id
21
21
 
22
22
  refute_equal sid, bid
23
-
24
- sid = ::SignupOp._defaults.object_id
25
- bid = ::AdminSignupOp._defaults.object_id
26
-
27
- refute_equal sid, bid
28
-
29
- sid = ::SignupOp._error_map.object_id
30
- bid = ::AdminSignupOp._error_map.object_id
31
-
32
- refute_equal sid, bid
33
-
34
23
  end
35
24
 
36
25
  def test_inputs_from_inherited_fields_without_inheriting_from_the_class
37
26
  refute ::BusinessSignupOp < ::SignupOp
38
27
 
39
- ::SignupOp._fields.each do |field|
40
- assert_includes ::BusinessSignupOp._fields, field
41
- end
28
+ user_fields = ::SignupOp._fields.keys
29
+ biz_fields = ::BusinessSignupOp._fields.keys
42
30
 
43
- ::SignupOp._defaults.each_pair do |k,v|
44
- assert_equal v, ::BusinessSignupOp._defaults[k]
45
- end
46
-
47
- ::SignupOp._error_map.each_pair do |k,v|
48
- assert_equal v, ::BusinessSignupOp._error_map[k]
31
+ user_fields.each do |field|
32
+ assert_includes biz_fields, field
49
33
  end
50
34
  end
51
35
 
52
36
  def test_defaults_declaration_options
53
- assert_equal ::DefaultsOp._defaults, {
54
- 'foo' => 'foo',
55
- 'baz' => 'baz',
56
- 'bar' => 'bar'
57
- }
37
+ op = ::DefaultsOp.new
38
+ assert_equal 'foo', op.foo
39
+ assert_equal 'bar', op.bar
58
40
  end
59
41
 
60
42
  def test_inherited_defaults_override_correctly
61
- assert_equal 'barstool', ::InheritedDefaultsOp._defaults['bar']
43
+ op = ::InheritedDefaultsOp.new
44
+ assert_equal 'barstool', op.bar
62
45
  end
63
46
 
64
47
  def test_accessors_are_created
@@ -87,6 +70,9 @@ module Subroutine
87
70
  assert_nil op.email
88
71
  assert_nil op.password
89
72
  assert_equal 'min', op.priveleges
73
+
74
+ op.priveleges = 'max'
75
+ assert_equal 'max', op.priveleges
90
76
  end
91
77
 
92
78
  def test_validations_are_evaluated_before_perform_is_invoked
@@ -107,7 +93,7 @@ module Subroutine
107
93
  assert op.perform_called
108
94
  refute op.perform_finished
109
95
 
110
- assert_equal ["has gotta be @admin.com"], op.errors[:email]
96
+ assert_equal ["Email address has gotta be @admin.com"], op.errors[:base]
111
97
  end
112
98
 
113
99
  def test_when_valid_perform_completes_it_returns_control
@@ -0,0 +1,224 @@
1
+ require 'test_helper'
2
+
3
+ module Subroutine
4
+ class TypeCasterTest < TestCase
5
+
6
+ def op
7
+ @op ||= TypeCastOp.new
8
+ end
9
+
10
+ def test_integer_inputs
11
+ op.integer_input = nil
12
+ assert_equal nil, op.integer_input
13
+
14
+ op.integer_input = 'foo'
15
+ assert_equal 0, op.integer_input
16
+
17
+ op.integer_input = '4.5'
18
+ assert_equal 4, op.integer_input
19
+
20
+ op.integer_input = 0.5
21
+ assert_equal 0, op.integer_input
22
+
23
+ op.integer_input = 5.2
24
+ assert_equal 5, op.integer_input
25
+
26
+ op.integer_input = 6
27
+ assert_equal 6, op.integer_input
28
+ end
29
+
30
+ def test_number_inputs
31
+ op.number_input = nil
32
+ assert_equal nil, op.number_input
33
+
34
+ op.number_input = 4
35
+ assert_equal 4.0, op.number_input
36
+
37
+ op.number_input = 0.5
38
+ assert_equal 0.5, op.number_input
39
+
40
+ op.number_input = 'foo'
41
+ assert_equal 0.0, op.number_input
42
+ end
43
+
44
+ def test_string_inputs
45
+ op.string_input = nil
46
+ assert_equal nil, op.string_input
47
+
48
+ op.string_input = ""
49
+ assert_equal '', op.string_input
50
+
51
+ op.string_input = "foo"
52
+ assert_equal 'foo', op.string_input
53
+
54
+ op.string_input = 4
55
+ assert_equal '4', op.string_input
56
+
57
+ op.string_input = 4.2
58
+ assert_equal '4.2', op.string_input
59
+ end
60
+
61
+ def test_boolean_inputs
62
+ op.boolean_input = nil
63
+ assert_equal nil, op.boolean_input
64
+
65
+ op.boolean_input = 'yes'
66
+ assert_equal true, op.boolean_input
67
+
68
+ op.boolean_input = 'no'
69
+ assert_equal false, op.boolean_input
70
+
71
+ op.boolean_input = 'true'
72
+ assert_equal true, op.boolean_input
73
+
74
+ op.boolean_input = 'false'
75
+ assert_equal false, op.boolean_input
76
+
77
+ op.boolean_input = 'ok'
78
+ assert_equal true, op.boolean_input
79
+
80
+ op.boolean_input = ''
81
+ assert_equal false, op.boolean_input
82
+
83
+ op.boolean_input = true
84
+ assert_equal true, op.boolean_input
85
+
86
+ op.boolean_input = false
87
+ assert_equal false, op.boolean_input
88
+
89
+ op.boolean_input = '1'
90
+ assert_equal true, op.boolean_input
91
+
92
+ op.boolean_input = '0'
93
+ assert_equal false, op.boolean_input
94
+
95
+ op.boolean_input = 1
96
+ assert_equal true, op.boolean_input
97
+
98
+ op.boolean_input = 0
99
+ assert_equal false, op.boolean_input
100
+ end
101
+
102
+ def test_hash_inputs
103
+ op.object_input = nil
104
+ assert_equal nil, op.object_input
105
+
106
+ op.object_input = ''
107
+ assert_equal({}, op.object_input)
108
+
109
+ op.object_input = [[:a,:b]]
110
+ assert_equal({"a" => :b}, op.object_input)
111
+
112
+ op.object_input = false
113
+ assert_equal({}, op.object_input)
114
+
115
+ op.object_input = {foo: 'bar'}
116
+ assert_equal({'foo' => 'bar'}, op.object_input)
117
+
118
+ op.object_input = {"foo" => {:bar => :baz}}
119
+ assert_equal({"foo" => {:bar => :baz}}, op.object_input)
120
+ end
121
+
122
+ def test_array_inputs
123
+ op.array_input = nil
124
+ assert_equal nil, op.array_input
125
+
126
+ op.array_input = ''
127
+ assert_equal [], op.array_input
128
+
129
+ op.array_input = 'foo'
130
+ assert_equal ['foo'], op.array_input
131
+
132
+ op.array_input = ['foo']
133
+ assert_equal ['foo'], op.array_input
134
+
135
+ op.array_input = {:bar => true}
136
+ assert_equal [{:bar => true}], op.array_input
137
+ end
138
+
139
+ def test_date_inputs
140
+ op.date_input = nil
141
+ assert_equal nil, op.date_input
142
+
143
+ op.date_input = "2022-12-22"
144
+ assert_equal ::Date, op.date_input.class
145
+ refute_equal ::DateTime, op.date_input.class
146
+
147
+ assert_equal 2022, op.date_input.year
148
+ assert_equal 12, op.date_input.month
149
+ assert_equal 22, op.date_input.day
150
+
151
+ op.date_input = "2023-05-05T10:00:30"
152
+ assert_equal ::Date, op.date_input.class
153
+ refute_equal ::DateTime, op.date_input.class
154
+
155
+ assert_equal 2023, op.date_input.year
156
+ assert_equal 5, op.date_input.month
157
+ assert_equal 5, op.date_input.day
158
+
159
+ op.date_input = "2020-05-03 13:44:45 -0400"
160
+
161
+ assert_equal ::Date, op.date_input.class
162
+ refute_equal ::DateTime, op.date_input.class
163
+
164
+ assert_equal 2020, op.date_input.year
165
+ assert_equal 5, op.date_input.month
166
+ assert_equal 3, op.date_input.day
167
+ end
168
+
169
+
170
+ def test_time_inputs
171
+ op.time_input = nil
172
+ assert_equal nil, op.time_input
173
+
174
+ op.time_input = "2022-12-22"
175
+ assert_equal ::Time, op.time_input.class
176
+ refute_equal ::DateTime, op.time_input.class
177
+
178
+ assert_equal 2022, op.time_input.year
179
+ assert_equal 12, op.time_input.month
180
+ assert_equal 22, op.time_input.day
181
+ assert_equal 0, op.time_input.hour
182
+ assert_equal 0, op.time_input.min
183
+ assert_equal 0, op.time_input.sec
184
+
185
+ op.time_input = "2023-05-05T10:00:30Z"
186
+ assert_equal ::Time, op.time_input.class
187
+ refute_equal ::DateTime, op.time_input.class
188
+
189
+ assert_equal 2023, op.time_input.year
190
+ assert_equal 5, op.time_input.month
191
+ assert_equal 5, op.time_input.day
192
+ assert_equal 10, op.time_input.hour
193
+ assert_equal 0, op.time_input.min
194
+ assert_equal 30, op.time_input.sec
195
+ end
196
+
197
+ def test_iso_date_inputs
198
+ op.iso_date_input = nil
199
+ assert_equal nil, op.iso_date_input
200
+
201
+ op.iso_date_input = "2022-12-22"
202
+ assert_equal ::String, op.iso_date_input.class
203
+ assert_equal "2022-12-22", op.iso_date_input
204
+
205
+ op.iso_date_input = Date.parse("2022-12-22")
206
+ assert_equal ::String, op.iso_date_input.class
207
+ assert_equal "2022-12-22", op.iso_date_input
208
+ end
209
+
210
+ def test_iso_time_inputs
211
+ op.iso_time_input = nil
212
+ assert_equal nil, op.iso_time_input
213
+
214
+ op.iso_time_input = "2022-12-22T10:30:24Z"
215
+ assert_equal ::String, op.iso_time_input.class
216
+ assert_equal "2022-12-22T10:30:24Z", op.iso_time_input
217
+
218
+ op.iso_time_input = Time.parse("2022-12-22T10:30:24Z")
219
+ assert_equal ::String, op.iso_time_input.class
220
+ assert_equal "2022-12-22T10:30:24Z", op.iso_time_input
221
+ end
222
+
223
+ end
224
+ end
data/test/support/ops.rb CHANGED
@@ -18,14 +18,12 @@ end
18
18
 
19
19
  class SignupOp < ::Subroutine::Op
20
20
 
21
- field :email
22
- field :password
21
+ string :email
22
+ string :password
23
23
 
24
24
  validates :email, :presence => true
25
25
  validates :password, :presence => true
26
26
 
27
- error_map :email_address => :email
28
-
29
27
  attr_reader :perform_called
30
28
  attr_reader :perform_finished
31
29
 
@@ -74,7 +72,7 @@ end
74
72
 
75
73
  class BusinessSignupOp < ::Subroutine::Op
76
74
 
77
- field :business_name
75
+ string :business_name
78
76
  inputs_from ::SignupOp
79
77
 
80
78
  end
@@ -82,16 +80,27 @@ end
82
80
  class DefaultsOp < ::Subroutine::Op
83
81
 
84
82
  field :foo, :default => 'foo'
85
-
86
- field baz: 'baz'
87
-
88
- field :bar
89
- default :bar => 'bar'
83
+ field :bar, :default => 'bar'
90
84
 
91
85
  end
92
86
 
93
87
  class InheritedDefaultsOp < ::DefaultsOp
94
88
 
95
- default :bar => 'barstool'
89
+ field :bar, :default => 'barstool'
90
+
91
+ end
92
+
93
+ class TypeCastOp < ::Subroutine::Op
94
+
95
+ integer :integer_input
96
+ number :number_input
97
+ string :string_input
98
+ boolean :boolean_input
99
+ date :date_input
100
+ time :time_input, :default => lambda{ Time.now }
101
+ iso_date :iso_date_input
102
+ iso_time :iso_time_input
103
+ object :object_input
104
+ array :array_input, :default => 'foo'
96
105
 
97
106
  end
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.0.1
4
+ version: 0.1.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: 2015-04-07 00:00:00.000000000 Z
11
+ date: 2015-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -100,9 +100,13 @@ files:
100
100
  - gemfiles/am41.gemfile
101
101
  - gemfiles/am42.gemfile
102
102
  - lib/subroutine.rb
103
+ - lib/subroutine/failure.rb
104
+ - lib/subroutine/op.rb
105
+ - lib/subroutine/type_caster.rb
103
106
  - lib/subroutine/version.rb
104
107
  - subroutine.gemspec
105
108
  - test/subroutine/base_test.rb
109
+ - test/subroutine/type_caster_test.rb
106
110
  - test/support/ops.rb
107
111
  - test/test_helper.rb
108
112
  homepage: https://github.com/mnelson/subroutine
@@ -137,5 +141,6 @@ test_files:
137
141
  - gemfiles/am41.gemfile
138
142
  - gemfiles/am42.gemfile
139
143
  - test/subroutine/base_test.rb
144
+ - test/subroutine/type_caster_test.rb
140
145
  - test/support/ops.rb
141
146
  - test/test_helper.rb