subroutine 4.4.0 → 4.5.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: 395466c8997f2ba267ca8aeb4d2ab31cc4da5c84d1d5cc46c603191d32f52c35
4
- data.tar.gz: dce7398c28cb192b05dc51823143cccc9200382d998e0afad835c2a3db8dffae
3
+ metadata.gz: 719502dade68ec34e525c97130a82e7ca6b3a5406002882a7916a93ad80d9099
4
+ data.tar.gz: 3eadd2bb114ef5d1724864b67462d0b563f241e21c3b61c92ea47366022aba9a
5
5
  SHA512:
6
- metadata.gz: c61146f544f205efef0e653485a21d93a49843415bbf37ca3afc4c466e87e4a639cc142ca75b5c2f00d646b1453d59d881becbd852be984b96bf14a55b3b4fa5
7
- data.tar.gz: 5ebf1feeb586fefc32707e7dd2c17b54f03eee51573be7d03aa048796cdd6acaec0b541756e84c2d00a62ba198ec07f2fda5d2c004c2e87a6674fd923c59c047
6
+ metadata.gz: a57ccde1e224deab1dd2854091b5a1117b026b3feabb2d47626b7227a9905c370fdd95d3aadce201d0f1a84d7409ff225bff20fb9fba07b8444c7b77b069f30c
7
+ data.tar.gz: 750f373e085dfa7a91b78c49f187f9df728a7a03794bde4806d49ae8742c52dcc2f462ba9d4ef2db203e6df190d24dede8f124ef2811af5d5ca4085f7c475411
data/lib/subroutine/op.rb CHANGED
@@ -135,7 +135,7 @@ module Subroutine
135
135
  end
136
136
 
137
137
  if field_config
138
- errors.add(field_config.field_name, error.message)
138
+ errors.add(field_config.field_name, error.type, message: error.message)
139
139
  else
140
140
  errors.add(:base, error_object.full_message(field_name, error.message))
141
141
  end
@@ -3,7 +3,7 @@
3
3
  module Subroutine
4
4
 
5
5
  MAJOR = 4
6
- MINOR = 4
6
+ MINOR = 5
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
 
data/subroutine.gemspec CHANGED
@@ -14,11 +14,14 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = "https://github.com/guideline-tech/subroutine"
15
15
  spec.license = "MIT"
16
16
 
17
- spec.files = `git ls-files -z`.split("\x0")
17
+ # Specify which files should be added to the gem when it is released.
18
+ spec.files = Dir["lib/**/*"] + Dir["*.gemspec"]
18
19
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
- spec.test_files = spec.files.grep(%r{^(gemfiles|test)/})
20
20
  spec.require_paths = ["lib"]
21
21
 
22
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
23
+ spec.metadata["rubygems_mfa_required"] = "true"
24
+
22
25
  spec.add_dependency "activemodel", ">= 6.1"
23
26
  spec.add_dependency "activesupport", ">= 6.1"
24
27
  spec.add_dependency "base64"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: subroutine
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.0
4
+ version: 4.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Nelson
@@ -198,29 +198,6 @@ executables: []
198
198
  extensions: []
199
199
  extra_rdoc_files: []
200
200
  files:
201
- - ".bundler-version"
202
- - ".github/CODEOWNERS"
203
- - ".github/dependabot.yml"
204
- - ".github/workflows/build.yml"
205
- - ".gitignore"
206
- - ".ruby-gemset"
207
- - ".ruby-version"
208
- - Appraisals
209
- - CHANGELOG.MD
210
- - Gemfile
211
- - LICENSE.txt
212
- - README.md
213
- - Rakefile
214
- - gemfiles/rails_6.1.gemfile
215
- - gemfiles/rails_6.1.gemfile.lock
216
- - gemfiles/rails_7.0.gemfile
217
- - gemfiles/rails_7.0.gemfile.lock
218
- - gemfiles/rails_7.1.gemfile
219
- - gemfiles/rails_7.1.gemfile.lock
220
- - gemfiles/rails_7.2.gemfile
221
- - gemfiles/rails_7.2.gemfile.lock
222
- - gemfiles/rails_8.0.gemfile
223
- - gemfiles/rails_8.0.gemfile.lock
224
201
  - lib/subroutine.rb
225
202
  - lib/subroutine/association_fields.rb
226
203
  - lib/subroutine/association_fields/association_type_mismatch_error.rb
@@ -242,18 +219,12 @@ files:
242
219
  - lib/subroutine/type_caster.rb
243
220
  - lib/subroutine/version.rb
244
221
  - subroutine.gemspec
245
- - test/subroutine/association_test.rb
246
- - test/subroutine/auth_test.rb
247
- - test/subroutine/base_test.rb
248
- - test/subroutine/fields_test.rb
249
- - test/subroutine/outputs_test.rb
250
- - test/subroutine/type_caster_test.rb
251
- - test/support/ops.rb
252
- - test/test_helper.rb
253
222
  homepage: https://github.com/guideline-tech/subroutine
254
223
  licenses:
255
224
  - MIT
256
- metadata: {}
225
+ metadata:
226
+ allowed_push_host: https://rubygems.org
227
+ rubygems_mfa_required: 'true'
257
228
  rdoc_options: []
258
229
  require_paths:
259
230
  - lib
@@ -268,25 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
268
239
  - !ruby/object:Gem::Version
269
240
  version: '0'
270
241
  requirements: []
271
- rubygems_version: 3.6.7
242
+ rubygems_version: 3.6.9
272
243
  specification_version: 4
273
244
  summary: Feature-driven operation objects.
274
- test_files:
275
- - gemfiles/rails_6.1.gemfile
276
- - gemfiles/rails_6.1.gemfile.lock
277
- - gemfiles/rails_7.0.gemfile
278
- - gemfiles/rails_7.0.gemfile.lock
279
- - gemfiles/rails_7.1.gemfile
280
- - gemfiles/rails_7.1.gemfile.lock
281
- - gemfiles/rails_7.2.gemfile
282
- - gemfiles/rails_7.2.gemfile.lock
283
- - gemfiles/rails_8.0.gemfile
284
- - gemfiles/rails_8.0.gemfile.lock
285
- - test/subroutine/association_test.rb
286
- - test/subroutine/auth_test.rb
287
- - test/subroutine/base_test.rb
288
- - test/subroutine/fields_test.rb
289
- - test/subroutine/outputs_test.rb
290
- - test/subroutine/type_caster_test.rb
291
- - test/support/ops.rb
292
- - test/test_helper.rb
245
+ test_files: []
data/.bundler-version DELETED
@@ -1 +0,0 @@
1
- 2.5.22
data/.github/CODEOWNERS DELETED
@@ -1 +0,0 @@
1
- .github/workflows @guideline-tech/engineering
@@ -1,24 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: "github-actions"
4
- directory: "/"
5
- open-pull-requests-limit: 20
6
- schedule:
7
- interval: "daily"
8
- time: "09:00"
9
- timezone: "America/New_York"
10
- commit-message:
11
- prefix: "[github-actions] "
12
- - package-ecosystem: "bundler"
13
- directory: "/"
14
- schedule:
15
- interval: "daily"
16
- time: "08:30"
17
- timezone: "America/New_York"
18
- versioning-strategy: increase
19
- open-pull-requests-limit: 20
20
- insecure-external-code-execution: deny
21
- allow:
22
- - dependency-type: "all"
23
- commit-message:
24
- prefix: "[bundler] "
@@ -1,27 +0,0 @@
1
- name: build
2
- on:
3
- pull_request:
4
- push:
5
- branches:
6
- - main
7
- jobs:
8
- build:
9
- runs-on: ubuntu-latest
10
- strategy:
11
- fail-fast: false
12
- matrix:
13
- ruby-version: [3.2, 3.3, 3.4]
14
- steps:
15
- - uses: actions/checkout@v4
16
- with:
17
- show-progress: "false"
18
- - name: get bundler version
19
- run: echo "BUNDLER=$(cat .bundler-version)" >> $GITHUB_ENV
20
- - uses: ruby/setup-ruby@v1
21
- with:
22
- ruby-version: ${{ matrix.ruby-version }}
23
- bundler: ${{ env.BUNDLER }}
24
- bundler-cache: true
25
- - run: bundle install
26
- - run: bundle exec appraisal install
27
- - run: bundle exec appraisal rake test
data/.gitignore DELETED
@@ -1,16 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- *.bundle
11
- *.so
12
- *.o
13
- *.a
14
- mkmf.log
15
- *.gem
16
- .byebug_history
data/.ruby-gemset DELETED
@@ -1 +0,0 @@
1
- subroutine
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 3.3.7
data/Appraisals DELETED
@@ -1,26 +0,0 @@
1
- appraise "rails-6.1" do
2
- gem 'activemodel', '~> 6.1.0'
3
- gem 'actionpack', '~> 6.1.0'
4
- gem 'concurrent-ruby', '1.3.4'
5
- end
6
-
7
- appraise "rails-7.0" do
8
- gem 'activemodel', '~> 7.0.0'
9
- gem 'actionpack', '~> 7.0.0'
10
- gem 'concurrent-ruby', '1.3.4'
11
- end
12
-
13
- appraise "rails-7.1" do
14
- gem 'activemodel', '~> 7.1.0'
15
- gem 'actionpack', '~> 7.1.0'
16
- end
17
-
18
- appraise "rails-7.2" do
19
- gem 'activemodel', '~> 7.2.0'
20
- gem 'actionpack', '~> 7.2.0'
21
- end
22
-
23
- appraise "rails-8.0" do
24
- gem 'activemodel', '~> 8.0.0'
25
- gem 'actionpack', '~> 8.0.0'
26
- end
data/CHANGELOG.MD DELETED
@@ -1,189 +0,0 @@
1
- # Changelog
2
-
3
- ## Subroutine 4.3.0
4
-
5
- Default repository to Ruby 3.3.7, and gem to a minimum version of 3.2.0.
6
- Tests continue to run against both Ruby 3.2 and Ruby 3.3.
7
-
8
- ## Subroutine 4.2.0
9
-
10
- If you are using polymorphic association fields, you can now customize how Subroutine
11
- resolves those class names to a ruby class by setting a global callable/lambda/proc:
12
-
13
- ```ruby
14
- ::Subroutine.constantize_polymorphic_class_name = ->(class_name) do
15
- class_name.classify.constantize
16
- end
17
- ```
18
-
19
- ## Subroutine 4.1.4
20
-
21
- Fields using the time/timestamp/datetime caster will now default back to the old behavior, and use a `precision:` option to opt-in to the new behavior introduced in `v4.1.1`.
22
-
23
- `precision: :seconds` will retain the old behavior of always parsing to a new Time object
24
- with floored sub-second precision, but applied more forcefully than before as it would have parsed whatever you passed to it. (This is the default, now.)
25
-
26
- `precision: :high` will now use the new functionality of re-using Time objects when they
27
- are passed in, or if not parsing exactly the provided string as to a Time object.
28
-
29
- ## Subroutine 4.1.1
30
-
31
- Fields using the time/timestamp/datetime caster will now return exactly the passed in value
32
- if it acts like a time object (`acts_like?(:time)`/`acts_like_time?`), instead of serializing
33
- to string and re-parsing to a Time object. This fixes issues with losing usec precision.
34
-
35
- ## Subroutine 4.1.0
36
-
37
- A field can now opt out of the natural assignment behavior of ActiveSupport::HashWithIndifferentAccess. Top level param groups are still accessible via indifferent access but if a field sets the `bypass_indifferent_assignment` option to `true` the HashWithIndifferentAccess assignment behavior will be bypassed in favor of direct Hash-like assignment.
38
-
39
- ```ruby
40
- class MyOp < Subroutine::Op
41
-
42
- object :some_hash
43
- object :some_other_hash, bypass_indifferent_assignment: true
44
-
45
- end
46
- ```
47
-
48
- ## Subroutine 4.0.1
49
-
50
- Association fields can now use `find_by()` instead of `find_by!()` by passing a `raise_on_miss: false` option. This places the responsibility on the op to manage nil cases rather than handling RecordNotFound errors.
51
-
52
- ## Subroutine 4.0
53
-
54
- The `Subroutine::Fields` module now contains a class_attribute that allows the altering of param accessor behaviors. `include_defaults_in_params` is now available to opt into including the default values in usage of the `all_params (alias params)` method. Backwards compatibility is preserved by defaulting the value to `false`. If switched to true, when an input is omitted and the field is configured with a default value, it will be included in the `all_params` object.
55
-
56
- Removed all usage of `ungrouped` params and refactored the storage of grouped params. Params are now stored in either the provided group or the defaults group and accessed via provided_params, params, default_params, and params_with_defaults. Grouped params are accessed the same way but with the group name prefixed eg. `my_private_default_params`.
57
-
58
- Polymorphic association fields now resolve class names via `klass.camelize.constantize`,
59
- previously was `klass.classify.constantize`.
60
-
61
- ## Subroutine 3.0
62
-
63
- Add support for Rails 6.1. Drop support for Rails 6.0 and lower.
64
-
65
- ## Subroutine 2.3
66
-
67
- Support dynamic types for foreign keys on association fields. The class type is used at runtime to determine the casting behavior of the foreign key field.
68
-
69
- ## Subroutine 2.2
70
-
71
- Add `type` validation for Output.
72
-
73
- ## Subroutine 2.0
74
-
75
- 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.
76
-
77
- **Note:** 2.0.0 was released with a bug and subsequently yanked. 2.0.1 is the first available 2.x version.
78
-
79
- ## Subroutine 1.0
80
-
81
- 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.
82
-
83
- ### Subroutine::Fields
84
-
85
- `Subroutine::Fields` was completely refactored to manage field declaration, configuration, and access in a more systematic and safer way.
86
-
87
- `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.
88
-
89
- Fields can be accessed via helpers and accessors can be managed by the field declration. Helpers include `get_field`, `set_field`, and `clear_field`.
90
-
91
- ```ruby
92
- class SomeOp < ::Subroutine::Op
93
- string :foo, read_accessor: field_reader: false, field_writer: true
94
-
95
- def perform
96
- self.foo = "bar"
97
- self.foo # NoMethodError
98
- self.get_field(:foo) # => "bar"
99
- end
100
- end
101
- ```
102
-
103
- Fields can be omitted from mass assignment, meaning they would not be respected via constructor signatures.
104
-
105
- ```ruby
106
- class SomeOp < ::Subroutine::Op
107
- string :foo, mass_assignable: false
108
- def perform
109
- puts foo
110
- end
111
- end
112
-
113
- SomeOp.submit!(foo: "Hello World!") # raises ::Subroutine::Fields::MassAssignmentError
114
- SomeOp.new{|op| op.foo = "Hello World!" }.submit! # prints "Hello World!"
115
- ```
116
-
117
- This is especially useful when dealing with user input and potentially unsafe attributes.
118
-
119
- ```ruby
120
- class UserUpdateOp < ::Op
121
- association :user
122
- string :first_name
123
- string :last_name
124
- integer :credit_balance_cents, mass_assignable: false
125
-
126
- def perform
127
- user.update(params)
128
- end
129
- end
130
-
131
- # some_controller.rb
132
- def update
133
- UserUpdateOp.submit!(params.merge(user: current_user))
134
- end
135
- ```
136
-
137
- Field groups were added as well, allowing you to access subsets of the fields easily.
138
-
139
- ```ruby
140
- class AccountUpdateOp < ::Op
141
- association :account
142
-
143
- with_options group: :user do
144
- string :first_name
145
- string :last_name
146
- date :dob
147
- end
148
-
149
- with_options group: :business do
150
- string :company_name
151
- string :ein
152
- end
153
-
154
- def perform
155
- account.user.update(user_params)
156
- account.business.update(business_params)
157
- end
158
-
159
- end
160
- ```
161
-
162
- 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`.
163
-
164
- Read more about field management and access in https://github.com/guideline-tech/subroutine/wiki/Param-Usage
165
-
166
- ### Subroutine::Association
167
-
168
- The `Subroutine::Association` module has been moved to `Subroutine::AssociationFields`.
169
-
170
- 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.
171
-
172
- ```ruby
173
- class SomeOp < ::Subroutine::Op
174
- association :user
175
- association :resource, polymorphic: true
176
- end
177
-
178
- user = User.find(4)
179
-
180
- op = SomeOp.new(user: user, resource: user)
181
- op.params #=> { user_id: 4, resource_type: "User", resource_id: 4 }
182
- op.params_with_association #=> { user: <User:103204 @id=4>, resource: <User:103204 @id=4> }
183
-
184
- op = SomeOp.new(user_id: user.id, resource_type: "User", resource_id: user.id)
185
- op.params #=> { user_id: 4, resource_type: "User", resource_id: 4 }
186
- op.params_with_association #=> { user: <User:290053 @id=4>, resource: <User:29042 @id=4> }
187
- ```
188
-
189
- Assignment of associations now validates the type. If an association is not polymorphic, the type will be validated against the expected type.
data/Gemfile DELETED
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in subroutine.gemspec
6
- gemspec
7
-
8
- gem "appraisal"
data/LICENSE.txt DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2015 Mike Nelson
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md DELETED
@@ -1,88 +0,0 @@
1
- # Subroutine
2
-
3
- A gem that provides an interface for creating feature-driven operations. You've probably heard at least one of these terms: "service objects", "form objects", "intentions", or "commands". Subroutine calls these "ops" and really it's just about enabling clear, concise, testable, and meaningful code.
4
-
5
- ## Example
6
-
7
- So you need to sign up a user? or maybe update one's account? or change a password? or maybe you need to sign up a business along with a user, associate them, send an email, and queue a worker in a single request? Not a problem, create an op for any of these use cases. Here's the signup example.
8
-
9
- ```ruby
10
- class SignupOp < ::Subroutine::Op
11
-
12
- string :name
13
- string :email
14
- string :password
15
-
16
- string :company_name
17
-
18
- validates :name, presence: true
19
- validates :email, presence: true
20
- validates :password, presence: true
21
- validates :company_name, presence: true
22
-
23
- outputs :user
24
- outputs :business, type: Business # validate that output type is an instance of Business
25
- outputs :heavy_operation, lazy: true # delay the execution of the output until accessed
26
-
27
- protected
28
-
29
- def perform
30
- u = create_user!
31
- b = create_business!(u)
32
-
33
- deliver_welcome_email(u)
34
-
35
- output :user, u
36
- output :business, b
37
- output :heavy_operation, -> { some_heavy_operation }
38
- end
39
-
40
- def create_user!
41
- User.create!(name: name, email: email, password: password)
42
- end
43
-
44
- def create_business!(owner)
45
- Business.create!(company_name: company_name, owner: owner)
46
- end
47
-
48
- def some_heavy_operation
49
- # ...
50
- end
51
-
52
- def deliver_welcome_email(u)
53
- UserMailer.welcome(u.id).deliver_later
54
- end
55
- end
56
- ```
57
-
58
- ## So why use this?
59
-
60
- - Avoid cluttering models or controllers with logic only applicable to one intention. You also don't need strong parameters because the inputs to the Op are well-defined.
61
- - Test the Op in isolation
62
- - Clear and concise intention in a single file
63
- - Multi-model operations become simple
64
-
65
- ## Continue Reading
66
-
67
- - [Implementing an Op](https://github.com/guideline-tech/subroutine/wiki/Implementing-an-Op)
68
- - [Using an Op](https://github.com/guideline-tech/subroutine/wiki/Using-an-Op)
69
- - [Errors](https://github.com/guideline-tech/subroutine/wiki/Errors)
70
- - [Basic Usage in Rails](https://github.com/guideline-tech/subroutine/wiki/Rails-Usage)
71
-
72
- ## Development
73
-
74
- Run the test suite against current Rails version:
75
-
76
- ```
77
- bundle exec rake test
78
- ```
79
-
80
- Run the test suite against all supported Rails versions using `appraisal`:
81
-
82
- ```
83
- bundle exec appraisal rake test
84
- ```
85
-
86
- For help updating the `Gemfile` or changing supported Rails versions, see the `appraisal` gem [README](https://github.com/thoughtbot/appraisal#usage).
87
-
88
- Note that the gemfiles in `gemfiles/*` are auto-generated by `appraisal` and should not be modified directly.
data/Rakefile DELETED
@@ -1,10 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require 'rake/testtask'
3
-
4
- Rake::TestTask.new do |t|
5
- t.libs << 'test'
6
- t.pattern = "test/**/*_test.rb"
7
- end
8
-
9
- task :default => [:test]
10
-
@@ -1,10 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "appraisal"
6
- gem "activemodel", "~> 6.1.0"
7
- gem "actionpack", "~> 6.1.0"
8
- gem "concurrent-ruby", "1.3.4"
9
-
10
- gemspec path: "../"