subroutine 4.1.5 → 4.4.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 +4 -4
- data/.github/workflows/build.yml +1 -1
- data/.ruby-version +1 -1
- data/Appraisals +7 -0
- data/CHANGELOG.MD +22 -1
- data/README.md +7 -1
- data/gemfiles/rails_6.1.gemfile +1 -0
- data/gemfiles/rails_6.1.gemfile.lock +102 -23
- data/gemfiles/rails_7.0.gemfile +1 -0
- data/gemfiles/rails_7.0.gemfile.lock +100 -22
- data/gemfiles/rails_7.1.gemfile.lock +106 -26
- data/gemfiles/rails_7.2.gemfile.lock +106 -28
- data/gemfiles/rails_8.0.gemfile +9 -0
- data/gemfiles/rails_8.0.gemfile.lock +204 -0
- data/lib/subroutine/association_fields.rb +1 -1
- data/lib/subroutine/outputs/configuration.rb +8 -1
- data/lib/subroutine/outputs.rb +58 -14
- data/lib/subroutine/version.rb +2 -2
- data/lib/subroutine.rb +12 -0
- data/subroutine.gemspec +7 -1
- data/test/subroutine/outputs_test.rb +59 -0
- metadata +65 -8
data/lib/subroutine/outputs.rb
CHANGED
@@ -10,11 +10,27 @@ module Subroutine
|
|
10
10
|
|
11
11
|
extend ActiveSupport::Concern
|
12
12
|
|
13
|
+
class LazyExecutor
|
14
|
+
def initialize(value)
|
15
|
+
@value_block = value
|
16
|
+
@executed = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def value
|
20
|
+
return @value if @executed
|
21
|
+
@value = @value_block.respond_to?(:call) ? @value_block.call : @value
|
22
|
+
@executed = true
|
23
|
+
@value
|
24
|
+
end
|
25
|
+
|
26
|
+
def executed?
|
27
|
+
@executed
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
13
31
|
included do
|
14
32
|
class_attribute :output_configurations
|
15
33
|
self.output_configurations = {}
|
16
|
-
|
17
|
-
attr_reader :outputs
|
18
34
|
end
|
19
35
|
|
20
36
|
module ClassMethods
|
@@ -39,20 +55,42 @@ module Subroutine
|
|
39
55
|
@outputs = {} # don't do with_indifferent_access because it will turn provided objects into with_indifferent_access objects, which may not be the desired behavior
|
40
56
|
end
|
41
57
|
|
58
|
+
def outputs
|
59
|
+
unless @outputs_executed
|
60
|
+
@outputs.each_pair do |key, value|
|
61
|
+
@outputs[key] = value.is_a?(LazyExecutor) ? value.value : value
|
62
|
+
end
|
63
|
+
@outputs_executed = true
|
64
|
+
end
|
65
|
+
|
66
|
+
@outputs
|
67
|
+
end
|
68
|
+
|
42
69
|
def output(name, value)
|
43
70
|
name = name.to_sym
|
44
71
|
unless output_configurations.key?(name)
|
45
72
|
raise ::Subroutine::Outputs::UnknownOutputError, name
|
46
73
|
end
|
47
74
|
|
48
|
-
outputs[name] = value
|
75
|
+
@outputs[name] = output_configurations[name].lazy? ? LazyExecutor.new(value) : value
|
49
76
|
end
|
50
77
|
|
51
78
|
def get_output(name)
|
52
79
|
name = name.to_sym
|
53
80
|
raise ::Subroutine::Outputs::UnknownOutputError, name unless output_configurations.key?(name)
|
54
81
|
|
55
|
-
outputs[name]
|
82
|
+
output = @outputs[name]
|
83
|
+
unless output.is_a?(LazyExecutor)
|
84
|
+
output
|
85
|
+
else
|
86
|
+
# if its not executed, validate the type
|
87
|
+
unless output.executed?
|
88
|
+
@outputs[name] = output.value
|
89
|
+
ensure_output_type_valid!(name)
|
90
|
+
end
|
91
|
+
|
92
|
+
@outputs[name]
|
93
|
+
end
|
56
94
|
end
|
57
95
|
|
58
96
|
def validate_outputs!
|
@@ -60,21 +98,27 @@ module Subroutine
|
|
60
98
|
if config.required? && !output_provided?(name)
|
61
99
|
raise ::Subroutine::Outputs::OutputNotSetError, name
|
62
100
|
end
|
63
|
-
unless
|
64
|
-
name
|
65
|
-
raise ::Subroutine::Outputs::InvalidOutputTypeError.new(
|
66
|
-
name: name,
|
67
|
-
actual_type: outputs[name].class,
|
68
|
-
expected_type: output_configurations[name][:type]
|
69
|
-
)
|
101
|
+
unless output_configurations[name].lazy?
|
102
|
+
ensure_output_type_valid!(name)
|
70
103
|
end
|
71
104
|
end
|
72
105
|
end
|
73
106
|
|
107
|
+
def ensure_output_type_valid!(name)
|
108
|
+
return if valid_output_type?(name)
|
109
|
+
|
110
|
+
name = name.to_sym
|
111
|
+
raise ::Subroutine::Outputs::InvalidOutputTypeError.new(
|
112
|
+
name: name,
|
113
|
+
actual_type: @outputs[name].class,
|
114
|
+
expected_type: output_configurations[name][:type]
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
74
118
|
def output_provided?(name)
|
75
119
|
name = name.to_sym
|
76
120
|
|
77
|
-
outputs.key?(name)
|
121
|
+
@outputs.key?(name)
|
78
122
|
end
|
79
123
|
|
80
124
|
def valid_output_type?(name)
|
@@ -84,9 +128,9 @@ module Subroutine
|
|
84
128
|
|
85
129
|
output_configuration = output_configurations[name]
|
86
130
|
return true unless output_configuration[:type]
|
87
|
-
return true if !output_configuration.required? && outputs[name].nil?
|
131
|
+
return true if !output_configuration.required? && @outputs[name].nil?
|
88
132
|
|
89
|
-
outputs[name].is_a?(output_configuration[:type])
|
133
|
+
@outputs[name].is_a?(output_configuration[:type])
|
90
134
|
end
|
91
135
|
end
|
92
136
|
end
|
data/lib/subroutine/version.rb
CHANGED
data/lib/subroutine.rb
CHANGED
@@ -16,6 +16,18 @@ require "subroutine/op"
|
|
16
16
|
|
17
17
|
module Subroutine
|
18
18
|
|
19
|
+
# Used by polymorphic association fields to resolve the class name to a ruby class
|
20
|
+
def self.constantize_polymorphic_class_name(class_name)
|
21
|
+
return @constantize_polymorphic_class_name.call(class_name) if defined?(@constantize_polymorphic_class_name)
|
22
|
+
|
23
|
+
class_name.camelize.constantize
|
24
|
+
end
|
25
|
+
|
26
|
+
# When you need to customize how a polymorphic class name is resolved, you can set this callable/lambda/proc
|
27
|
+
def self.constantize_polymorphic_class_name=(callable)
|
28
|
+
@constantize_polymorphic_class_name = callable
|
29
|
+
end
|
30
|
+
|
19
31
|
def self.include_defaults_in_params=(bool)
|
20
32
|
@include_defaults_in_params = !!bool
|
21
33
|
end
|
data/subroutine.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.email = ["mike@mnelson.io"]
|
12
12
|
spec.summary = "Feature-driven operation objects."
|
13
13
|
spec.description = "An interface for creating feature-driven operations."
|
14
|
-
spec.homepage = "https://github.com/
|
14
|
+
spec.homepage = "https://github.com/guideline-tech/subroutine"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -21,6 +21,10 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.add_dependency "activemodel", ">= 6.1"
|
23
23
|
spec.add_dependency "activesupport", ">= 6.1"
|
24
|
+
spec.add_dependency "base64"
|
25
|
+
spec.add_dependency "bigdecimal"
|
26
|
+
spec.add_dependency "logger"
|
27
|
+
spec.add_dependency "mutex_m"
|
24
28
|
|
25
29
|
spec.add_development_dependency "actionpack", ">= 6.1"
|
26
30
|
spec.add_development_dependency "byebug"
|
@@ -29,4 +33,6 @@ Gem::Specification.new do |spec|
|
|
29
33
|
spec.add_development_dependency "minitest-reporters"
|
30
34
|
spec.add_development_dependency "mocha"
|
31
35
|
spec.add_development_dependency "rake"
|
36
|
+
|
37
|
+
spec.required_ruby_version = ">= 3.2.0"
|
32
38
|
end
|
@@ -10,6 +10,20 @@ module Subroutine
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
class LazyOutputOp < ::Subroutine::Op
|
14
|
+
outputs :foo, lazy: true
|
15
|
+
outputs :baz, lazy: true, type: String
|
16
|
+
|
17
|
+
def perform
|
18
|
+
output :foo, -> { call_me }
|
19
|
+
output :baz, -> { call_baz }
|
20
|
+
end
|
21
|
+
|
22
|
+
def call_me; end
|
23
|
+
|
24
|
+
def call_baz; end
|
25
|
+
end
|
26
|
+
|
13
27
|
class MissingOutputSetOp < ::Subroutine::Op
|
14
28
|
outputs :foo
|
15
29
|
def perform
|
@@ -99,5 +113,50 @@ module Subroutine
|
|
99
113
|
op.submit
|
100
114
|
end
|
101
115
|
end
|
116
|
+
|
117
|
+
################
|
118
|
+
# lazy outputs #
|
119
|
+
################
|
120
|
+
|
121
|
+
def test_it_does_not_call_lazy_output_values_if_not_accessed
|
122
|
+
op = LazyOutputOp.new
|
123
|
+
op.expects(:call_me).never
|
124
|
+
op.submit!
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_it_calls_lazy_output_values_if_accessed
|
128
|
+
op = LazyOutputOp.new
|
129
|
+
op.expects(:call_me).once
|
130
|
+
op.submit!
|
131
|
+
op.foo
|
132
|
+
end
|
133
|
+
|
134
|
+
def test_it_validates_type_when_lazy_output_is_accessed
|
135
|
+
op = LazyOutputOp.new
|
136
|
+
op.expects(:call_baz).once.returns("a string")
|
137
|
+
op.submit!
|
138
|
+
assert_silent do
|
139
|
+
op.baz
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_it_raises_error_on_invalid_type_when_lazy_output_is_accessed
|
144
|
+
op = LazyOutputOp.new
|
145
|
+
op.expects(:call_baz).once.returns(10)
|
146
|
+
op.submit!
|
147
|
+
error = assert_raises(Subroutine::Outputs::InvalidOutputTypeError) do
|
148
|
+
op.baz
|
149
|
+
end
|
150
|
+
assert_match(/Invalid output type for 'baz' expected String but got Integer/, error.message)
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_it_returns_outputs
|
154
|
+
op = LazyOutputOp.new
|
155
|
+
op.expects(:call_me).once.returns(1)
|
156
|
+
op.expects(:call_baz).once.returns("a string")
|
157
|
+
op.submit!
|
158
|
+
assert_equal({ foo: 1, baz: "a string" }, op.outputs)
|
159
|
+
end
|
160
|
+
|
102
161
|
end
|
103
162
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: subroutine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Nelson
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activemodel
|
@@ -38,6 +37,62 @@ dependencies:
|
|
38
37
|
- - ">="
|
39
38
|
- !ruby/object:Gem::Version
|
40
39
|
version: '6.1'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: base64
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: bigdecimal
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: logger
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: mutex_m
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
41
96
|
- !ruby/object:Gem::Dependency
|
42
97
|
name: actionpack
|
43
98
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +219,8 @@ files:
|
|
164
219
|
- gemfiles/rails_7.1.gemfile.lock
|
165
220
|
- gemfiles/rails_7.2.gemfile
|
166
221
|
- gemfiles/rails_7.2.gemfile.lock
|
222
|
+
- gemfiles/rails_8.0.gemfile
|
223
|
+
- gemfiles/rails_8.0.gemfile.lock
|
167
224
|
- lib/subroutine.rb
|
168
225
|
- lib/subroutine/association_fields.rb
|
169
226
|
- lib/subroutine/association_fields/association_type_mismatch_error.rb
|
@@ -193,11 +250,10 @@ files:
|
|
193
250
|
- test/subroutine/type_caster_test.rb
|
194
251
|
- test/support/ops.rb
|
195
252
|
- test/test_helper.rb
|
196
|
-
homepage: https://github.com/
|
253
|
+
homepage: https://github.com/guideline-tech/subroutine
|
197
254
|
licenses:
|
198
255
|
- MIT
|
199
256
|
metadata: {}
|
200
|
-
post_install_message:
|
201
257
|
rdoc_options: []
|
202
258
|
require_paths:
|
203
259
|
- lib
|
@@ -205,15 +261,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
205
261
|
requirements:
|
206
262
|
- - ">="
|
207
263
|
- !ruby/object:Gem::Version
|
208
|
-
version:
|
264
|
+
version: 3.2.0
|
209
265
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
210
266
|
requirements:
|
211
267
|
- - ">="
|
212
268
|
- !ruby/object:Gem::Version
|
213
269
|
version: '0'
|
214
270
|
requirements: []
|
215
|
-
rubygems_version: 3.
|
216
|
-
signing_key:
|
271
|
+
rubygems_version: 3.6.7
|
217
272
|
specification_version: 4
|
218
273
|
summary: Feature-driven operation objects.
|
219
274
|
test_files:
|
@@ -225,6 +280,8 @@ test_files:
|
|
225
280
|
- gemfiles/rails_7.1.gemfile.lock
|
226
281
|
- gemfiles/rails_7.2.gemfile
|
227
282
|
- gemfiles/rails_7.2.gemfile.lock
|
283
|
+
- gemfiles/rails_8.0.gemfile
|
284
|
+
- gemfiles/rails_8.0.gemfile.lock
|
228
285
|
- test/subroutine/association_test.rb
|
229
286
|
- test/subroutine/auth_test.rb
|
230
287
|
- test/subroutine/base_test.rb
|