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.
@@ -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 valid_output_type?(name)
64
- name = name.to_sym
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
@@ -3,8 +3,8 @@
3
3
  module Subroutine
4
4
 
5
5
  MAJOR = 4
6
- MINOR = 1
7
- PATCH = 5
6
+ MINOR = 4
7
+ PATCH = 0
8
8
  PRE = nil
9
9
 
10
10
  VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
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/mnelson/subroutine"
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.1.5
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: 2024-11-22 00:00:00.000000000 Z
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/mnelson/subroutine
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: '0'
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.5.23
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