subroutine 0.2.1 → 0.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/.gitignore +1 -0
- data/README.md +76 -46
- data/lib/subroutine/association.rb +132 -0
- data/lib/subroutine/auth.rb +97 -0
- data/lib/subroutine/version.rb +2 -2
- data/subroutine.gemspec +1 -0
- data/test/subroutine/association_test.rb +58 -0
- data/test/subroutine/auth_test.rb +69 -0
- data/test/subroutine/type_caster_test.rb +14 -0
- data/test/support/ops.rb +79 -0
- data/test/test_helper.rb +1 -2
- metadata +37 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1eb7328166962d1dfee177ad4311926c7958217
|
4
|
+
data.tar.gz: f5133fc409be40473d1c14a0ae1880b1d007a35d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9bb3b82b53ef32a8f0d236c4e2561d7c56ce5c233f520a06bded564017e32e7d017758f43e0dff4d80c425b476e5ea95562d1f0414b7c26bd4e0d426efa8244
|
7
|
+
data.tar.gz: d4752345399d5ed62c138438ac80050c55de4ca84163d3c2121aed0bd7b404ffd36cc1caf6447d83a28bf570602f007bb5c55d59b70965e7dd6224831f350702
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -343,93 +343,123 @@ op.submit!
|
|
343
343
|
# if the op succeeds nothing will be raised, otherwise a ::Subroutine::Failure will be raised.
|
344
344
|
```
|
345
345
|
|
346
|
-
##
|
346
|
+
## Built-in Extensions
|
347
347
|
|
348
|
-
|
348
|
+
### Subroutine::Association
|
349
|
+
|
350
|
+
The `Subroutine::Association` module provides an interface for loading ActiveRecord instances easily.
|
349
351
|
|
350
352
|
```ruby
|
351
|
-
class
|
353
|
+
class UserUpdateOp < ::Subroutine::Op
|
354
|
+
include ::Subroutine::Association
|
352
355
|
|
353
|
-
|
356
|
+
association :user
|
354
357
|
|
355
|
-
|
356
|
-
params = args.extract_options!
|
357
|
-
@current_user = args[0]
|
358
|
-
super(params)
|
359
|
-
end
|
358
|
+
string :first_name, :last_name
|
360
359
|
|
361
|
-
|
362
|
-
```
|
360
|
+
protected
|
363
361
|
|
364
|
-
|
362
|
+
def perform
|
363
|
+
user.update_attributes(
|
364
|
+
first_name: first_name,
|
365
|
+
last_name: last_name
|
366
|
+
)
|
365
367
|
|
366
|
-
|
367
|
-
|
368
|
-
require_role :admin
|
368
|
+
true
|
369
|
+
end
|
369
370
|
end
|
370
371
|
```
|
371
372
|
|
372
|
-
In the case of a more complex permission system, I'll usually utilize pundit but still standardize the check as a validation.
|
373
|
-
|
374
373
|
```ruby
|
375
|
-
class
|
374
|
+
class RecordTouchOp < ::Subroutine::Op
|
375
|
+
include ::Subroutine::Association
|
376
376
|
|
377
|
-
|
377
|
+
association :record, polymorphic: true
|
378
378
|
|
379
379
|
protected
|
380
380
|
|
381
|
-
|
382
|
-
|
383
|
-
true
|
384
|
-
end
|
381
|
+
def perform
|
382
|
+
record.touch
|
385
383
|
|
386
|
-
|
387
|
-
errors.add(:current_user, :not_authorized)
|
388
|
-
false
|
384
|
+
true
|
389
385
|
end
|
390
386
|
end
|
387
|
+
```
|
388
|
+
|
389
|
+
### Subroutine::Auth
|
390
|
+
|
391
|
+
The `Subroutine::Auth` module provides basic bindings for application authorization. It assumes that, optionally, a `User` will be provided as the first argument to an Op. It forces authorization to be declared on each class it's included in.
|
392
|
+
|
393
|
+
```ruby
|
394
|
+
class SayHiOp < ::Subroutine::Op
|
395
|
+
include ::Subroutine::Auth
|
396
|
+
|
397
|
+
require_user!
|
391
398
|
|
392
|
-
|
399
|
+
string :say_what, default: "hi"
|
393
400
|
|
394
401
|
protected
|
395
402
|
|
396
|
-
def
|
397
|
-
|
398
|
-
return not_authorized!
|
399
|
-
end
|
403
|
+
def perform
|
404
|
+
puts "#{current_user.name} says: #{say_what}"
|
400
405
|
|
401
406
|
true
|
402
407
|
end
|
403
|
-
|
404
408
|
end
|
405
409
|
```
|
406
410
|
|
407
|
-
|
411
|
+
```ruby
|
412
|
+
user = User.find("john")
|
413
|
+
SayHiOp.submit!(user)
|
414
|
+
# => John says: hi
|
415
|
+
|
416
|
+
SayHiOp.submit!(user, say_what: "hello")
|
417
|
+
# => John says: hello
|
418
|
+
|
419
|
+
|
420
|
+
SayHiOp.submit!
|
421
|
+
# => raises Subroutine::Auth::NotAuthorizedError
|
422
|
+
```
|
423
|
+
|
424
|
+
There are a handful of authorization configurations:
|
425
|
+
|
426
|
+
1. `require_user!` - ensures that a user is provided
|
427
|
+
2. `require_no_user!` - ensures that a user is not present
|
428
|
+
3. `no_user_requirements!` - explicitly doesn't matter
|
408
429
|
|
409
|
-
|
430
|
+
In addition to these top-level authorization declarations you can provide custom authorizations like so:
|
410
431
|
|
411
432
|
```ruby
|
412
|
-
class
|
433
|
+
class AccountSetSecretOp < ::Subroutine::Op
|
434
|
+
include ::Subroutine::Auth
|
435
|
+
|
436
|
+
require_user!
|
437
|
+
authorize :authorize_first_name_is_john
|
438
|
+
|
439
|
+
policy :can_set_secret?
|
440
|
+
|
441
|
+
string :secret
|
442
|
+
belongs_to :account
|
413
443
|
|
414
444
|
protected
|
415
445
|
|
416
|
-
def
|
417
|
-
|
418
|
-
|
419
|
-
|
446
|
+
def perform
|
447
|
+
account.secret = secret
|
448
|
+
current_user.save!
|
449
|
+
|
450
|
+
true
|
420
451
|
end
|
421
452
|
|
422
|
-
def
|
423
|
-
|
424
|
-
|
453
|
+
def authorize_first_name_is_john
|
454
|
+
unless current_user.first_name == "john"
|
455
|
+
unauthorized!
|
425
456
|
end
|
426
457
|
end
|
427
458
|
|
428
|
-
def
|
429
|
-
|
430
|
-
yield
|
431
|
-
end
|
459
|
+
def policy
|
460
|
+
::UserPolicy.new(current_user, current_user)
|
432
461
|
end
|
462
|
+
|
433
463
|
end
|
434
464
|
```
|
435
465
|
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Subroutine
|
2
|
+
module Association
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ::Subroutine::Association::ClassMethods
|
6
|
+
class << base
|
7
|
+
alias_method :field_without_associations, :field
|
8
|
+
alias_method :field, :field_with_associations
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
|
16
|
+
def field_with_associations(*args)
|
17
|
+
opts = args.extract_options!
|
18
|
+
if opts[:association]
|
19
|
+
args.each do |arg|
|
20
|
+
association(arg, opts)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
field_without_associations(*args, opts)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# association :user
|
28
|
+
# - user_id
|
29
|
+
# - user_type => "User"
|
30
|
+
|
31
|
+
# association :user, polymorphic: true
|
32
|
+
# - user_id
|
33
|
+
# - user_type
|
34
|
+
# - user => polymorphic_lookup(user_type, user_id)
|
35
|
+
|
36
|
+
# association :inbound_user_request, as: :request
|
37
|
+
# - inbound_user_request_id
|
38
|
+
# - inbound_user_request_type => "InboundUserRequest"
|
39
|
+
# - request => polymorphic_lookup(inbound_user_request_type, inbound_user_request_id)
|
40
|
+
|
41
|
+
# association :inbound_user_request, foreign_key: :request_id
|
42
|
+
# - request_id
|
43
|
+
# - request_type
|
44
|
+
# - inbound_user_request => polymorphic_lookup(request_type, request_id)
|
45
|
+
|
46
|
+
# Other options:
|
47
|
+
# - unscoped => set true if the record should be looked up via Type.unscoped
|
48
|
+
|
49
|
+
def association(field, options = {})
|
50
|
+
|
51
|
+
if options[:as] && options[:foreign_key]
|
52
|
+
raise ArgumentError.new(":as and :foreign_key options should be provided together to an association invocation")
|
53
|
+
end
|
54
|
+
|
55
|
+
class_name = options[:class_name]
|
56
|
+
|
57
|
+
poly = options[:polymorphic] || !class_name.nil?
|
58
|
+
as = options[:as] || field
|
59
|
+
unscoped = !!options[:unscoped]
|
60
|
+
|
61
|
+
klass = class_name.to_s if class_name
|
62
|
+
|
63
|
+
foreign_key_method = (options[:foreign_key] || "#{field}_id").to_s
|
64
|
+
foreign_type_method = foreign_key_method.gsub(/_id$/, "_type")
|
65
|
+
|
66
|
+
|
67
|
+
if poly
|
68
|
+
string foreign_type_method
|
69
|
+
else
|
70
|
+
class_eval <<-EV, __FILE__, __LINE__+1
|
71
|
+
def #{foreign_type_method}
|
72
|
+
#{as.to_s.camelize.inspect}
|
73
|
+
end
|
74
|
+
EV
|
75
|
+
end
|
76
|
+
|
77
|
+
integer foreign_key_method
|
78
|
+
|
79
|
+
field_without_associations as, options.merge(association: true)
|
80
|
+
|
81
|
+
class_eval <<-EV, __FILE__, __LINE__+1
|
82
|
+
def #{as}_with_association
|
83
|
+
return @#{as} if defined?(@#{as})
|
84
|
+
@#{as} = begin
|
85
|
+
#{as}_without_association ||
|
86
|
+
polymorphic_instance(#{klass.nil? ? foreign_type_method : klass.to_s}, #{foreign_key_method}, #{unscoped.inspect})
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def #{as}_with_association=(r)
|
91
|
+
@#{as} = r
|
92
|
+
#{poly || klass ? "params['#{foreign_type_method}'] = r.nil? ? nil : #{klass.nil? ? "r.class.name" : klass.to_s.inspect}" : ""}
|
93
|
+
params['#{foreign_key_method}'] = r.nil? ? nil : r.id
|
94
|
+
r
|
95
|
+
end
|
96
|
+
EV
|
97
|
+
|
98
|
+
alias_method :"#{as}_without_association", :"#{as}"
|
99
|
+
alias_method :"#{as}", :"#{as}_with_association"
|
100
|
+
|
101
|
+
alias_method :"#{as}_without_association=", :"#{as}="
|
102
|
+
alias_method :"#{as}=", :"#{as}_with_association="
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def initialize(*args)
|
108
|
+
super(*args)
|
109
|
+
|
110
|
+
self._fields.each_pair do |field, config|
|
111
|
+
next unless config[:association]
|
112
|
+
next unless @original_params.has_key?(field)
|
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.camelize.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
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Subroutine
|
2
|
+
module Auth
|
3
|
+
|
4
|
+
class NotAuthorizedError < ::StandardError
|
5
|
+
|
6
|
+
def initialize(msg = nil)
|
7
|
+
msg = I18n.t("errors.#{msg}", default: "Sorry, you are not authorized to perform this action.") if msg.is_a?(Symbol)
|
8
|
+
msg ||= I18n.t('errors.unauthorized', default: "Sorry, you are not authorized to perform this action.")
|
9
|
+
super msg
|
10
|
+
end
|
11
|
+
|
12
|
+
def status
|
13
|
+
401
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.included(base)
|
19
|
+
base.instance_eval do
|
20
|
+
extend ::Subroutine::Auth::ClassMethods
|
21
|
+
|
22
|
+
class_attribute :authorization_declared
|
23
|
+
self.authorization_declared = false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
|
30
|
+
def authorize(validation_name)
|
31
|
+
validate validation_name, unless: :skip_auth_checks?
|
32
|
+
end
|
33
|
+
|
34
|
+
def no_user_requirements!
|
35
|
+
self.authorization_declared = true
|
36
|
+
end
|
37
|
+
|
38
|
+
def require_user!
|
39
|
+
self.authorization_declared = true
|
40
|
+
|
41
|
+
validate unless: :skip_auth_checks? do
|
42
|
+
unauthorized! unless current_user.present?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def require_no_user!
|
47
|
+
self.authorization_declared = true
|
48
|
+
|
49
|
+
validate unless: :skip_auth_checks? do
|
50
|
+
unauthorized! :empty_unauthorized if current_user.present?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# policy :can_update_user
|
55
|
+
# policy :can_do_whatever, policy: :foo_policy
|
56
|
+
def policy(*meths)
|
57
|
+
opts = meths.extract_options!
|
58
|
+
policy_name = opts[:policy] || :policy
|
59
|
+
validate unless: :skip_auth_checks? do
|
60
|
+
p = self.send(policy_name)
|
61
|
+
if !p || meths.any?{|m| !(p.respond_to?("#{m}?") ? p.send("#{m}?") : p.send(m)) }
|
62
|
+
unauthorized! opts[:error]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(*args)
|
70
|
+
raise "Authorization management has not been declared on this class" if(!self.class.authorization_declared)
|
71
|
+
|
72
|
+
super(args.extract_options!)
|
73
|
+
@skip_auth_checks = false
|
74
|
+
@current_user = args.shift
|
75
|
+
end
|
76
|
+
|
77
|
+
def skip_auth_checks!
|
78
|
+
@skip_auth_checks = true
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def skip_auth_checks?
|
83
|
+
!!@skip_auth_checks
|
84
|
+
end
|
85
|
+
|
86
|
+
def current_user
|
87
|
+
@current_user = ::User.find(@current_user) if Fixnum === @current_user
|
88
|
+
@current_user
|
89
|
+
end
|
90
|
+
|
91
|
+
def unauthorized!(reason = nil)
|
92
|
+
reason ||= :unauthorized
|
93
|
+
raise ::Subroutine::Auth::NotAuthorizedError.new(reason)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
data/lib/subroutine/version.rb
CHANGED
data/subroutine.gemspec
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Subroutine
|
4
|
+
class AuthTest < TestCase
|
5
|
+
|
6
|
+
def doug
|
7
|
+
@doug ||= ::User.new(id: 1, email_address: "doug@example.com")
|
8
|
+
end
|
9
|
+
|
10
|
+
def fred
|
11
|
+
@fred ||= ::User.new(id: 2, email_address: "fred@example.com")
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_it_sets_accessors_on_init
|
15
|
+
op = SimpleAssociationOp.new user: doug
|
16
|
+
assert_equal "User", op.user_type
|
17
|
+
assert_equal doug.id, op.user_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_it_looks_up_an_association
|
21
|
+
all_mock = mock
|
22
|
+
|
23
|
+
::User.expects(:all).returns(all_mock)
|
24
|
+
all_mock.expects(:find).with(1).returns(doug)
|
25
|
+
|
26
|
+
op = SimpleAssociationOp.new user_type: "User", user_id: doug.id
|
27
|
+
assert_equal doug, op.user
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_it_allows_an_association_to_be_looked_up_without_default_scoping
|
31
|
+
all_mock = mock
|
32
|
+
unscoped_mock = mock
|
33
|
+
|
34
|
+
::User.expects(:all).returns(all_mock)
|
35
|
+
all_mock.expects(:unscoped).returns(unscoped_mock)
|
36
|
+
unscoped_mock.expects(:find).with(1).returns(doug)
|
37
|
+
|
38
|
+
op = UnscopedSimpleAssociationOp.new user_type: "User", user_id: doug.id
|
39
|
+
assert_equal doug, op.user
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_it_allows_polymorphic_associations
|
43
|
+
all_mock = mock
|
44
|
+
::User.expects(:all).never
|
45
|
+
::AdminUser.expects(:all).returns(all_mock)
|
46
|
+
all_mock.expects(:find).with(1).returns(doug)
|
47
|
+
|
48
|
+
op = PolymorphicAssociationOp.new(admin_type: "AdminUser", admin_id: doug.id)
|
49
|
+
assert_equal doug, op.admin
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_it_allows_the_class_to_be_set
|
53
|
+
op = ::AssociationWithClassOp.new(admin: doug)
|
54
|
+
assert_equal "AdminUser", op.admin_type
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Subroutine
|
4
|
+
class AuthTest < TestCase
|
5
|
+
|
6
|
+
def user
|
7
|
+
@user ||= ::User.new(email_address: "doug@example.com")
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_it_throws_an_error_if_authorization_is_not_defined
|
11
|
+
assert_raises "Authorization management has not been declared on this class" do
|
12
|
+
MissingAuthOp.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_it_throws_an_error_if_require_user_but_none_is_provided
|
17
|
+
assert_raises ::Subroutine::Auth::NotAuthorizedError do
|
18
|
+
RequireUserOp.submit!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_it_does_not_throw_an_error_if_require_user_but_none_is_provided
|
23
|
+
RequireUserOp.submit! user
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_it_throws_an_error_if_require_no_user_but_one_is_present
|
27
|
+
assert_raises ::Subroutine::Auth::NotAuthorizedError do
|
28
|
+
RequireNoUserOp.submit! user
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_it_does_not_throw_an_error_if_require_no_user_and_none_is_provided
|
33
|
+
RequireNoUserOp.submit!
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_it_does_not_throw_an_error_if_no_user_requirements_and_one_is_provided
|
37
|
+
NoUserRequirementsOp.submit! user
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_it_does_not_throw_an_error_if_no_user_requirements_and_none_is_provided
|
41
|
+
NoUserRequirementsOp.submit!
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_it_runs_custom_authorizations
|
45
|
+
CustomAuthorizeOp.submit! user
|
46
|
+
|
47
|
+
assert_raises ::Subroutine::Auth::NotAuthorizedError do
|
48
|
+
CustomAuthorizeOp.submit! User.new(email_address: "foo@bar.com")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_it_does_not_run_authorizations_if_explicitly_bypassed
|
53
|
+
op = CustomAuthorizeOp.new User.new(email_address: "foo@bar.com")
|
54
|
+
op.skip_auth_checks!
|
55
|
+
op.submit!
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_it_runs_policies_as_part_of_authorization
|
59
|
+
assert_raises ::Subroutine::Auth::NotAuthorizedError do
|
60
|
+
PolicyOp.submit! user
|
61
|
+
end
|
62
|
+
|
63
|
+
op = PolicyOp.new
|
64
|
+
op.skip_auth_checks!
|
65
|
+
op.submit!
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -220,5 +220,19 @@ module Subroutine
|
|
220
220
|
assert_equal "2022-12-22T10:30:24Z", op.iso_time_input
|
221
221
|
end
|
222
222
|
|
223
|
+
def test_field_provided
|
224
|
+
op = ::SignupOp.new()
|
225
|
+
assert_equal false, op.send(:field_provided?, :email)
|
226
|
+
|
227
|
+
op = ::SignupOp.new(email: "foo")
|
228
|
+
assert_equal true, op.send(:field_provided?, :email)
|
229
|
+
|
230
|
+
op = ::DefaultsOp.new()
|
231
|
+
assert_equal false, op.send(:field_provided?, :foo)
|
232
|
+
|
233
|
+
op = ::DefaultsOp.new(foo: "foo")
|
234
|
+
assert_equal true, op.send(:field_provided?, :foo)
|
235
|
+
end
|
236
|
+
|
223
237
|
end
|
224
238
|
end
|
data/test/support/ops.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
require "subroutine/auth"
|
2
|
+
require "subroutine/association"
|
3
|
+
|
1
4
|
## Models ##
|
2
5
|
|
3
6
|
class User
|
4
7
|
include ::ActiveModel::Model
|
5
8
|
|
9
|
+
attr_accessor :id
|
6
10
|
attr_accessor :email_address
|
7
11
|
attr_accessor :password
|
8
12
|
|
@@ -109,3 +113,78 @@ class TypeCastOp < ::Subroutine::Op
|
|
109
113
|
array :array_input, :default => 'foo'
|
110
114
|
|
111
115
|
end
|
116
|
+
|
117
|
+
class OpWithAuth < ::Subroutine::Op
|
118
|
+
include ::Subroutine::Auth
|
119
|
+
def perform
|
120
|
+
true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class MissingAuthOp < OpWithAuth
|
125
|
+
end
|
126
|
+
|
127
|
+
class RequireUserOp < OpWithAuth
|
128
|
+
require_user!
|
129
|
+
end
|
130
|
+
|
131
|
+
class RequireNoUserOp < OpWithAuth
|
132
|
+
require_no_user!
|
133
|
+
end
|
134
|
+
|
135
|
+
class NoUserRequirementsOp < OpWithAuth
|
136
|
+
no_user_requirements!
|
137
|
+
end
|
138
|
+
|
139
|
+
class CustomAuthorizeOp < OpWithAuth
|
140
|
+
|
141
|
+
require_user!
|
142
|
+
authorize :authorize_user_is_correct
|
143
|
+
|
144
|
+
protected
|
145
|
+
|
146
|
+
def authorize_user_is_correct
|
147
|
+
unless current_user.email_address.to_s =~ /example\.com$/
|
148
|
+
unauthorized!
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class PolicyOp < OpWithAuth
|
154
|
+
|
155
|
+
class FakePolicy
|
156
|
+
def user_can_access?
|
157
|
+
false
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
require_user!
|
162
|
+
policy :user_can_access?
|
163
|
+
|
164
|
+
def policy
|
165
|
+
@policy ||= FakePolicy.new
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
class OpWithAssociation < ::Subroutine::Op
|
171
|
+
include ::Subroutine::Association
|
172
|
+
end
|
173
|
+
|
174
|
+
class SimpleAssociationOp < ::OpWithAssociation
|
175
|
+
|
176
|
+
association :user
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
class UnscopedSimpleAssociationOp < ::OpWithAssociation
|
181
|
+
association :user, unscoped: true
|
182
|
+
end
|
183
|
+
|
184
|
+
class PolymorphicAssociationOp < ::OpWithAssociation
|
185
|
+
association :admin, polymorphic: true
|
186
|
+
end
|
187
|
+
|
188
|
+
class AssociationWithClassOp < ::OpWithAssociation
|
189
|
+
association :admin, class_name: "AdminUser"
|
190
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -3,11 +3,10 @@ require 'minitest/autorun'
|
|
3
3
|
require 'minitest/unit'
|
4
4
|
|
5
5
|
require 'minitest/reporters'
|
6
|
+
require 'mocha/mini_test'
|
6
7
|
|
7
8
|
Minitest::Reporters.use!([Minitest::Reporters::DefaultReporter.new])
|
8
9
|
|
9
10
|
class TestCase < (MiniTest::Unit::TestCase rescue ::MiniTest::Test); end
|
10
11
|
|
11
12
|
require_relative 'support/ops'
|
12
|
-
|
13
|
-
|
metadata
CHANGED
@@ -1,83 +1,97 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: subroutine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.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: 2017-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: 4.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 4.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - ~>
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '1.7'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - ~>
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.7'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - ~>
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '10.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - ~>
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '10.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: minitest-reporters
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: mocha
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '0'
|
83
97
|
description: An interface for creating feature-driven operations.
|
@@ -87,8 +101,8 @@ executables: []
|
|
87
101
|
extensions: []
|
88
102
|
extra_rdoc_files: []
|
89
103
|
files:
|
90
|
-
- .gitignore
|
91
|
-
- .travis.yml
|
104
|
+
- ".gitignore"
|
105
|
+
- ".travis.yml"
|
92
106
|
- Gemfile
|
93
107
|
- LICENSE.txt
|
94
108
|
- README.md
|
@@ -100,12 +114,16 @@ files:
|
|
100
114
|
- gemfiles/am41.gemfile
|
101
115
|
- gemfiles/am42.gemfile
|
102
116
|
- lib/subroutine.rb
|
117
|
+
- lib/subroutine/association.rb
|
118
|
+
- lib/subroutine/auth.rb
|
103
119
|
- lib/subroutine/failure.rb
|
104
120
|
- lib/subroutine/filtered_errors.rb
|
105
121
|
- lib/subroutine/op.rb
|
106
122
|
- lib/subroutine/type_caster.rb
|
107
123
|
- lib/subroutine/version.rb
|
108
124
|
- subroutine.gemspec
|
125
|
+
- test/subroutine/association_test.rb
|
126
|
+
- test/subroutine/auth_test.rb
|
109
127
|
- test/subroutine/base_test.rb
|
110
128
|
- test/subroutine/type_caster_test.rb
|
111
129
|
- test/support/ops.rb
|
@@ -120,17 +138,17 @@ require_paths:
|
|
120
138
|
- lib
|
121
139
|
required_ruby_version: !ruby/object:Gem::Requirement
|
122
140
|
requirements:
|
123
|
-
- -
|
141
|
+
- - ">="
|
124
142
|
- !ruby/object:Gem::Version
|
125
143
|
version: '0'
|
126
144
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
145
|
requirements:
|
128
|
-
- -
|
146
|
+
- - ">="
|
129
147
|
- !ruby/object:Gem::Version
|
130
148
|
version: '0'
|
131
149
|
requirements: []
|
132
150
|
rubyforge_project:
|
133
|
-
rubygems_version: 2.
|
151
|
+
rubygems_version: 2.6.8
|
134
152
|
signing_key:
|
135
153
|
specification_version: 4
|
136
154
|
summary: Feature-driven operation objects.
|
@@ -141,6 +159,8 @@ test_files:
|
|
141
159
|
- gemfiles/am40.gemfile
|
142
160
|
- gemfiles/am41.gemfile
|
143
161
|
- gemfiles/am42.gemfile
|
162
|
+
- test/subroutine/association_test.rb
|
163
|
+
- test/subroutine/auth_test.rb
|
144
164
|
- test/subroutine/base_test.rb
|
145
165
|
- test/subroutine/type_caster_test.rb
|
146
166
|
- test/support/ops.rb
|