subroutine 1.0.0 → 1.0.1
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/CHANGELOG.MD +111 -0
- data/README.md +9 -7
- data/lib/subroutine/association_fields.rb +1 -2
- data/lib/subroutine/association_fields/configuration.rb +9 -2
- data/lib/subroutine/fields.rb +5 -2
- data/lib/subroutine/fields/configuration.rb +5 -1
- data/lib/subroutine/version.rb +1 -1
- data/test/subroutine/base_test.rb +14 -0
- data/test/support/ops.rb +13 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8871d701ab81402f275e38e7bf632c629bffc4320d31040119d99b12db05caf
|
4
|
+
data.tar.gz: edba79a803523a74e4e53d5e9cf62bf0dbdb2087592e7503b7b2d18395e5a8a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9953dd7a8ac11f4c842773c88a338e255c254bbf38f50a3437f204406a8136eca15544cd87aa9641395faf933ca9a00ddf9b3951a2a76326302e677988240143
|
7
|
+
data.tar.gz: 900b42b891947630463f3700a5df207d5e689a91dbe2e1970987f41b565fac7889083161d9d39bfd36082a7d0f4d1667d9b8bd549e7804371c58ad2987d238a0
|
data/CHANGELOG.MD
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
## Subroutine 1.0
|
2
|
+
|
3
|
+
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.
|
4
|
+
|
5
|
+
### Subroutine::Fields
|
6
|
+
|
7
|
+
`Subroutine::Fields` was completely refactored to manage field declaration, configuration, and access in a more systematic and safer way.
|
8
|
+
|
9
|
+
`Op._fields` was removed in favor of `Op.field_configurations`. `field_configurations` is a hash with keys of the field name and values of `FieldConfiguration` objects. FieldConfiguration objects are SimpleDelegates to the underlying option hashes. They answer questions and provide a mechanism for validating the configuration of a field.
|
10
|
+
|
11
|
+
Fields can be accessed via helpers and accessors can be managed by the field declration. Helpers include `get_field`, `set_field`, and `clear_field`.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
class SomeOp < ::Subroutine::Op
|
15
|
+
string :foo, read_accessor: field_reader: false, field_writer: true
|
16
|
+
|
17
|
+
def perform
|
18
|
+
self.foo = "bar"
|
19
|
+
self.foo # NoMethodError
|
20
|
+
self.get_field(:foo) # => "bar"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
Fields can be omitted from mass assignment, meaning they would not be respected via constructor signatures.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class SomeOp < ::Subroutine::Op
|
29
|
+
string :foo, mass_assignable: false
|
30
|
+
def perform
|
31
|
+
puts foo
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
SomeOp.submit!(foo: "Hello World!") # raises ::Subroutine::Fields::MassAssignmentError
|
36
|
+
SomeOp.new{|op| op.foo = "Hello World!" }.submit! # prints "Hello World!"
|
37
|
+
```
|
38
|
+
|
39
|
+
This is especially useful when dealing with user input and potentially unsafe attributes.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class UserUpdateOp < ::Op
|
43
|
+
association :user
|
44
|
+
string :first_name
|
45
|
+
string :last_name
|
46
|
+
integer :credit_balance_cents, mass_assignable: false
|
47
|
+
|
48
|
+
def perform
|
49
|
+
user.update(params)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# some_controller.rb
|
54
|
+
def update
|
55
|
+
UserUpdateOp.submit!(params.merge(user: current_user))
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
Field groups were added as well, allowing you to access subsets of the fields easily.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
class AccountUpdateOp < ::Op
|
63
|
+
association :account
|
64
|
+
|
65
|
+
with_options group: :user do
|
66
|
+
string :first_name
|
67
|
+
string :last_name
|
68
|
+
date :dob
|
69
|
+
end
|
70
|
+
|
71
|
+
with_options group: :business do
|
72
|
+
string :company_name
|
73
|
+
string :ein
|
74
|
+
end
|
75
|
+
|
76
|
+
def perform
|
77
|
+
account.user.update(user_params)
|
78
|
+
account.business.update(business_params)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
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`.
|
85
|
+
|
86
|
+
Read more about field management and access in https://github.com/guideline-tech/subroutine/wiki/Param-Usage
|
87
|
+
|
88
|
+
### Subroutine::Association
|
89
|
+
|
90
|
+
The `Subroutine::Association` module has been moved to `Subroutine::AssociationFields`.
|
91
|
+
|
92
|
+
Only native types are stored in params now. The objects loaded from associations are stored in an `association_cache`. This ensures access to fields are consistent regardless of the inputs.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class SomeOp < ::Subroutine::Op
|
96
|
+
association :user
|
97
|
+
association :resource, polymorphic: true
|
98
|
+
end
|
99
|
+
|
100
|
+
user = User.find(4)
|
101
|
+
|
102
|
+
op = SomeOp.new(user: user, resource: user)
|
103
|
+
op.params #=> { user_id: 4, resource_type: "User", resource_id: 4 }
|
104
|
+
op.params_with_association #=> { user: <User:103204 @id=4>, resource: <User:103204 @id=4> }
|
105
|
+
|
106
|
+
op = SomeOp.new(user_id: user.id, resource_type: "User", resource_id: user.id)
|
107
|
+
op.params #=> { user_id: 4, resource_type: "User", resource_id: 4 }
|
108
|
+
op.params_with_association #=> { user: <User:290053 @id=4>, resource: <User:29042 @id=4> }
|
109
|
+
```
|
110
|
+
|
111
|
+
Assignment of associations now validates the type. If an association is not polymorphic, the type will be validated against the expected type.
|
data/README.md
CHANGED
@@ -12,7 +12,7 @@ class SignupOp < ::Subroutine::Op
|
|
12
12
|
string :name
|
13
13
|
string :email
|
14
14
|
string :password
|
15
|
-
|
15
|
+
|
16
16
|
string :company_name
|
17
17
|
|
18
18
|
validates :name, presence: true
|
@@ -28,7 +28,7 @@ class SignupOp < ::Subroutine::Op
|
|
28
28
|
def perform
|
29
29
|
u = create_user!
|
30
30
|
b = create_business!(u)
|
31
|
-
|
31
|
+
|
32
32
|
deliver_welcome_email(u)
|
33
33
|
|
34
34
|
output :user, u
|
@@ -38,7 +38,7 @@ class SignupOp < ::Subroutine::Op
|
|
38
38
|
def create_user!
|
39
39
|
User.create!(name: name, email: email, password: password)
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def create_business!(owner)
|
43
43
|
Business.create!(company_name: company_name, owner: owner)
|
44
44
|
end
|
@@ -56,7 +56,9 @@ end
|
|
56
56
|
- Clear and concise intention in a single file
|
57
57
|
- Multi-model operations become simple
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
[
|
62
|
-
[
|
59
|
+
## Continue Reading
|
60
|
+
|
61
|
+
- [Implementing an Op](https://github.com/guideline-tech/subroutine/wiki/Implementing-an-Op)
|
62
|
+
- [Using an Op](https://github.com/guideline-tech/subroutine/wiki/Using-an-Op)
|
63
|
+
- [Errors](https://github.com/guideline-tech/subroutine/wiki/Errors)
|
64
|
+
- [Basic Usage in Rails](https://github.com/guideline-tech/subroutine/wiki/Rails-Usage)
|
@@ -100,8 +100,7 @@ module Subroutine
|
|
100
100
|
|
101
101
|
excepts = []
|
102
102
|
association_fields.each_pair do |_name, config|
|
103
|
-
excepts
|
104
|
-
excepts << config.foreign_type_method if config.polymorphic?
|
103
|
+
excepts |= config.related_field_names
|
105
104
|
end
|
106
105
|
|
107
106
|
out = params.except(*excepts)
|
@@ -19,6 +19,13 @@ module Subroutine
|
|
19
19
|
super + [::Subroutine::AssociationFields]
|
20
20
|
end
|
21
21
|
|
22
|
+
def related_field_names
|
23
|
+
out = super
|
24
|
+
out << foreign_key_method
|
25
|
+
out << foreign_type_method if polymorphic?
|
26
|
+
out
|
27
|
+
end
|
28
|
+
|
22
29
|
def polymorphic?
|
23
30
|
!!config[:polymorphic]
|
24
31
|
end
|
@@ -40,11 +47,11 @@ module Subroutine
|
|
40
47
|
end
|
41
48
|
|
42
49
|
def foreign_key_method
|
43
|
-
foreign_key || "#{field_name}_id"
|
50
|
+
(foreign_key || "#{field_name}_id").to_sym
|
44
51
|
end
|
45
52
|
|
46
53
|
def foreign_type_method
|
47
|
-
foreign_key_method.gsub(/_id$/, "_type")
|
54
|
+
foreign_key_method.to_s.gsub(/_id$/, "_type").to_sym
|
48
55
|
end
|
49
56
|
|
50
57
|
def build_foreign_key_field
|
data/lib/subroutine/fields.rb
CHANGED
@@ -58,9 +58,12 @@ module Subroutine
|
|
58
58
|
onlys = options.key?(:only) ? Array(options.delete(:only)) : nil
|
59
59
|
|
60
60
|
things.each do |thing|
|
61
|
+
local_excepts = excepts.map { |field| thing.get_field_config(field)&.related_field_names }.flatten.compact.uniq if excepts
|
62
|
+
local_onlys = onlys.map { |field| thing.get_field_config(field)&.related_field_names }.flatten.compact.uniq if onlys
|
63
|
+
|
61
64
|
thing.field_configurations.each_pair do |field_name, config|
|
62
|
-
next if
|
63
|
-
next if
|
65
|
+
next if local_excepts&.include?(field_name)
|
66
|
+
next if local_onlys && !local_onlys.include?(field_name)
|
64
67
|
|
65
68
|
config.required_modules.each do |mod|
|
66
69
|
include mod unless included_modules.include?(mod)
|
@@ -22,7 +22,7 @@ module Subroutine
|
|
22
22
|
attr_reader :field_name
|
23
23
|
|
24
24
|
def initialize(field_name, options)
|
25
|
-
@field_name = field_name
|
25
|
+
@field_name = field_name.to_sym
|
26
26
|
config = sanitize_options(options)
|
27
27
|
super(config)
|
28
28
|
validate!
|
@@ -38,6 +38,10 @@ module Subroutine
|
|
38
38
|
[]
|
39
39
|
end
|
40
40
|
|
41
|
+
def related_field_names
|
42
|
+
[field_name]
|
43
|
+
end
|
44
|
+
|
41
45
|
def behavior
|
42
46
|
nil
|
43
47
|
end
|
data/lib/subroutine/version.rb
CHANGED
@@ -42,6 +42,13 @@ module Subroutine
|
|
42
42
|
assert_equal [:baz], op.field_configurations.keys.sort
|
43
43
|
end
|
44
44
|
|
45
|
+
def test_fields_from_ignores_except_association_fields
|
46
|
+
op = ::ExceptAssociationOp.new
|
47
|
+
refute op.field_configurations.key?(:admin)
|
48
|
+
refute op.field_configurations.key?(:admin_id)
|
49
|
+
refute op.field_configurations.key?(:admin_type)
|
50
|
+
end
|
51
|
+
|
45
52
|
def test_fields_from_only_fields
|
46
53
|
op = ::OnlyFooBarOp.new
|
47
54
|
assert op.field_configurations.key?(:foo)
|
@@ -49,6 +56,13 @@ module Subroutine
|
|
49
56
|
refute_equal [:baz], op.field_configurations.keys.sort
|
50
57
|
end
|
51
58
|
|
59
|
+
def test_fields_from_only_association_fields
|
60
|
+
op = ::OnlyAssociationOp.new
|
61
|
+
assert op.field_configurations.key?(:admin)
|
62
|
+
assert op.field_configurations.key?(:admin_type)
|
63
|
+
assert op.field_configurations.key?(:admin_id)
|
64
|
+
end
|
65
|
+
|
52
66
|
def test_defaults_declaration_options
|
53
67
|
op = ::DefaultsOp.new
|
54
68
|
assert_equal "foo", op.foo
|
data/test/support/ops.rb
CHANGED
@@ -208,7 +208,7 @@ class CustomAuthorizeOp < OpWithAuth
|
|
208
208
|
protected
|
209
209
|
|
210
210
|
def authorize_user_is_correct
|
211
|
-
unauthorized! unless current_user.email_address.to_s =~ /example\.com$/
|
211
|
+
unauthorized! unless current_user.email_address.to_s =~ /example\.com$/ # rubocop:disable Performance/RegexpMatch
|
212
212
|
end
|
213
213
|
|
214
214
|
end
|
@@ -304,6 +304,18 @@ class AssociationWithClassOp < ::OpWithAssociation
|
|
304
304
|
|
305
305
|
end
|
306
306
|
|
307
|
+
class ExceptAssociationOp < ::Subroutine::Op
|
308
|
+
|
309
|
+
fields_from ::PolymorphicAssociationOp, except: %i[admin]
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
class OnlyAssociationOp < ::Subroutine::Op
|
314
|
+
|
315
|
+
fields_from ::PolymorphicAssociationOp, only: %i[admin]
|
316
|
+
|
317
|
+
end
|
318
|
+
|
307
319
|
class InheritedSimpleAssociation < ::Subroutine::Op
|
308
320
|
|
309
321
|
fields_from SimpleAssociationOp
|
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: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Nelson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -161,6 +161,7 @@ files:
|
|
161
161
|
- ".ruby-gemset"
|
162
162
|
- ".ruby-version"
|
163
163
|
- ".travis.yml"
|
164
|
+
- CHANGELOG.MD
|
164
165
|
- Gemfile
|
165
166
|
- LICENSE.txt
|
166
167
|
- README.md
|