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 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