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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0ade77ec5abb9601e1dce7bc53e34baac35c978a41aef3268b57ee5b9def6e5
4
- data.tar.gz: c450947886efc34b26b202b52202909f44735a2d5f26a3c1af51fdf93be509c6
3
+ metadata.gz: be1eef70ae231ae5b344cf1400ed9ea17a60df49fd1243eb744c13e4aca5904e
4
+ data.tar.gz: 21f60843110c2b5f6c70281dc1999ed84923848f805cbd08472cb7a863a00a59
5
5
  SHA512:
6
- metadata.gz: c171a9bb82076ed4f5e407375d19e423a5e6bba0211192109d300e3b7372d537a0bac3d67ef5c480ef9af34ddd2a849a07fef79459f5597ea97e195cad239fe7
7
- data.tar.gz: 52502e8dd504de2d59faa4665ab7c755c9ff6ba8b951e79acaa264c4d257f09a20da58977f1565e8d121023d6a2f5291cd4528eca143d697252b2f6a27d4197b
6
+ metadata.gz: 3cb5596227a06ddfdcca54205bb0c599919ffae5f29ef30ab25d437e7cf8cb03462c99015c957fdfce8d2a7a3ed910b013da7e84f6516a9f37a34a44fa44734b
7
+ data.tar.gz: 141e1f223f7da3dc7d080cbc891cd9533a905833ceb8a85e0941b06b6455cf83dea42aabe9f3eeaf977966ea227ead94446462c86ad28261b37436b389152c45
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.5.1
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 as hash in `Subroutine::Fields` by default. This means strong parameters are essentially unused when passing `Subroutine::Fields`.
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
- ComponentConfiguration.new(name, inheritable_options.merge(opts).merge(association_name: as))
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
@@ -9,11 +9,13 @@ module Subroutine
9
9
  extend ActiveSupport::Concern
10
10
 
11
11
  included do
12
- class_attribute :authorization_declared, instance_writer: false
13
- self.authorization_declared = false
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 authorize(validation_name)
26
- validate validation_name, unless: :skip_auth_checks?
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
- self.authorization_declared = true
36
+ authorize :authorize_user_not_required
31
37
  end
32
38
 
33
39
  def require_user!
34
- self.authorization_declared = true
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
- self.authorization_declared = true
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
- validate unless: :skip_auth_checks? do
61
- run_it = true
62
- # http://guides.rubyonrails.org/active_record_validations.html#combining-validation-conditions
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
- # The validation only runs when all the :if conditions
65
- if if_conditionals.present?
66
- run_it &&= if_conditionals.all? { |i| send(i) }
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
- # and none of the :unless conditions are evaluated to true.
70
- if unless_conditionals.present?
71
- run_it &&= unless_conditionals.none? { |u| send(u) }
72
- end
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
- next unless run_it
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
- p = send(policy_name)
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
@@ -101,6 +101,7 @@ module Subroutine
101
101
  opts[:groups] = Array(groups).map(&:to_sym).presence
102
102
  opts.delete(:group)
103
103
  opts[:aka] = opts[:aka].to_sym if opts[:aka]
104
+ opts[:name] = field_name
104
105
  opts
105
106
  end
106
107
 
@@ -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
@@ -3,9 +3,9 @@
3
3
  module Subroutine
4
4
 
5
5
  MAJOR = 2
6
- MINOR = 0
6
+ MINOR = 1
7
7
  PATCH = 0
8
- PRE = "beta"
8
+ PRE = nil
9
9
 
10
10
  VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
11
11
 
@@ -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: "foobarbaz").returns(doug)
86
+ all_mock.expects(:find_by!).with(id: 10).returns(doug)
87
87
 
88
- op = ::AssociationWithForeignKeyOp.new(user_identifier: "foobarbaz")
88
+ op = ::AssociationWithForeignKeyOp.new(owner_id: 10)
89
89
  assert_equal doug, op.user
90
- assert_equal "user_identifier", op.field_configurations[:user][:foreign_key]
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: doug.email_address).returns(doug)
107
+ all_mock.expects(:find_by!).with(email_address: "foo@bar.com").returns(doug)
97
108
 
98
- op = ::AssociationWithFindByAndForeignKeyOp.new(email_address: doug.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.user_identifier
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: "user_identifier"
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.0.0.beta
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: 2021-06-23 00:00:00.000000000 Z
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: 1.3.1
219
+ version: '0'
220
220
  requirements: []
221
- rubygems_version: 3.0.6
221
+ rubygems_version: 3.1.6
222
222
  signing_key:
223
223
  specification_version: 4
224
224
  summary: Feature-driven operation objects.