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