subroutine 0.10.0.beta → 0.10.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.ruby-version +1 -1
- data/Gemfile +4 -2
- data/lib/subroutine/association_fields/component_configuration.rb +19 -0
- data/lib/subroutine/association_fields/configuration.rb +74 -0
- data/lib/subroutine/association_fields.rb +158 -0
- data/lib/subroutine/auth.rb +21 -16
- data/lib/subroutine/fields/configuration.rb +90 -0
- data/lib/subroutine/fields/mass_assignment_error.rb +13 -0
- data/lib/subroutine/fields.rb +146 -82
- data/lib/subroutine/op.rb +14 -38
- data/lib/subroutine/outputs/configuration.rb +39 -0
- data/lib/subroutine/outputs/output_not_set_error.rb +13 -0
- data/lib/subroutine/outputs/unknown_output_error.rb +13 -0
- data/lib/subroutine/outputs.rb +66 -0
- data/lib/subroutine/version.rb +4 -2
- data/test/subroutine/association_test.rb +18 -17
- data/test/subroutine/auth_test.rb +11 -4
- data/test/subroutine/base_test.rb +70 -58
- data/test/subroutine/fields_test.rb +49 -12
- data/test/support/ops.rb +113 -16
- metadata +12 -7
- data/lib/subroutine/association.rb +0 -131
- data/lib/subroutine/output_not_set_error.rb +0 -9
- data/lib/subroutine/unknown_output_error.rb +0 -9
data/test/support/ops.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "subroutine/auth"
|
4
|
+
require "subroutine/association_fields"
|
5
5
|
|
6
6
|
## Models ##
|
7
7
|
|
8
8
|
class User
|
9
|
+
|
9
10
|
include ::ActiveModel::Model
|
10
11
|
|
11
12
|
attr_accessor :id
|
@@ -17,15 +18,19 @@ class User
|
|
17
18
|
def self.find(id)
|
18
19
|
new(id: id)
|
19
20
|
end
|
21
|
+
|
20
22
|
end
|
21
23
|
|
22
24
|
class AdminUser < ::User
|
23
|
-
|
25
|
+
|
26
|
+
validates :email_address, format: { with: /@admin\.com/, message: "has gotta be @admin.com" }
|
27
|
+
|
24
28
|
end
|
25
29
|
|
26
30
|
## Ops ##
|
27
31
|
|
28
32
|
class SignupOp < ::Subroutine::Op
|
33
|
+
|
29
34
|
string :email, aka: :email_address
|
30
35
|
string :password
|
31
36
|
|
@@ -63,43 +68,57 @@ class SignupOp < ::Subroutine::Op
|
|
63
68
|
def user_class
|
64
69
|
::User
|
65
70
|
end
|
71
|
+
|
66
72
|
end
|
67
73
|
|
68
74
|
class AdminSignupOp < ::SignupOp
|
69
|
-
|
75
|
+
|
76
|
+
field :privileges, default: "min"
|
70
77
|
|
71
78
|
protected
|
72
79
|
|
73
80
|
def user_class
|
74
81
|
::AdminUser
|
75
82
|
end
|
83
|
+
|
76
84
|
end
|
77
85
|
|
78
86
|
class BusinessSignupOp < ::Subroutine::Op
|
87
|
+
|
79
88
|
string :business_name
|
80
89
|
|
81
90
|
inputs_from ::SignupOp
|
91
|
+
|
82
92
|
end
|
83
93
|
|
84
94
|
class DefaultsOp < ::Subroutine::Op
|
85
|
-
|
86
|
-
field :
|
95
|
+
|
96
|
+
field :foo, default: "foo"
|
97
|
+
field :bar, default: "bar"
|
87
98
|
field :baz, default: false
|
99
|
+
|
88
100
|
end
|
89
101
|
|
90
102
|
class ExceptFooBarOp < ::Subroutine::Op
|
91
|
-
|
103
|
+
|
104
|
+
inputs_from ::DefaultsOp, except: %i[foo bar]
|
105
|
+
|
92
106
|
end
|
93
107
|
|
94
108
|
class OnlyFooBarOp < ::Subroutine::Op
|
95
|
-
|
109
|
+
|
110
|
+
inputs_from ::DefaultsOp, only: %i[foo bar]
|
111
|
+
|
96
112
|
end
|
97
113
|
|
98
114
|
class InheritedDefaultsOp < ::DefaultsOp
|
99
|
-
|
115
|
+
|
116
|
+
field :bar, default: "barstool", allow_overwrite: true
|
117
|
+
|
100
118
|
end
|
101
119
|
|
102
120
|
class TypeCastOp < ::Subroutine::Op
|
121
|
+
|
103
122
|
integer :integer_input
|
104
123
|
number :number_input
|
105
124
|
decimal :decimal_input
|
@@ -110,39 +129,51 @@ class TypeCastOp < ::Subroutine::Op
|
|
110
129
|
iso_date :iso_date_input
|
111
130
|
iso_time :iso_time_input
|
112
131
|
object :object_input
|
113
|
-
array :array_input, default:
|
132
|
+
array :array_input, default: "foo"
|
114
133
|
array :type_array_input, of: :integer
|
115
134
|
file :file_input
|
135
|
+
|
116
136
|
end
|
117
137
|
|
118
138
|
class OpWithAuth < ::Subroutine::Op
|
139
|
+
|
119
140
|
include ::Subroutine::Auth
|
120
141
|
def perform
|
121
142
|
true
|
122
143
|
end
|
144
|
+
|
123
145
|
end
|
124
146
|
|
125
147
|
class MissingAuthOp < OpWithAuth
|
126
148
|
end
|
127
149
|
|
128
150
|
class RequireUserOp < OpWithAuth
|
151
|
+
|
129
152
|
require_user!
|
153
|
+
|
130
154
|
end
|
131
155
|
|
132
156
|
class RequireNoUserOp < OpWithAuth
|
157
|
+
|
133
158
|
require_no_user!
|
159
|
+
|
134
160
|
end
|
135
161
|
|
136
162
|
class NoUserRequirementsOp < OpWithAuth
|
163
|
+
|
137
164
|
no_user_requirements!
|
165
|
+
|
138
166
|
end
|
139
167
|
|
140
168
|
class DifferentUserClassOp < OpWithAuth
|
169
|
+
|
141
170
|
self.user_class_name = "AdminUser"
|
142
171
|
require_user!
|
172
|
+
|
143
173
|
end
|
144
174
|
|
145
175
|
class CustomAuthorizeOp < OpWithAuth
|
176
|
+
|
146
177
|
require_user!
|
147
178
|
authorize :authorize_user_is_correct
|
148
179
|
|
@@ -151,13 +182,17 @@ class CustomAuthorizeOp < OpWithAuth
|
|
151
182
|
def authorize_user_is_correct
|
152
183
|
unauthorized! unless current_user.email_address.to_s =~ /example\.com$/
|
153
184
|
end
|
185
|
+
|
154
186
|
end
|
155
187
|
|
156
188
|
class PolicyOp < OpWithAuth
|
189
|
+
|
157
190
|
class FakePolicy
|
191
|
+
|
158
192
|
def user_can_access?
|
159
193
|
false
|
160
194
|
end
|
195
|
+
|
161
196
|
end
|
162
197
|
|
163
198
|
require_user!
|
@@ -166,13 +201,17 @@ class PolicyOp < OpWithAuth
|
|
166
201
|
def policy
|
167
202
|
@policy ||= FakePolicy.new
|
168
203
|
end
|
204
|
+
|
169
205
|
end
|
170
206
|
|
171
207
|
class IfConditionalPolicyOp < OpWithAuth
|
208
|
+
|
172
209
|
class FakePolicy
|
210
|
+
|
173
211
|
def user_can_access?
|
174
212
|
false
|
175
213
|
end
|
214
|
+
|
176
215
|
end
|
177
216
|
|
178
217
|
require_user!
|
@@ -183,13 +222,17 @@ class IfConditionalPolicyOp < OpWithAuth
|
|
183
222
|
def policy
|
184
223
|
@policy ||= FakePolicy.new
|
185
224
|
end
|
225
|
+
|
186
226
|
end
|
187
227
|
|
188
228
|
class UnlessConditionalPolicyOp < OpWithAuth
|
229
|
+
|
189
230
|
class FakePolicy
|
231
|
+
|
190
232
|
def user_can_access?
|
191
233
|
false
|
192
234
|
end
|
235
|
+
|
193
236
|
end
|
194
237
|
|
195
238
|
require_user!
|
@@ -200,95 +243,149 @@ class UnlessConditionalPolicyOp < OpWithAuth
|
|
200
243
|
def policy
|
201
244
|
@policy ||= FakePolicy.new
|
202
245
|
end
|
246
|
+
|
203
247
|
end
|
204
248
|
|
205
249
|
class OpWithAssociation < ::Subroutine::Op
|
206
|
-
|
250
|
+
|
251
|
+
include ::Subroutine::AssociationFields
|
252
|
+
|
207
253
|
end
|
208
254
|
|
209
255
|
class SimpleAssociationOp < ::OpWithAssociation
|
256
|
+
|
210
257
|
association :user
|
258
|
+
|
211
259
|
end
|
212
260
|
|
213
261
|
class UnscopedSimpleAssociationOp < ::OpWithAssociation
|
214
|
-
|
262
|
+
|
263
|
+
association :user, unscoped: true, allow_overwrite: true
|
264
|
+
|
215
265
|
end
|
216
266
|
|
217
267
|
class PolymorphicAssociationOp < ::OpWithAssociation
|
268
|
+
|
218
269
|
association :admin, polymorphic: true
|
270
|
+
|
219
271
|
end
|
220
272
|
|
221
273
|
class AssociationWithClassOp < ::OpWithAssociation
|
222
|
-
|
274
|
+
|
275
|
+
association :admin, class_name: "AdminUser"
|
276
|
+
|
223
277
|
end
|
224
278
|
|
225
279
|
class InheritedSimpleAssociation < ::Subroutine::Op
|
280
|
+
|
226
281
|
inputs_from SimpleAssociationOp
|
282
|
+
|
227
283
|
end
|
228
284
|
|
229
285
|
class InheritedUnscopedAssociation < ::Subroutine::Op
|
286
|
+
|
230
287
|
inputs_from UnscopedSimpleAssociationOp
|
288
|
+
|
231
289
|
end
|
232
290
|
|
233
291
|
class InheritedPolymorphicAssociationOp < ::Subroutine::Op
|
292
|
+
|
234
293
|
inputs_from PolymorphicAssociationOp
|
294
|
+
|
235
295
|
end
|
236
296
|
|
237
297
|
class FalsePerformOp < ::Subroutine::Op
|
298
|
+
|
238
299
|
def perform
|
239
300
|
false
|
240
301
|
end
|
302
|
+
|
241
303
|
end
|
242
304
|
|
243
305
|
class MissingOutputOp < ::Subroutine::Op
|
306
|
+
|
244
307
|
def perform
|
245
|
-
output :foo,
|
308
|
+
output :foo, "bar"
|
246
309
|
end
|
310
|
+
|
247
311
|
end
|
248
312
|
|
249
313
|
class MissingOutputSetOp < ::Subroutine::Op
|
314
|
+
|
250
315
|
outputs :foo
|
251
316
|
def perform
|
252
317
|
true
|
253
318
|
end
|
319
|
+
|
254
320
|
end
|
255
321
|
|
256
322
|
class OutputNotRequiredOp < ::Subroutine::Op
|
323
|
+
|
257
324
|
outputs :foo, required: false
|
258
325
|
def perform
|
259
326
|
true
|
260
327
|
end
|
328
|
+
|
261
329
|
end
|
262
330
|
|
263
331
|
class NoOutputNoSuccessOp < ::Subroutine::Op
|
332
|
+
|
264
333
|
outputs :foo
|
265
334
|
def perform
|
266
|
-
errors.add(:foo,
|
335
|
+
errors.add(:foo, "bar")
|
267
336
|
end
|
337
|
+
|
268
338
|
end
|
269
339
|
|
270
340
|
class ErrorTraceOp < ::Subroutine::Op
|
341
|
+
|
271
342
|
class SomeObject
|
343
|
+
|
272
344
|
include ::ActiveModel::Model
|
273
345
|
include ::ActiveModel::Validations::Callbacks
|
274
346
|
|
275
347
|
def foo
|
276
|
-
errors.add(:base,
|
348
|
+
errors.add(:base, "Failure of things")
|
277
349
|
raise Subroutine::Failure, self
|
278
350
|
end
|
279
351
|
|
280
352
|
def bar
|
281
353
|
foo
|
282
354
|
end
|
355
|
+
|
283
356
|
end
|
284
357
|
|
285
358
|
class SubOp < ::Subroutine::Op
|
359
|
+
|
286
360
|
def perform
|
287
361
|
SomeObject.new.bar
|
288
362
|
end
|
363
|
+
|
289
364
|
end
|
290
365
|
|
291
366
|
def perform
|
292
367
|
SubOp.submit!
|
293
368
|
end
|
369
|
+
|
370
|
+
end
|
371
|
+
|
372
|
+
class CustomFailureClassOp < ::Subroutine::Op
|
373
|
+
|
374
|
+
class Failure < StandardError
|
375
|
+
|
376
|
+
attr_reader :record
|
377
|
+
def initialize(record)
|
378
|
+
@record = record
|
379
|
+
errors = @record.errors.full_messages.join(", ")
|
380
|
+
super(errors)
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
|
385
|
+
failure_class Failure
|
386
|
+
|
387
|
+
def perform
|
388
|
+
errors.add(:base, "Will never work")
|
389
|
+
end
|
390
|
+
|
294
391
|
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.10.0.
|
4
|
+
version: 0.10.0.beta2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Nelson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-11-
|
11
|
+
date: 2019-11-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -144,14 +144,20 @@ files:
|
|
144
144
|
- gemfiles/am52.gemfile
|
145
145
|
- gemfiles/am60.gemfile
|
146
146
|
- lib/subroutine.rb
|
147
|
-
- lib/subroutine/
|
147
|
+
- lib/subroutine/association_fields.rb
|
148
|
+
- lib/subroutine/association_fields/component_configuration.rb
|
149
|
+
- lib/subroutine/association_fields/configuration.rb
|
148
150
|
- lib/subroutine/auth.rb
|
149
151
|
- lib/subroutine/failure.rb
|
150
152
|
- lib/subroutine/fields.rb
|
153
|
+
- lib/subroutine/fields/configuration.rb
|
154
|
+
- lib/subroutine/fields/mass_assignment_error.rb
|
151
155
|
- lib/subroutine/op.rb
|
152
|
-
- lib/subroutine/
|
156
|
+
- lib/subroutine/outputs.rb
|
157
|
+
- lib/subroutine/outputs/configuration.rb
|
158
|
+
- lib/subroutine/outputs/output_not_set_error.rb
|
159
|
+
- lib/subroutine/outputs/unknown_output_error.rb
|
153
160
|
- lib/subroutine/type_caster.rb
|
154
|
-
- lib/subroutine/unknown_output_error.rb
|
155
161
|
- lib/subroutine/version.rb
|
156
162
|
- subroutine.gemspec
|
157
163
|
- test/subroutine/association_test.rb
|
@@ -180,8 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
186
|
- !ruby/object:Gem::Version
|
181
187
|
version: 1.3.1
|
182
188
|
requirements: []
|
183
|
-
|
184
|
-
rubygems_version: 2.6.14.4
|
189
|
+
rubygems_version: 3.0.6
|
185
190
|
signing_key:
|
186
191
|
specification_version: 4
|
187
192
|
summary: Feature-driven operation objects.
|
@@ -1,131 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
require "active_support/concern"
|
3
|
-
|
4
|
-
module Subroutine
|
5
|
-
module Association
|
6
|
-
extend ActiveSupport::Concern
|
7
|
-
|
8
|
-
included do
|
9
|
-
class << self
|
10
|
-
alias_method :field_without_associations, :field
|
11
|
-
alias_method :field, :field_with_associations
|
12
|
-
end
|
13
|
-
|
14
|
-
alias_method :setup_fields_without_association, :setup_fields
|
15
|
-
alias_method :setup_fields, :setup_fields_with_association
|
16
|
-
end
|
17
|
-
|
18
|
-
module ClassMethods
|
19
|
-
def field_with_associations(*args)
|
20
|
-
opts = args.extract_options!
|
21
|
-
if opts[:association]
|
22
|
-
args.each do |arg|
|
23
|
-
association(arg, opts)
|
24
|
-
end
|
25
|
-
else
|
26
|
-
field_without_associations(*args, opts)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# association :user
|
31
|
-
# - user_id
|
32
|
-
# - user_type => "User"
|
33
|
-
|
34
|
-
# association :user, polymorphic: true
|
35
|
-
# - user_id
|
36
|
-
# - user_type
|
37
|
-
# - user => polymorphic_lookup(user_type, user_id)
|
38
|
-
|
39
|
-
# association :inbound_user_request, as: :request
|
40
|
-
# - inbound_user_request_id
|
41
|
-
# - inbound_user_request_type => "InboundUserRequest"
|
42
|
-
# - request => polymorphic_lookup(inbound_user_request_type, inbound_user_request_id)
|
43
|
-
|
44
|
-
# association :inbound_user_request, foreign_key: :request_id
|
45
|
-
# - request_id
|
46
|
-
# - request_type
|
47
|
-
# - inbound_user_request => polymorphic_lookup(request_type, request_id)
|
48
|
-
|
49
|
-
# Other options:
|
50
|
-
# - unscoped => set true if the record should be looked up via Type.unscoped
|
51
|
-
|
52
|
-
def association(field, options = {})
|
53
|
-
if options[:as] && options[:foreign_key]
|
54
|
-
raise ArgumentError, ':as and :foreign_key options should be provided together to an association invocation'
|
55
|
-
end
|
56
|
-
|
57
|
-
class_name = options[:class_name]
|
58
|
-
|
59
|
-
poly = options[:polymorphic] || !class_name.nil?
|
60
|
-
as = options[:as] || field
|
61
|
-
unscoped = !!options[:unscoped]
|
62
|
-
|
63
|
-
klass = class_name.to_s if class_name
|
64
|
-
|
65
|
-
foreign_key_method = (options[:foreign_key] || "#{field}_id").to_s
|
66
|
-
foreign_type_method = foreign_key_method.gsub(/_id$/, '_type')
|
67
|
-
|
68
|
-
if poly
|
69
|
-
string foreign_type_method
|
70
|
-
else
|
71
|
-
class_eval <<-EV, __FILE__, __LINE__ + 1
|
72
|
-
def #{foreign_type_method}
|
73
|
-
#{as.to_s.camelize.inspect}
|
74
|
-
end
|
75
|
-
EV
|
76
|
-
end
|
77
|
-
|
78
|
-
integer foreign_key_method
|
79
|
-
|
80
|
-
field_without_associations as, options.merge(association: true, field_writer: false, field_reader: false)
|
81
|
-
|
82
|
-
class_eval <<-EV, __FILE__, __LINE__ + 1
|
83
|
-
try(:silence_redefinition_of_method, :#{as})
|
84
|
-
def #{as}
|
85
|
-
return @#{as} if defined?(@#{as})
|
86
|
-
@#{as} = polymorphic_instance(#{klass.nil? ? foreign_type_method : klass.to_s}, #{foreign_key_method}, #{unscoped.inspect})
|
87
|
-
end
|
88
|
-
|
89
|
-
try(:silence_redefinition_of_method, :#{as}=)
|
90
|
-
def #{as}=(r)
|
91
|
-
@#{as} = r
|
92
|
-
#{poly || klass ? "send('#{foreign_type_method}=', r.nil? ? nil : #{klass.nil? ? 'r.class.name' : klass.to_s.inspect})" : ''}
|
93
|
-
send('#{foreign_key_method}=', r.nil? ? nil : r.id)
|
94
|
-
r
|
95
|
-
end
|
96
|
-
|
97
|
-
try(:silence_redefinition_of_method, :#{as}_field_provided?)
|
98
|
-
def #{as}_field_provided?
|
99
|
-
field_provided?('#{foreign_key_method}')#{poly ? "&& field_provided?('#{foreign_type_method}')" : ""}
|
100
|
-
end
|
101
|
-
EV
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def setup_fields_with_association(*args)
|
106
|
-
setup_fields_without_association(*args)
|
107
|
-
|
108
|
-
_fields.each_pair do |field, config|
|
109
|
-
next unless config[:association]
|
110
|
-
next if config[:mass_assignable] == false
|
111
|
-
next unless @original_params.key?(field)
|
112
|
-
|
113
|
-
send("#{field}=", @original_params[field]) # this gets the _id and _type into the params hash
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
def polymorphic_instance(_type, _id, _unscoped = false)
|
118
|
-
return nil unless _type && _id
|
119
|
-
|
120
|
-
klass = _type
|
121
|
-
klass = klass.classify.constantize if klass.is_a?(String)
|
122
|
-
|
123
|
-
return nil unless klass
|
124
|
-
|
125
|
-
scope = klass.all
|
126
|
-
scope = scope.unscoped if _unscoped
|
127
|
-
|
128
|
-
scope.find(_id)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|