subroutine 2.0.0.beta → 2.1.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/.ruby-version +1 -1
- data/CHANGELOG.MD +7 -1
- data/lib/subroutine/association_fields/configuration.rb +5 -2
- data/lib/subroutine/auth.rb +60 -32
- data/lib/subroutine/fields/configuration.rb +1 -0
- data/lib/subroutine/type_caster.rb +9 -0
- data/lib/subroutine/version.rb +2 -2
- data/test/subroutine/association_test.rb +18 -6
- data/test/subroutine/auth_test.rb +14 -0
- data/test/subroutine/type_caster_test.rb +23 -0
- data/test/support/ops.rb +4 -2
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be1eef70ae231ae5b344cf1400ed9ea17a60df49fd1243eb744c13e4aca5904e
|
4
|
+
data.tar.gz: 21f60843110c2b5f6c70281dc1999ed84923848f805cbd08472cb7a863a00a59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cb5596227a06ddfdcca54205bb0c599919ffae5f29ef30ab25d437e7cf8cb03462c99015c957fdfce8d2a7a3ed910b013da7e84f6516a9f37a34a44fa44734b
|
7
|
+
data.tar.gz: 141e1f223f7da3dc7d080cbc891cd9533a905833ceb8a85e0941b06b6455cf83dea42aabe9f3eeaf977966ea227ead94446462c86ad28261b37436b389152c45
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.5
|
1
|
+
2.7.5
|
data/CHANGELOG.MD
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## Subroutine 2.0
|
2
|
+
|
3
|
+
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.
|
4
|
+
|
5
|
+
**Note:** 2.0.0 was released with a bug and subsequently yanked. 2.0.1 is the first available 2.x version.
|
6
|
+
|
1
7
|
## Subroutine 1.0
|
2
8
|
|
3
9
|
A massive refactor took place between 0.9 and 1.0, including breaking changes. The goal was to reduce complexity, simplify backtraces, and increase the overall safety and reliability of the library.
|
@@ -81,7 +87,7 @@ class AccountUpdateOp < ::Op
|
|
81
87
|
end
|
82
88
|
```
|
83
89
|
|
84
|
-
ActionController::Parameters from Rails 5+ are now transformed to
|
90
|
+
ActionController::Parameters from Rails 5+ are now transformed to a hash in `Subroutine::Fields` by default. This means strong parameters are essentially unused when passing `Subroutine::Fields`.
|
85
91
|
|
86
92
|
Read more about field management and access in https://github.com/guideline-tech/subroutine/wiki/Param-Usage
|
87
93
|
|
@@ -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)
|
63
|
+
build_child_field(foreign_key_method, type: :foreign_key, foreign_key_type: config[:foreign_key_type])
|
64
64
|
end
|
65
65
|
|
66
66
|
def build_foreign_type_field
|
@@ -78,7 +78,10 @@ module Subroutine
|
|
78
78
|
protected
|
79
79
|
|
80
80
|
def build_child_field(name, opts = {})
|
81
|
-
|
81
|
+
child_opts = inheritable_options
|
82
|
+
child_opts.merge!(opts)
|
83
|
+
child_opts[:association_name] = as
|
84
|
+
ComponentConfiguration.new(name, child_opts)
|
82
85
|
end
|
83
86
|
|
84
87
|
end
|
data/lib/subroutine/auth.rb
CHANGED
@@ -9,11 +9,13 @@ module Subroutine
|
|
9
9
|
extend ActiveSupport::Concern
|
10
10
|
|
11
11
|
included do
|
12
|
-
class_attribute :
|
13
|
-
self.
|
12
|
+
class_attribute :authorization_checks
|
13
|
+
self.authorization_checks = []
|
14
14
|
|
15
15
|
class_attribute :user_class_name, instance_writer: false
|
16
16
|
self.user_class_name = "User"
|
17
|
+
|
18
|
+
validate :validate_authorization_checks, unless: :skip_auth_checks?
|
17
19
|
end
|
18
20
|
|
19
21
|
module ClassMethods
|
@@ -22,28 +24,24 @@ module Subroutine
|
|
22
24
|
[user_class_name, "Integer", "NilClass"].compact
|
23
25
|
end
|
24
26
|
|
25
|
-
def
|
26
|
-
|
27
|
+
def authorization_declared?
|
28
|
+
authorization_checks.any?
|
29
|
+
end
|
30
|
+
|
31
|
+
def authorize(check_name)
|
32
|
+
self.authorization_checks += [check_name.to_sym]
|
27
33
|
end
|
28
34
|
|
29
35
|
def no_user_requirements!
|
30
|
-
|
36
|
+
authorize :authorize_user_not_required
|
31
37
|
end
|
32
38
|
|
33
39
|
def require_user!
|
34
|
-
|
35
|
-
|
36
|
-
validate unless: :skip_auth_checks? do
|
37
|
-
unauthorized! unless current_user.present?
|
38
|
-
end
|
40
|
+
authorize :authorize_user_required
|
39
41
|
end
|
40
42
|
|
41
43
|
def require_no_user!
|
42
|
-
|
43
|
-
|
44
|
-
validate unless: :skip_auth_checks? do
|
45
|
-
unauthorized! :empty_unauthorized if current_user.present?
|
46
|
-
end
|
44
|
+
authorize :authorize_no_user_required
|
47
45
|
end
|
48
46
|
|
49
47
|
# policy :can_update_user
|
@@ -57,33 +55,45 @@ module Subroutine
|
|
57
55
|
if_conditionals = Array(opts[:if])
|
58
56
|
unless_conditionals = Array(opts[:unless])
|
59
57
|
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
meths.each do |meth|
|
59
|
+
normalized_meth = meth.to_s[0...-1] if meth.to_s.end_with?("?")
|
60
|
+
auth_method_name = :"authorize_#{policy_name}_#{normalized_meth}"
|
63
61
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
62
|
+
define_method auth_method_name do
|
63
|
+
run_it = true
|
64
|
+
# http://guides.rubyonrails.org/active_record_validations.html#combining-validation-conditions
|
68
65
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
66
|
+
# The validation only runs when all the :if conditions evaluate to true
|
67
|
+
if if_conditionals.present?
|
68
|
+
run_it &&= if_conditionals.all? { |i| send(i) }
|
69
|
+
end
|
70
|
+
|
71
|
+
# and none of the :unless conditions are evaluated to true.
|
72
|
+
if unless_conditionals.present?
|
73
|
+
run_it &&= unless_conditionals.none? { |u| send(u) }
|
74
|
+
end
|
75
|
+
|
76
|
+
return unless run_it
|
77
|
+
|
78
|
+
p = send(policy_name)
|
79
|
+
unauthorized! unless p
|
73
80
|
|
74
|
-
|
81
|
+
result = if p.respond_to?("#{normalized_meth}?")
|
82
|
+
p.send("#{normalized_meth}?")
|
83
|
+
else
|
84
|
+
p.send(normalized_meth)
|
85
|
+
end
|
75
86
|
|
76
|
-
|
77
|
-
if !p || meths.any? { |m| !(p.respond_to?("#{m}?") ? p.send("#{m}?") : p.send(m)) }
|
78
|
-
unauthorized! opts[:error]
|
87
|
+
unauthorized! opts[:error] unless result
|
79
88
|
end
|
89
|
+
|
90
|
+
authorize auth_method_name
|
80
91
|
end
|
81
92
|
end
|
82
|
-
|
83
93
|
end
|
84
94
|
|
85
95
|
def initialize(*args, &block)
|
86
|
-
raise Subroutine::Auth::AuthorizationNotDeclaredError unless self.class.authorization_declared
|
96
|
+
raise Subroutine::Auth::AuthorizationNotDeclaredError unless self.class.authorization_declared?
|
87
97
|
|
88
98
|
@skip_auth_checks = false
|
89
99
|
|
@@ -124,5 +134,23 @@ module Subroutine
|
|
124
134
|
raise ::Subroutine::Auth::NotAuthorizedError, reason
|
125
135
|
end
|
126
136
|
|
137
|
+
def validate_authorization_checks
|
138
|
+
authorization_checks.each do |check|
|
139
|
+
send(check)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def authorize_user_not_required
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
def authorize_user_required
|
148
|
+
unauthorized! unless current_user.present?
|
149
|
+
end
|
150
|
+
|
151
|
+
def authorize_no_user_required
|
152
|
+
unauthorized! :empty_unauthorized if current_user.present?
|
153
|
+
end
|
154
|
+
|
127
155
|
end
|
128
156
|
end
|
@@ -63,6 +63,15 @@ end
|
|
63
63
|
String(value)
|
64
64
|
end
|
65
65
|
|
66
|
+
::Subroutine::TypeCaster.register :foreign_key do |value, options = {}|
|
67
|
+
next nil if value.blank?
|
68
|
+
|
69
|
+
next ::Subroutine::TypeCaster.cast(value, type: options[:foreign_key_type]) if options[:foreign_key_type]
|
70
|
+
next ::Subroutine::TypeCaster.cast(value, type: :integer) if options[:name] && options[:name].to_s.end_with?("_id")
|
71
|
+
|
72
|
+
value
|
73
|
+
end
|
74
|
+
|
66
75
|
::Subroutine::TypeCaster.register :boolean, :bool do |value, _options = {}|
|
67
76
|
!!(String(value) =~ /^(yes|true|1|ok)$/)
|
68
77
|
end
|
data/lib/subroutine/version.rb
CHANGED
@@ -83,20 +83,32 @@ module Subroutine
|
|
83
83
|
def test_it_allows_foreign_key_to_be_set
|
84
84
|
all_mock = mock
|
85
85
|
::User.expects(:all).returns(all_mock)
|
86
|
-
all_mock.expects(:find_by!).with(id:
|
86
|
+
all_mock.expects(:find_by!).with(id: 10).returns(doug)
|
87
87
|
|
88
|
-
op = ::AssociationWithForeignKeyOp.new(
|
88
|
+
op = ::AssociationWithForeignKeyOp.new(owner_id: 10)
|
89
89
|
assert_equal doug, op.user
|
90
|
-
assert_equal "
|
90
|
+
assert_equal "owner_id", op.field_configurations[:user][:foreign_key]
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_the_foreign_key_is_cast
|
94
|
+
all_mock = mock
|
95
|
+
::User.expects(:all).returns(all_mock)
|
96
|
+
all_mock.expects(:find_by!).with(id: 10).returns(doug)
|
97
|
+
|
98
|
+
op = ::AssociationWithForeignKeyOp.new(owner_id: "10")
|
99
|
+
assert_equal doug, op.user
|
100
|
+
assert_equal 10, op.owner_id
|
101
|
+
assert_equal "owner_id", op.field_configurations[:user][:foreign_key]
|
91
102
|
end
|
92
103
|
|
93
104
|
def test_it_allows_a_foreign_key_and_find_by_to_be_set
|
94
105
|
all_mock = mock
|
95
106
|
::User.expects(:all).returns(all_mock)
|
96
|
-
all_mock.expects(:find_by!).with(email_address:
|
107
|
+
all_mock.expects(:find_by!).with(email_address: "foo@bar.com").returns(doug)
|
97
108
|
|
98
|
-
op = ::AssociationWithFindByAndForeignKeyOp.new(email_address:
|
109
|
+
op = ::AssociationWithFindByAndForeignKeyOp.new(email_address: "foo@bar.com")
|
99
110
|
assert_equal doug, op.user
|
111
|
+
assert_equal "foo@bar.com", op.email_address
|
100
112
|
assert_equal "email_address", op.field_configurations[:user][:find_by]
|
101
113
|
end
|
102
114
|
|
@@ -119,7 +131,7 @@ module Subroutine
|
|
119
131
|
def test_values_are_correct_for_foreign_key_usage
|
120
132
|
op = ::AssociationWithForeignKeyOp.new(user: doug)
|
121
133
|
assert_equal doug, op.user
|
122
|
-
assert_equal doug.id, op.
|
134
|
+
assert_equal doug.id, op.owner_id
|
123
135
|
end
|
124
136
|
|
125
137
|
def test_values_are_correct_for_both_foreign_key_and_find_by_usage
|
@@ -57,6 +57,16 @@ module Subroutine
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
def test_authorization_checks_are_registered_on_the_class
|
61
|
+
assert_equal false, MissingAuthOp.authorization_declared?
|
62
|
+
|
63
|
+
assert_equal true, CustomAuthorizeOp.authorization_declared?
|
64
|
+
assert_equal [:authorize_user_required, :authorize_user_is_correct], CustomAuthorizeOp.authorization_checks
|
65
|
+
|
66
|
+
assert_equal true, NoUserRequirementsOp.authorization_declared?
|
67
|
+
assert_equal [:authorize_user_not_required], NoUserRequirementsOp.authorization_checks
|
68
|
+
end
|
69
|
+
|
60
70
|
def test_the_current_user_can_be_defined_by_an_id
|
61
71
|
user = CustomAuthorizeOp.new(1).current_user
|
62
72
|
assert_equal 1, user.id
|
@@ -92,6 +102,10 @@ module Subroutine
|
|
92
102
|
op.submit!
|
93
103
|
end
|
94
104
|
|
105
|
+
def policy_invocations_are_registered_as_authorization_methods
|
106
|
+
assert PolicyOp.authorization_checks.include?(:authorize_policy_user_can_access)
|
107
|
+
end
|
108
|
+
|
95
109
|
def test_it_runs_policies_with_conditionals
|
96
110
|
# if: false
|
97
111
|
op = IfConditionalPolicyOp.new(user, check_policy: false)
|
@@ -261,5 +261,28 @@ module Subroutine
|
|
261
261
|
op.date_input = "2015-13-01"
|
262
262
|
end
|
263
263
|
end
|
264
|
+
|
265
|
+
def test_foreign_key_inputs
|
266
|
+
op.fk_input_owner_id = nil
|
267
|
+
assert_nil op.fk_input_owner_id
|
268
|
+
|
269
|
+
op.fk_input_owner_id = ""
|
270
|
+
assert_nil op.fk_input_owner_id
|
271
|
+
|
272
|
+
op.fk_input_owner_id = "19402"
|
273
|
+
assert_equal 19402, op.fk_input_owner_id
|
274
|
+
|
275
|
+
op.fk_input_owner_id = "19402.0"
|
276
|
+
assert_equal 19402, op.fk_input_owner_id
|
277
|
+
|
278
|
+
op.fk_input_email_address = nil
|
279
|
+
assert_nil op.fk_input_email_address
|
280
|
+
|
281
|
+
op.fk_input_email_address = ""
|
282
|
+
assert_nil op.fk_input_email_address
|
283
|
+
|
284
|
+
op.fk_input_email_address = "foo@bar.com"
|
285
|
+
assert_equal "foo@bar.com", op.fk_input_email_address
|
286
|
+
end
|
264
287
|
end
|
265
288
|
end
|
data/test/support/ops.rb
CHANGED
@@ -166,6 +166,8 @@ class TypeCastOp < ::Subroutine::Op
|
|
166
166
|
array :array_input, default: "foo"
|
167
167
|
array :type_array_input, of: :integer
|
168
168
|
file :file_input
|
169
|
+
foreign_key :fk_input_owner_id
|
170
|
+
foreign_key :fk_input_email_address, foreign_key_type: :string
|
169
171
|
|
170
172
|
end
|
171
173
|
|
@@ -314,13 +316,13 @@ end
|
|
314
316
|
|
315
317
|
class AssociationWithForeignKeyOp < ::OpWithAssociation
|
316
318
|
|
317
|
-
association :user, foreign_key: "
|
319
|
+
association :user, foreign_key: "owner_id"
|
318
320
|
|
319
321
|
end
|
320
322
|
|
321
323
|
class AssociationWithFindByKeyOp < ::OpWithAssociation
|
322
324
|
|
323
|
-
association :user, find_by: "email_address"
|
325
|
+
association :user, find_by: "email_address", foreign_key_type: :string
|
324
326
|
|
325
327
|
end
|
326
328
|
|
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.1.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: 2022-04-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -214,11 +214,11 @@ 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: '0'
|
220
220
|
requirements: []
|
221
|
-
rubygems_version: 3.
|
221
|
+
rubygems_version: 3.1.6
|
222
222
|
signing_key:
|
223
223
|
specification_version: 4
|
224
224
|
summary: Feature-driven operation objects.
|