subroutine 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|