subroutine 2.1.2 → 2.3.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 +23 -0
- data/CHANGELOG.MD +4 -0
- data/README.md +1 -1
- data/lib/subroutine/association_fields/configuration.rb +18 -1
- data/lib/subroutine/outputs/invalid_output_type_error.rb +13 -0
- data/lib/subroutine/outputs.rb +26 -10
- data/lib/subroutine/version.rb +2 -2
- data/test/subroutine/association_test.rb +24 -0
- data/test/subroutine/base_test.rb +0 -24
- data/test/subroutine/outputs_test.rb +103 -0
- data/test/support/ops.rb +29 -35
- metadata +7 -4
- data/.travis.yml +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a130a9c00d47631ee67621f3772ff5d050c348af94c9ec99a3ef4d90f0164079
|
4
|
+
data.tar.gz: d7c099c1c586bfaf516955b1f94aa86188fbbe0194e42021d0f0191341efb482
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffa8be2183e84ad07c523251c499673a169f7b9968d79164f7662082ffc9497027f4344cb12fda6b602aaf2b6884a8be17690a58448f50138f4fb547f9d6e0cf
|
7
|
+
data.tar.gz: c6130d737e8860e05d529e679e4a2f2be2de3f7114da5e97c5f8d3844463f3fc6f9e3a2f23da50891282989adcbd08759be9f243ff3aaf25adc978255cd18d43
|
@@ -0,0 +1,23 @@
|
|
1
|
+
name: build
|
2
|
+
on: [push, pull_request]
|
3
|
+
jobs:
|
4
|
+
build:
|
5
|
+
runs-on: ubuntu-latest
|
6
|
+
continue-on-error: ${{ matrix.experimental }}
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
ruby-version: [2.7.5, 2.7.6]
|
11
|
+
experimental: [false]
|
12
|
+
include:
|
13
|
+
- ruby-version: 3.0
|
14
|
+
experimental: true
|
15
|
+
- ruby-version: 3.1
|
16
|
+
experimental: true
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v2
|
19
|
+
- uses: ruby/setup-ruby@v1
|
20
|
+
with:
|
21
|
+
ruby-version: ${{ matrix.ruby-version }}
|
22
|
+
bundler-cache: true # runs `bundle install` and caches installed gems automatically
|
23
|
+
- run: bundle exec rake test
|
data/CHANGELOG.MD
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## Subroutine 2.2
|
2
|
+
|
3
|
+
Add `type` validation for Output.
|
4
|
+
|
1
5
|
## Subroutine 2.0
|
2
6
|
|
3
7
|
The updates between 1.0 and 2.0 are relatively minor and are focused more on cleaning up the codebase and extending the use of the 0.9->1.0 refactor. There are, however, breaking changes to how associations are loaded. The association is no longer loaded via `find()` but rather `find_by!(id:)`. Given this, a major version was released.
|
data/README.md
CHANGED
@@ -60,7 +60,7 @@ module Subroutine
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def build_foreign_key_field
|
63
|
-
build_child_field(foreign_key_method, type: :foreign_key, foreign_key_type:
|
63
|
+
build_child_field(foreign_key_method, type: :foreign_key, foreign_key_type: determine_foreign_key_type)
|
64
64
|
end
|
65
65
|
|
66
66
|
def build_foreign_type_field
|
@@ -84,6 +84,23 @@ module Subroutine
|
|
84
84
|
ComponentConfiguration.new(name, child_opts)
|
85
85
|
end
|
86
86
|
|
87
|
+
def determine_foreign_key_type
|
88
|
+
return config[:foreign_key_type] if config[:foreign_key_type]
|
89
|
+
|
90
|
+
# TODO: Make this logic work for polymorphic associations.
|
91
|
+
return if polymorphic?
|
92
|
+
|
93
|
+
klass = inferred_foreign_type&.constantize
|
94
|
+
if klass && klass.respond_to?(:type_for_attribute)
|
95
|
+
case klass.type_for_attribute(find_by)&.type&.to_sym
|
96
|
+
when :string
|
97
|
+
:string
|
98
|
+
else
|
99
|
+
:integer
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
87
104
|
end
|
88
105
|
end
|
89
106
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Subroutine
|
4
|
+
module Outputs
|
5
|
+
class InvalidOutputTypeError < StandardError
|
6
|
+
|
7
|
+
def initialize(name:, expected_type:, actual_type:)
|
8
|
+
super("Invalid output type for '#{name}' expected #{expected_type} but got #{actual_type}")
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/subroutine/outputs.rb
CHANGED
@@ -4,6 +4,7 @@ require "active_support/concern"
|
|
4
4
|
require "subroutine/outputs/configuration"
|
5
5
|
require "subroutine/outputs/output_not_set_error"
|
6
6
|
require "subroutine/outputs/unknown_output_error"
|
7
|
+
require "subroutine/outputs/invalid_output_type_error"
|
7
8
|
|
8
9
|
module Subroutine
|
9
10
|
module Outputs
|
@@ -39,16 +40,6 @@ module Subroutine
|
|
39
40
|
@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
41
|
end
|
41
42
|
|
42
|
-
def output_provided?(name)
|
43
|
-
name = name.to_sym
|
44
|
-
|
45
|
-
unless output_configurations.key?(name)
|
46
|
-
raise ::Subroutine::Outputs::UnknownOutputError, name
|
47
|
-
end
|
48
|
-
|
49
|
-
outputs.key?(name)
|
50
|
-
end
|
51
|
-
|
52
43
|
def output(name, value)
|
53
44
|
name = name.to_sym
|
54
45
|
unless output_configurations.key?(name)
|
@@ -70,8 +61,33 @@ module Subroutine
|
|
70
61
|
if config.required? && !output_provided?(name)
|
71
62
|
raise ::Subroutine::Outputs::OutputNotSetError, name
|
72
63
|
end
|
64
|
+
unless valid_output_type?(name)
|
65
|
+
name = name.to_sym
|
66
|
+
raise ::Subroutine::Outputs::InvalidOutputTypeError.new(
|
67
|
+
name: name,
|
68
|
+
actual_type: outputs[name].class,
|
69
|
+
expected_type: output_configurations[name][:type]
|
70
|
+
)
|
71
|
+
end
|
73
72
|
end
|
74
73
|
end
|
75
74
|
|
75
|
+
def output_provided?(name)
|
76
|
+
name = name.to_sym
|
77
|
+
|
78
|
+
outputs.key?(name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def valid_output_type?(name)
|
82
|
+
name = name.to_sym
|
83
|
+
|
84
|
+
return true unless output_configurations.key?(name)
|
85
|
+
|
86
|
+
output_configuration = output_configurations[name]
|
87
|
+
return true unless output_configuration[:type]
|
88
|
+
return true if !output_configuration.required? && outputs[name].nil?
|
89
|
+
|
90
|
+
outputs[name].is_a?(output_configuration[:type])
|
91
|
+
end
|
76
92
|
end
|
77
93
|
end
|
data/lib/subroutine/version.rb
CHANGED
@@ -13,6 +13,10 @@ module Subroutine
|
|
13
13
|
@fred ||= ::User.new(id: 2, email_address: "fred@example.com")
|
14
14
|
end
|
15
15
|
|
16
|
+
def murphy
|
17
|
+
@murphy ||= ::StringIdUser.new(id: "ABACABADABACABA", email_address: "murphy@example.com")
|
18
|
+
end
|
19
|
+
|
16
20
|
def account
|
17
21
|
@account ||= ::Account.new(id: 1)
|
18
22
|
end
|
@@ -43,6 +47,16 @@ module Subroutine
|
|
43
47
|
assert_equal doug, op.user
|
44
48
|
end
|
45
49
|
|
50
|
+
def test_it_looks_up_an_association_with_string_ids
|
51
|
+
all_mock = mock
|
52
|
+
|
53
|
+
::StringIdUser.expects(:all).returns(all_mock)
|
54
|
+
all_mock.expects(:find_by!).with(id: "ABACABADABACABA").returns(murphy)
|
55
|
+
|
56
|
+
op = ::SimpleAssociationWithStringIdOp.new(string_id_user_id: murphy.id)
|
57
|
+
assert_equal murphy, op.string_id_user
|
58
|
+
end
|
59
|
+
|
46
60
|
def test_it_sanitizes_types
|
47
61
|
all_mock = mock
|
48
62
|
|
@@ -122,6 +136,16 @@ module Subroutine
|
|
122
136
|
assert_equal "email_address", op.field_configurations[:user][:find_by]
|
123
137
|
end
|
124
138
|
|
139
|
+
def test_it_allows_a_find_by_to_be_set_with_implicit_string
|
140
|
+
all_mock = mock
|
141
|
+
::User.expects(:all).returns(all_mock)
|
142
|
+
all_mock.expects(:find_by!).with(email_address: doug.email_address).returns(doug)
|
143
|
+
|
144
|
+
op = ::AssociationWithImplicitStringFindByOp.new(user_id: doug.email_address)
|
145
|
+
assert_equal doug, op.user
|
146
|
+
assert_equal "email_address", op.field_configurations[:user][:find_by]
|
147
|
+
end
|
148
|
+
|
125
149
|
def test_values_are_correct_for_find_by_usage
|
126
150
|
op = ::AssociationWithFindByKeyOp.new(user: doug)
|
127
151
|
assert_equal doug, op.user
|
@@ -264,30 +264,6 @@ module Subroutine
|
|
264
264
|
assert_equal Hash, value.class
|
265
265
|
end
|
266
266
|
|
267
|
-
def test_it_raises_an_error_if_an_output_is_not_defined_but_is_set
|
268
|
-
op = ::MissingOutputOp.new
|
269
|
-
assert_raises ::Subroutine::Outputs::UnknownOutputError do
|
270
|
-
op.submit
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
def test_it_raises_an_error_if_not_all_outputs_were_set
|
275
|
-
op = ::MissingOutputSetOp.new
|
276
|
-
assert_raises ::Subroutine::Outputs::OutputNotSetError do
|
277
|
-
op.submit
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
def test_it_does_not_raise_an_error_if_output_is_not_set_and_is_not_required
|
282
|
-
op = ::OutputNotRequiredOp.new
|
283
|
-
op.submit
|
284
|
-
end
|
285
|
-
|
286
|
-
def test_it_does_not_raise_an_error_if_the_perform_is_not_a_success
|
287
|
-
op = ::NoOutputNoSuccessOp.new
|
288
|
-
refute op.submit
|
289
|
-
end
|
290
|
-
|
291
267
|
def test_it_does_not_omit_the_backtrace_from_the_original_error
|
292
268
|
op = ::ErrorTraceOp.new
|
293
269
|
begin
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
module Subroutine
|
6
|
+
class OutputsTest < TestCase
|
7
|
+
class MissingOutputOp < ::Subroutine::Op
|
8
|
+
def perform
|
9
|
+
output :foo, 'bar'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class MissingOutputSetOp < ::Subroutine::Op
|
14
|
+
outputs :foo
|
15
|
+
def perform
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class OutputNotRequiredOp < ::Subroutine::Op
|
21
|
+
outputs :foo, required: false
|
22
|
+
def perform
|
23
|
+
true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class NoOutputNoSuccessOp < ::Subroutine::Op
|
28
|
+
outputs :foo
|
29
|
+
|
30
|
+
def perform
|
31
|
+
errors.add(:foo, 'bar')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class OutputWithTypeValidationNotRequired < ::Subroutine::Op
|
36
|
+
outputs :value, type: String, required: false
|
37
|
+
|
38
|
+
def perform; end
|
39
|
+
end
|
40
|
+
|
41
|
+
class OutputWithTypeValidationRequired < ::Subroutine::Op
|
42
|
+
outputs :value, type: String, required: true
|
43
|
+
|
44
|
+
def perform; end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_it_raises_an_error_if_an_output_is_not_defined_but_is_set
|
48
|
+
op = MissingOutputOp.new
|
49
|
+
assert_raises ::Subroutine::Outputs::UnknownOutputError do
|
50
|
+
op.submit
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_it_raises_an_error_if_not_all_outputs_were_set
|
55
|
+
op = MissingOutputSetOp.new
|
56
|
+
assert_raises ::Subroutine::Outputs::OutputNotSetError do
|
57
|
+
op.submit
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_it_does_not_raise_an_error_if_output_is_not_set_and_is_not_required
|
62
|
+
op = OutputNotRequiredOp.new
|
63
|
+
op.submit
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_it_does_not_raise_an_error_if_the_perform_is_not_a_success
|
67
|
+
op = NoOutputNoSuccessOp.new
|
68
|
+
refute op.submit
|
69
|
+
end
|
70
|
+
|
71
|
+
###################
|
72
|
+
# type validation #
|
73
|
+
###################
|
74
|
+
|
75
|
+
def test_it_does_not_raise_an_error_if_output_is_set_to_the_right_type
|
76
|
+
op = OutputWithTypeValidationNotRequired.new
|
77
|
+
op.send(:output, :value, 'foo')
|
78
|
+
assert op.submit
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_it_raises_an_error_if_output_is_not_set_to_the_right_type
|
82
|
+
op = OutputWithTypeValidationNotRequired.new
|
83
|
+
op.send(:output, :value, 1)
|
84
|
+
assert_raises ::Subroutine::Outputs::InvalidOutputTypeError do
|
85
|
+
op.submit
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_it_does_not_raise_an_error_if_output_is_set_to_nil_when_there_is_type_validation_and_not_required
|
90
|
+
op = OutputWithTypeValidationNotRequired.new
|
91
|
+
op.send(:output, :value, nil)
|
92
|
+
op.submit
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_it_raises_an_error_if_output_is_set_to_nil_when_there_is_type_validation_and_is_required
|
96
|
+
op = OutputWithTypeValidationRequired.new
|
97
|
+
op.send(:output, :value, nil)
|
98
|
+
assert_raises ::Subroutine::Outputs::InvalidOutputTypeError do
|
99
|
+
op.submit
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/test/support/ops.rb
CHANGED
@@ -31,6 +31,23 @@ class User
|
|
31
31
|
find_by(params) || raise
|
32
32
|
end
|
33
33
|
|
34
|
+
def self.type_for_attribute(attribute)
|
35
|
+
case attribute
|
36
|
+
when :id
|
37
|
+
Struct.new(:type).new(:integer)
|
38
|
+
else
|
39
|
+
Struct.new(:type).new(:string)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
class StringIdUser < ::User
|
46
|
+
|
47
|
+
def self.type_for_attribute(attribute)
|
48
|
+
Struct.new(:type).new(:string)
|
49
|
+
end
|
50
|
+
|
34
51
|
end
|
35
52
|
|
36
53
|
class AdminUser < ::User
|
@@ -325,6 +342,12 @@ class SimpleAssociationOp < ::OpWithAssociation
|
|
325
342
|
|
326
343
|
end
|
327
344
|
|
345
|
+
class SimpleAssociationWithStringIdOp < ::OpWithAssociation
|
346
|
+
|
347
|
+
association :string_id_user
|
348
|
+
|
349
|
+
end
|
350
|
+
|
328
351
|
class UnscopedSimpleAssociationOp < ::OpWithAssociation
|
329
352
|
|
330
353
|
association :user, unscoped: true, allow_overwrite: true
|
@@ -355,6 +378,12 @@ class AssociationWithFindByKeyOp < ::OpWithAssociation
|
|
355
378
|
|
356
379
|
end
|
357
380
|
|
381
|
+
class AssociationWithImplicitStringFindByOp < ::OpWithAssociation
|
382
|
+
|
383
|
+
association :user, find_by: "email_address"
|
384
|
+
|
385
|
+
end
|
386
|
+
|
358
387
|
class AssociationWithFindByAndForeignKeyOp < ::OpWithAssociation
|
359
388
|
|
360
389
|
association :user, foreign_key: "email_address", find_by: "email_address"
|
@@ -417,41 +446,6 @@ class FalsePerformOp < ::Subroutine::Op
|
|
417
446
|
|
418
447
|
end
|
419
448
|
|
420
|
-
class MissingOutputOp < ::Subroutine::Op
|
421
|
-
|
422
|
-
def perform
|
423
|
-
output :foo, "bar"
|
424
|
-
end
|
425
|
-
|
426
|
-
end
|
427
|
-
|
428
|
-
class MissingOutputSetOp < ::Subroutine::Op
|
429
|
-
|
430
|
-
outputs :foo
|
431
|
-
def perform
|
432
|
-
true
|
433
|
-
end
|
434
|
-
|
435
|
-
end
|
436
|
-
|
437
|
-
class OutputNotRequiredOp < ::Subroutine::Op
|
438
|
-
|
439
|
-
outputs :foo, required: false
|
440
|
-
def perform
|
441
|
-
true
|
442
|
-
end
|
443
|
-
|
444
|
-
end
|
445
|
-
|
446
|
-
class NoOutputNoSuccessOp < ::Subroutine::Op
|
447
|
-
|
448
|
-
outputs :foo
|
449
|
-
def perform
|
450
|
-
errors.add(:foo, "bar")
|
451
|
-
end
|
452
|
-
|
453
|
-
end
|
454
|
-
|
455
449
|
class ErrorTraceOp < ::Subroutine::Op
|
456
450
|
|
457
451
|
class SomeObject
|
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: 2.
|
4
|
+
version: 2.3.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:
|
11
|
+
date: 2023-03-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -157,10 +157,10 @@ executables: []
|
|
157
157
|
extensions: []
|
158
158
|
extra_rdoc_files: []
|
159
159
|
files:
|
160
|
+
- ".github/workflows/build.yml"
|
160
161
|
- ".gitignore"
|
161
162
|
- ".ruby-gemset"
|
162
163
|
- ".ruby-version"
|
163
|
-
- ".travis.yml"
|
164
164
|
- CHANGELOG.MD
|
165
165
|
- Gemfile
|
166
166
|
- LICENSE.txt
|
@@ -187,6 +187,7 @@ files:
|
|
187
187
|
- lib/subroutine/op.rb
|
188
188
|
- lib/subroutine/outputs.rb
|
189
189
|
- lib/subroutine/outputs/configuration.rb
|
190
|
+
- lib/subroutine/outputs/invalid_output_type_error.rb
|
190
191
|
- lib/subroutine/outputs/output_not_set_error.rb
|
191
192
|
- lib/subroutine/outputs/unknown_output_error.rb
|
192
193
|
- lib/subroutine/type_caster.rb
|
@@ -196,6 +197,7 @@ files:
|
|
196
197
|
- test/subroutine/auth_test.rb
|
197
198
|
- test/subroutine/base_test.rb
|
198
199
|
- test/subroutine/fields_test.rb
|
200
|
+
- test/subroutine/outputs_test.rb
|
199
201
|
- test/subroutine/type_caster_test.rb
|
200
202
|
- test/support/ops.rb
|
201
203
|
- test/test_helper.rb
|
@@ -218,7 +220,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
218
220
|
- !ruby/object:Gem::Version
|
219
221
|
version: '0'
|
220
222
|
requirements: []
|
221
|
-
rubygems_version: 3.
|
223
|
+
rubygems_version: 3.3.23
|
222
224
|
signing_key:
|
223
225
|
specification_version: 4
|
224
226
|
summary: Feature-driven operation objects.
|
@@ -233,6 +235,7 @@ test_files:
|
|
233
235
|
- test/subroutine/auth_test.rb
|
234
236
|
- test/subroutine/base_test.rb
|
235
237
|
- test/subroutine/fields_test.rb
|
238
|
+
- test/subroutine/outputs_test.rb
|
236
239
|
- test/subroutine/type_caster_test.rb
|
237
240
|
- test/support/ops.rb
|
238
241
|
- test/test_helper.rb
|
data/.travis.yml
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
sudo: false
|
3
|
-
|
4
|
-
rvm:
|
5
|
-
- 2.4.6
|
6
|
-
- 2.5.5
|
7
|
-
- 2.6.3
|
8
|
-
|
9
|
-
gemfile:
|
10
|
-
- gemfiles/am41.gemfile
|
11
|
-
- gemfiles/am42.gemfile
|
12
|
-
- gemfiles/am50.gemfile
|
13
|
-
- gemfiles/am51.gemfile
|
14
|
-
- gemfiles/am52.gemfile
|
15
|
-
- gemfiles/am60.gemfile
|
16
|
-
|
17
|
-
matrix:
|
18
|
-
exclude:
|
19
|
-
- rvm: 2.4.6
|
20
|
-
gemfile: gemfiles/am60.gemfile
|