subroutine 1.0.1 → 2.0.0.beta
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/lib/subroutine/association_fields.rb +17 -13
- data/lib/subroutine/association_fields/configuration.rb +11 -6
- data/lib/subroutine/fields/configuration.rb +2 -1
- data/lib/subroutine/op.rb +16 -27
- data/lib/subroutine/version.rb +3 -3
- data/test/subroutine/association_test.rb +66 -7
- data/test/subroutine/base_test.rb +12 -0
- data/test/support/ops.rb +38 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0ade77ec5abb9601e1dce7bc53e34baac35c978a41aef3268b57ee5b9def6e5
|
4
|
+
data.tar.gz: c450947886efc34b26b202b52202909f44735a2d5f26a3c1af51fdf93be509c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c171a9bb82076ed4f5e407375d19e423a5e6bba0211192109d300e3b7372d537a0bac3d67ef5c480ef9af34ddd2a849a07fef79459f5597ea97e195cad239fe7
|
7
|
+
data.tar.gz: 52502e8dd504de2d59faa4665ab7c755c9ff6ba8b951e79acaa264c4d257f09a20da58977f1565e8d121023d6a2f5291cd4528eca143d697252b2f6a27d4197b
|
@@ -74,7 +74,7 @@ module Subroutine
|
|
74
74
|
class_eval <<-EV, __FILE__, __LINE__ + 1
|
75
75
|
try(:silence_redefinition_of_method, :#{config.foreign_type_method})
|
76
76
|
def #{config.foreign_type_method}
|
77
|
-
#{config.
|
77
|
+
#{config.inferred_foreign_type.inspect}
|
78
78
|
end
|
79
79
|
EV
|
80
80
|
end
|
@@ -119,7 +119,7 @@ module Subroutine
|
|
119
119
|
if config&.behavior == :association
|
120
120
|
maybe_raise_on_association_type_mismatch!(config, value)
|
121
121
|
set_field(config.foreign_type_method, value&.class&.name, opts) if config.polymorphic?
|
122
|
-
set_field(config.foreign_key_method, value&.
|
122
|
+
set_field(config.foreign_key_method, value&.send(config.find_by), opts)
|
123
123
|
association_cache[config.field_name] = value
|
124
124
|
else
|
125
125
|
if config&.behavior == :association_component
|
@@ -137,10 +137,7 @@ module Subroutine
|
|
137
137
|
stored_result = association_cache[config.field_name]
|
138
138
|
return stored_result unless stored_result.nil?
|
139
139
|
|
140
|
-
|
141
|
-
type = config.field_reader? || !config.polymorphic? ? send(config.foreign_type_method) : get_field(config.foreign_type_method)
|
142
|
-
|
143
|
-
result = fetch_association_instance(type, fk, config.unscoped?)
|
140
|
+
result = fetch_association_instance(config)
|
144
141
|
association_cache[config.field_name] = result
|
145
142
|
else
|
146
143
|
get_field_without_association(field_name)
|
@@ -175,25 +172,32 @@ module Subroutine
|
|
175
172
|
end
|
176
173
|
end
|
177
174
|
|
178
|
-
def fetch_association_instance(
|
179
|
-
|
175
|
+
def fetch_association_instance(config)
|
176
|
+
klass =
|
177
|
+
if config.field_reader?
|
178
|
+
config.polymorphic? ? send(config.foreign_type_method) : config.inferred_foreign_type
|
179
|
+
else
|
180
|
+
get_field(config.foreign_type_method)
|
181
|
+
end
|
180
182
|
|
181
|
-
klass = _type
|
182
183
|
klass = klass.classify.constantize if klass.is_a?(String)
|
183
|
-
|
184
184
|
return nil unless klass
|
185
185
|
|
186
|
+
foreign_key = config.foreign_key_method
|
187
|
+
value = send(foreign_key)
|
188
|
+
return nil unless value
|
189
|
+
|
186
190
|
scope = klass.all
|
187
|
-
scope = scope.unscoped if
|
191
|
+
scope = scope.unscoped if config.unscoped?
|
188
192
|
|
189
|
-
scope.
|
193
|
+
scope.find_by!(config.find_by => value)
|
190
194
|
end
|
191
195
|
|
192
196
|
def maybe_raise_on_association_type_mismatch!(config, record)
|
193
197
|
return if config.polymorphic?
|
194
198
|
return if record.nil?
|
195
199
|
|
196
|
-
klass = config.
|
200
|
+
klass = config.inferred_foreign_type.constantize
|
197
201
|
|
198
202
|
return if record.class <= klass || record.class >= klass
|
199
203
|
|
@@ -10,7 +10,7 @@ module Subroutine
|
|
10
10
|
def validate!
|
11
11
|
super
|
12
12
|
|
13
|
-
if as && foreign_key
|
13
|
+
if config[:as] && foreign_key
|
14
14
|
raise ArgumentError, ":as and :foreign_key options should not be provided together to an association invocation"
|
15
15
|
end
|
16
16
|
end
|
@@ -34,12 +34,13 @@ module Subroutine
|
|
34
34
|
config[:as] || field_name
|
35
35
|
end
|
36
36
|
|
37
|
-
def
|
38
|
-
config[:class_name]&.to_s
|
37
|
+
def foreign_type
|
38
|
+
(config[:foreign_type] || config[:class_name])&.to_s
|
39
39
|
end
|
40
|
+
alias class_name foreign_type
|
40
41
|
|
41
|
-
def
|
42
|
-
|
42
|
+
def inferred_foreign_type
|
43
|
+
foreign_type || as.to_s.camelize
|
43
44
|
end
|
44
45
|
|
45
46
|
def foreign_key
|
@@ -54,8 +55,12 @@ module Subroutine
|
|
54
55
|
foreign_key_method.to_s.gsub(/_id$/, "_type").to_sym
|
55
56
|
end
|
56
57
|
|
58
|
+
def find_by
|
59
|
+
(config[:find_by] || :id).to_sym
|
60
|
+
end
|
61
|
+
|
57
62
|
def build_foreign_key_field
|
58
|
-
build_child_field(foreign_key_method
|
63
|
+
build_child_field(foreign_key_method)
|
59
64
|
end
|
60
65
|
|
61
66
|
def build_foreign_type_field
|
@@ -7,7 +7,7 @@ module Subroutine
|
|
7
7
|
class Configuration < ::SimpleDelegator
|
8
8
|
|
9
9
|
PROTECTED_GROUP_IDENTIFIERS = %i[all original default].freeze
|
10
|
-
INHERITABLE_OPTIONS = %i[mass_assignable field_reader field_writer groups].freeze
|
10
|
+
INHERITABLE_OPTIONS = %i[mass_assignable field_reader field_writer groups aka].freeze
|
11
11
|
NO_GROUPS = [].freeze
|
12
12
|
|
13
13
|
def self.from(field_name, options)
|
@@ -100,6 +100,7 @@ module Subroutine
|
|
100
100
|
groups = nil if groups == false
|
101
101
|
opts[:groups] = Array(groups).map(&:to_sym).presence
|
102
102
|
opts.delete(:group)
|
103
|
+
opts[:aka] = opts[:aka].to_sym if opts[:aka]
|
103
104
|
opts
|
104
105
|
end
|
105
106
|
|
data/lib/subroutine/op.rb
CHANGED
@@ -37,28 +37,11 @@ module Subroutine
|
|
37
37
|
op
|
38
38
|
end
|
39
39
|
|
40
|
-
protected
|
41
|
-
|
42
|
-
def field(field_name, options = {})
|
43
|
-
result = super(field_name, options)
|
44
|
-
|
45
|
-
if options[:aka]
|
46
|
-
Array(options[:aka]).each do |as|
|
47
|
-
self._error_map = _error_map.merge(as.to_sym => field_name.to_sym)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
result
|
52
|
-
end
|
53
|
-
|
54
40
|
end
|
55
41
|
|
56
42
|
class_attribute :_failure_class
|
57
43
|
self._failure_class = Subroutine::Failure
|
58
44
|
|
59
|
-
class_attribute :_error_map
|
60
|
-
self._error_map = {}
|
61
|
-
|
62
45
|
def initialize(inputs = {})
|
63
46
|
setup_fields(inputs)
|
64
47
|
setup_outputs
|
@@ -82,7 +65,7 @@ module Subroutine
|
|
82
65
|
|
83
66
|
if errors.empty?
|
84
67
|
validate_outputs!
|
85
|
-
|
68
|
+
self
|
86
69
|
else
|
87
70
|
raise _failure_class, self
|
88
71
|
end
|
@@ -91,6 +74,7 @@ module Subroutine
|
|
91
74
|
# the action which should be invoked upon form submission (from the controller)
|
92
75
|
def submit
|
93
76
|
submit!
|
77
|
+
true
|
94
78
|
rescue Exception => e
|
95
79
|
if e.respond_to?(:record)
|
96
80
|
inherit_errors(e.record) unless e.record == self
|
@@ -127,18 +111,23 @@ module Subroutine
|
|
127
111
|
raise NotImplementedError
|
128
112
|
end
|
129
113
|
|
130
|
-
|
131
|
-
# returns false so failure cases can end with this invocation
|
132
|
-
def inherit_errors(error_object)
|
114
|
+
def inherit_errors(error_object, prefix: nil)
|
133
115
|
error_object = error_object.errors if error_object.respond_to?(:errors)
|
134
116
|
|
135
|
-
error_object.each do |
|
136
|
-
if
|
137
|
-
|
138
|
-
|
139
|
-
|
117
|
+
error_object.each do |field_name, error|
|
118
|
+
field_name = "#{prefix}#{field_name}" if prefix
|
119
|
+
field_name = field_name.to_sym
|
120
|
+
|
121
|
+
field_config = get_field_config(field_name)
|
122
|
+
field_config ||= begin
|
123
|
+
kv = field_configurations.find { |_k, config| config[:aka] == field_name }
|
124
|
+
kv ? kv.last : nil
|
125
|
+
end
|
126
|
+
|
127
|
+
if field_config
|
128
|
+
errors.add(field_config.field_name, error)
|
140
129
|
else
|
141
|
-
errors.add(:base, error_object.full_message(
|
130
|
+
errors.add(:base, error_object.full_message(field_name, error))
|
142
131
|
end
|
143
132
|
end
|
144
133
|
|
data/lib/subroutine/version.rb
CHANGED
@@ -23,11 +23,21 @@ module Subroutine
|
|
23
23
|
assert_equal doug.id, op.user_id
|
24
24
|
end
|
25
25
|
|
26
|
+
def test_it_can_be_nil
|
27
|
+
op = SimpleAssociationOp.new user: nil
|
28
|
+
assert_nil op.user
|
29
|
+
assert_nil op.user_id
|
30
|
+
|
31
|
+
op = SimpleAssociationOp.new
|
32
|
+
assert_nil op.user
|
33
|
+
assert_nil op.user_id
|
34
|
+
end
|
35
|
+
|
26
36
|
def test_it_looks_up_an_association
|
27
37
|
all_mock = mock
|
28
38
|
|
29
39
|
::User.expects(:all).returns(all_mock)
|
30
|
-
all_mock.expects(:
|
40
|
+
all_mock.expects(:find_by!).with(id: 1).returns(doug)
|
31
41
|
|
32
42
|
op = SimpleAssociationOp.new user_type: "User", user_id: doug.id
|
33
43
|
assert_equal doug, op.user
|
@@ -37,7 +47,7 @@ module Subroutine
|
|
37
47
|
all_mock = mock
|
38
48
|
|
39
49
|
::User.expects(:all).returns(all_mock)
|
40
|
-
all_mock.expects(:
|
50
|
+
all_mock.expects(:find_by!).with(id: 1).returns(doug)
|
41
51
|
|
42
52
|
op = SimpleAssociationOp.new user_id: doug.id
|
43
53
|
assert_equal doug, op.user
|
@@ -49,7 +59,7 @@ module Subroutine
|
|
49
59
|
|
50
60
|
::User.expects(:all).returns(all_mock)
|
51
61
|
all_mock.expects(:unscoped).returns(unscoped_mock)
|
52
|
-
unscoped_mock.expects(:
|
62
|
+
unscoped_mock.expects(:find_by!).with(id: 1).returns(doug)
|
53
63
|
|
54
64
|
op = UnscopedSimpleAssociationOp.new user_id: doug.id
|
55
65
|
assert_equal doug, op.user
|
@@ -59,7 +69,7 @@ module Subroutine
|
|
59
69
|
all_mock = mock
|
60
70
|
::User.expects(:all).never
|
61
71
|
::AdminUser.expects(:all).returns(all_mock)
|
62
|
-
all_mock.expects(:
|
72
|
+
all_mock.expects(:find_by!).with(id: 1).returns(doug)
|
63
73
|
|
64
74
|
op = PolymorphicAssociationOp.new(admin_type: "AdminUser", admin_id: doug.id)
|
65
75
|
assert_equal doug, op.admin
|
@@ -70,11 +80,60 @@ module Subroutine
|
|
70
80
|
assert_equal "AdminUser", op.admin_type
|
71
81
|
end
|
72
82
|
|
83
|
+
def test_it_allows_foreign_key_to_be_set
|
84
|
+
all_mock = mock
|
85
|
+
::User.expects(:all).returns(all_mock)
|
86
|
+
all_mock.expects(:find_by!).with(id: "foobarbaz").returns(doug)
|
87
|
+
|
88
|
+
op = ::AssociationWithForeignKeyOp.new(user_identifier: "foobarbaz")
|
89
|
+
assert_equal doug, op.user
|
90
|
+
assert_equal "user_identifier", op.field_configurations[:user][:foreign_key]
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_it_allows_a_foreign_key_and_find_by_to_be_set
|
94
|
+
all_mock = mock
|
95
|
+
::User.expects(:all).returns(all_mock)
|
96
|
+
all_mock.expects(:find_by!).with(email_address: doug.email_address).returns(doug)
|
97
|
+
|
98
|
+
op = ::AssociationWithFindByAndForeignKeyOp.new(email_address: doug.email_address)
|
99
|
+
assert_equal doug, op.user
|
100
|
+
assert_equal "email_address", op.field_configurations[:user][:find_by]
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_it_allows_a_find_by_to_be_set
|
104
|
+
all_mock = mock
|
105
|
+
::User.expects(:all).returns(all_mock)
|
106
|
+
all_mock.expects(:find_by!).with(email_address: doug.email_address).returns(doug)
|
107
|
+
|
108
|
+
op = ::AssociationWithFindByKeyOp.new(user_id: doug.email_address)
|
109
|
+
assert_equal doug, op.user
|
110
|
+
assert_equal "email_address", op.field_configurations[:user][:find_by]
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_values_are_correct_for_find_by_usage
|
114
|
+
op = ::AssociationWithFindByKeyOp.new(user: doug)
|
115
|
+
assert_equal doug, op.user
|
116
|
+
assert_equal doug.email_address, op.user_id
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_values_are_correct_for_foreign_key_usage
|
120
|
+
op = ::AssociationWithForeignKeyOp.new(user: doug)
|
121
|
+
assert_equal doug, op.user
|
122
|
+
assert_equal doug.id, op.user_identifier
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_values_are_correct_for_both_foreign_key_and_find_by_usage
|
126
|
+
op = ::AssociationWithFindByAndForeignKeyOp.new(user: doug)
|
127
|
+
assert_equal doug, op.user
|
128
|
+
assert_equal doug.email_address, op.email_address
|
129
|
+
assert_equal false, op.respond_to?(:user_id)
|
130
|
+
end
|
131
|
+
|
73
132
|
def test_it_inherits_associations_via_fields_from
|
74
133
|
all_mock = mock
|
75
134
|
|
76
135
|
::User.expects(:all).returns(all_mock)
|
77
|
-
all_mock.expects(:
|
136
|
+
all_mock.expects(:find_by!).with(id: 1).returns(doug)
|
78
137
|
|
79
138
|
op = ::InheritedSimpleAssociation.new(user_type: "User", user_id: doug.id)
|
80
139
|
assert_equal doug, op.user
|
@@ -88,7 +147,7 @@ module Subroutine
|
|
88
147
|
|
89
148
|
::User.expects(:all).returns(all_mock)
|
90
149
|
all_mock.expects(:unscoped).returns(unscoped_mock)
|
91
|
-
unscoped_mock.expects(:
|
150
|
+
unscoped_mock.expects(:find_by!).with(id: 1).returns(doug)
|
92
151
|
|
93
152
|
op = ::InheritedUnscopedAssociation.new(user_type: "User", user_id: doug.id)
|
94
153
|
assert_equal doug, op.user
|
@@ -100,7 +159,7 @@ module Subroutine
|
|
100
159
|
all_mock = mock
|
101
160
|
::User.expects(:all).never
|
102
161
|
::AdminUser.expects(:all).returns(all_mock)
|
103
|
-
all_mock.expects(:
|
162
|
+
all_mock.expects(:find_by!).with(id: 1).returns(doug)
|
104
163
|
|
105
164
|
op = ::InheritedPolymorphicAssociationOp.new(admin_type: "AdminUser", admin_id: doug.id)
|
106
165
|
assert_equal doug, op.admin
|
@@ -127,6 +127,13 @@ module Subroutine
|
|
127
127
|
assert_equal ["has gotta be @admin.com"], op.errors[:email]
|
128
128
|
end
|
129
129
|
|
130
|
+
def test_validation_errors_can_be_inherited_and_prefixed
|
131
|
+
op = PrefixedInputsOp.new(user_email_address: "foo@bar.com")
|
132
|
+
refute op.submit
|
133
|
+
|
134
|
+
assert_equal ["has gotta be @admin.com"], op.errors[:user_email_address]
|
135
|
+
end
|
136
|
+
|
130
137
|
def test_when_valid_perform_completes_it_returns_control
|
131
138
|
op = ::SignupOp.new(email: "foo@bar.com", password: "password123")
|
132
139
|
op.submit!
|
@@ -139,6 +146,11 @@ module Subroutine
|
|
139
146
|
assert_equal "foo@bar.com", u.email_address
|
140
147
|
end
|
141
148
|
|
149
|
+
def test_instance_and_class_submit_bang_return_instance
|
150
|
+
assert ::SignupOp.new(email: "foo@bar.com", password: "password123").submit!.is_a?(::SignupOp)
|
151
|
+
assert ::SignupOp.submit!(email: "foo@bar.com", password: "password123").is_a?(::SignupOp)
|
152
|
+
end
|
153
|
+
|
142
154
|
def test_it_raises_an_error_when_used_with_a_bang_and_performing_or_validation_fails
|
143
155
|
op = ::SignupOp.new(email: "foo@bar.com")
|
144
156
|
|
data/test/support/ops.rb
CHANGED
@@ -23,6 +23,14 @@ class User
|
|
23
23
|
new(id: id)
|
24
24
|
end
|
25
25
|
|
26
|
+
def self.find_by(params)
|
27
|
+
new(params)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.find_by!(params)
|
31
|
+
find_by(params) || raise
|
32
|
+
end
|
33
|
+
|
26
34
|
end
|
27
35
|
|
28
36
|
class AdminUser < ::User
|
@@ -304,6 +312,24 @@ class AssociationWithClassOp < ::OpWithAssociation
|
|
304
312
|
|
305
313
|
end
|
306
314
|
|
315
|
+
class AssociationWithForeignKeyOp < ::OpWithAssociation
|
316
|
+
|
317
|
+
association :user, foreign_key: "user_identifier"
|
318
|
+
|
319
|
+
end
|
320
|
+
|
321
|
+
class AssociationWithFindByKeyOp < ::OpWithAssociation
|
322
|
+
|
323
|
+
association :user, find_by: "email_address"
|
324
|
+
|
325
|
+
end
|
326
|
+
|
327
|
+
class AssociationWithFindByAndForeignKeyOp < ::OpWithAssociation
|
328
|
+
|
329
|
+
association :user, foreign_key: "email_address", find_by: "email_address"
|
330
|
+
|
331
|
+
end
|
332
|
+
|
307
333
|
class ExceptAssociationOp < ::Subroutine::Op
|
308
334
|
|
309
335
|
fields_from ::PolymorphicAssociationOp, except: %i[admin]
|
@@ -447,3 +473,15 @@ class CustomFailureClassOp < ::Subroutine::Op
|
|
447
473
|
end
|
448
474
|
|
449
475
|
end
|
476
|
+
|
477
|
+
class PrefixedInputsOp < ::Subroutine::Op
|
478
|
+
|
479
|
+
string :user_email_address
|
480
|
+
|
481
|
+
def perform
|
482
|
+
u = AdminUser.new(email_address: user_email_address)
|
483
|
+
u.valid?
|
484
|
+
inherit_errors(u, prefix: :user_)
|
485
|
+
end
|
486
|
+
|
487
|
+
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:
|
4
|
+
version: 2.0.0.beta
|
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: 2021-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -214,9 +214,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
214
214
|
version: '0'
|
215
215
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
216
216
|
requirements:
|
217
|
-
- - "
|
217
|
+
- - ">"
|
218
218
|
- !ruby/object:Gem::Version
|
219
|
-
version:
|
219
|
+
version: 1.3.1
|
220
220
|
requirements: []
|
221
221
|
rubygems_version: 3.0.6
|
222
222
|
signing_key:
|