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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25c11b61b2c6af19a7be888c2ba66284cb799cf72131e9f7a7893ccf188babd6
4
- data.tar.gz: b22a8fec39573b99429fb27ad07340c1e0fa3133562a5190b8d6d088eeb9dbab
3
+ metadata.gz: a8871d701ab81402f275e38e7bf632c629bffc4320d31040119d99b12db05caf
4
+ data.tar.gz: edba79a803523a74e4e53d5e9cf62bf0dbdb2087592e7503b7b2d18395e5a8a6
5
5
  SHA512:
6
- metadata.gz: 559745dc62dde51e43bde01b4b5fd2598a40edf6b02945abc2f7d71a0b948e5c76d259ac8239ba91fe8aadd4f23db4569feeaf01320287744b25dd9b168b4c29
7
- data.tar.gz: b8bb15b169ddd8fe7a8e64bb4dcd8f6815b458fda0dc6f035310c2e2ea483a92196357f4893945f982ed661e1dd01ca4d51a43a9250e4b96fd5eeda1459ff22f
6
+ metadata.gz: 9953dd7a8ac11f4c842773c88a338e255c254bbf38f50a3437f204406a8136eca15544cd87aa9641395faf933ca9a00ddf9b3951a2a76326302e677988240143
7
+ data.tar.gz: 900b42b891947630463f3700a5df207d5e689a91dbe2e1970987f41b565fac7889083161d9d39bfd36082a7d0f4d1667d9b8bd549e7804371c58ad2987d238a0
@@ -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
- [Implementing an Op](https://github.com/guideline-tech/subroutine/wiki/Implementing-an-Op)
60
- [Using an Op](https://github.com/guideline-tech/subroutine/wiki/Using-an-Op)
61
- [Errors](https://github.com/guideline-tech/subroutine/wiki/Errors)
62
- [Basic Usage in Rails](https://github.com/guideline-tech/subroutine/wiki/Rails-Usage)
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 << config.foreign_key_method
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
@@ -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 excepts&.include?(field_name)
63
- next if onlys && !onlys.include?(field_name)
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
@@ -4,7 +4,7 @@ module Subroutine
4
4
 
5
5
  MAJOR = 1
6
6
  MINOR = 0
7
- PATCH = 0
7
+ PATCH = 1
8
8
  PRE = nil
9
9
 
10
10
  VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
@@ -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
@@ -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.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-02-19 00:00:00.000000000 Z
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