solid-process 0.4.0 → 0.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 +4 -4
- data/.claude/CLAUDE.md +112 -0
- data/CHANGELOG.md +12 -0
- data/README.md +196 -31
- data/Rakefile +28 -8
- data/docs/010_KEY_CONCEPTS.md +31 -0
- data/docs/020_BASIC_USAGE.md +46 -0
- data/docs/030_INTERMEDIATE_USAGE.md +74 -0
- data/docs/040_ADVANCED_USAGE.md +96 -0
- data/docs/050_ERROR_HANDLING.md +3 -0
- data/docs/060_TESTING.md +3 -0
- data/docs/070_INSTRUMENTATION.md +3 -0
- data/docs/080_RAILS_INTEGRATION.md +3 -0
- data/docs/090_INTERNAL_LIBRARIES.md +3 -0
- data/docs/100_PORTS_AND_ADAPTERS.md +3 -0
- data/lib/solid/process/backtrace_cleaner.rb +1 -1
- data/lib/solid/process/event_logs/basic_logger_listener.rb +1 -2
- data/lib/solid/process/version.rb +1 -1
- metadata +16 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 56d7e7dae11f894dafd8cd1970ccf6fa1a9e6f6879a8f6370a3c5fa3b8419fcd
|
|
4
|
+
data.tar.gz: c15ca09276f4e0a366c980fd44cb6885f7cb5e1439786222c324828b2e7a5632
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a7afa31c7de01092d6c75130d50e557c83e7e8b12b7a30667fce3bbfdb7c2876298907da67c8c0cfa8b1e1bd375b88d11d106d800bb628cb70e7efe7203324d
|
|
7
|
+
data.tar.gz: 53b395cec3553f20305db78fc24b51b09283034310363f26bdf53711aec85021aa5aa85cb24645d692d85df0218c8cec2700f42c315f983507dc27375f65f80d
|
data/.claude/CLAUDE.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
`solid-process` is a Ruby gem for encapsulating business logic into manageable processes. It integrates with Rails and provides input validation, dependency injection, and observability features.
|
|
8
|
+
|
|
9
|
+
## Common Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Setup (install dependencies: bundle + appraisal)
|
|
13
|
+
bin/setup
|
|
14
|
+
|
|
15
|
+
# Clean install + full test suite. Useful when switching Ruby versions.
|
|
16
|
+
bin/matrix
|
|
17
|
+
|
|
18
|
+
# Run all tests for current Ruby version
|
|
19
|
+
bin/rake matrix
|
|
20
|
+
|
|
21
|
+
# Run a single test file
|
|
22
|
+
bundle exec appraisal rails-8-1 ruby -Ilib:test test/solid/process/result_test.rb
|
|
23
|
+
|
|
24
|
+
# Run a specific test by name
|
|
25
|
+
bundle exec appraisal rails-8-1 ruby -Ilib:test test/solid/process/result_test.rb -n test_method_name
|
|
26
|
+
|
|
27
|
+
# Interactive console
|
|
28
|
+
bin/console
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Lint
|
|
32
|
+
|
|
33
|
+
**Ensure lint is green before committing.**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Check for style violations (Ruby 3.4+)
|
|
37
|
+
bin/rake standard
|
|
38
|
+
|
|
39
|
+
# Auto-fix style violations
|
|
40
|
+
bin/rake standard:fix
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Release Checklist
|
|
44
|
+
|
|
45
|
+
When `lib/solid/process/version.rb` is changed:
|
|
46
|
+
1. Verify `CHANGELOG.md` has a matching version entry (e.g., `## [0.5.0] - YYYY-MM-DD`)
|
|
47
|
+
2. Verify `README.md` compatibility matrix is up-to-date if Ruby/Rails support changed
|
|
48
|
+
|
|
49
|
+
### Ruby/Rails Version Alignment
|
|
50
|
+
|
|
51
|
+
When modifying Ruby or Rails version support, ensure these files stay aligned:
|
|
52
|
+
|
|
53
|
+
| File | Purpose |
|
|
54
|
+
|------|---------|
|
|
55
|
+
| `Appraisals` | Source of truth for gem dependencies per Rails version |
|
|
56
|
+
| `Rakefile` | Local `rake matrix` task conditions |
|
|
57
|
+
| `.github/workflows/main.yml` | CI matrix and step conditions |
|
|
58
|
+
| `README.md` | Compatibility matrix table |
|
|
59
|
+
|
|
60
|
+
**Key checks:**
|
|
61
|
+
1. Ruby version conditions must match across `Appraisals`, `Rakefile`, and CI
|
|
62
|
+
2. CI string values (e.g., `'head'`) need explicit checks since numeric comparisons won't match them
|
|
63
|
+
3. README table must reflect the actual tested combinations
|
|
64
|
+
4. Ruby `head` should only run against Rails edge (not stable Rails versions)
|
|
65
|
+
|
|
66
|
+
## Switching Ruby Versions
|
|
67
|
+
|
|
68
|
+
### ASDF
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
asdf list ruby # List installed Ruby versions
|
|
72
|
+
asdf set ruby 4.0.1 # Ruby 4.x
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Tests
|
|
76
|
+
|
|
77
|
+
Always test on multiple Ruby versions when fixing compatibility issues.
|
|
78
|
+
|
|
79
|
+
### Support Files
|
|
80
|
+
|
|
81
|
+
Test fixtures in `test/support/` follow numbered naming (e.g., `051_user_token_creation.rb`) for load order. These define sample processes used across multiple tests.
|
|
82
|
+
|
|
83
|
+
### Fix Library Code, Not Tests
|
|
84
|
+
|
|
85
|
+
When tests fail due to Ruby version differences:
|
|
86
|
+
1. First check if the **library code** can be updated to normalize behavior
|
|
87
|
+
2. Only modify tests if the library fix isn't possible
|
|
88
|
+
|
|
89
|
+
### Available Rails versions (see Rakefile for full list)
|
|
90
|
+
```sh
|
|
91
|
+
bundle exec appraisal rails-6-0 rake test # Ruby 2.7, 3.0
|
|
92
|
+
bundle exec appraisal rails-6-1 rake test # Ruby 2.7, 3.0
|
|
93
|
+
bundle exec appraisal rails-7-0 rake test # Ruby 3.0, 3.1, 3.2, 3.3
|
|
94
|
+
bundle exec appraisal rails-7-1 rake test # Ruby 3.0, 3.1, 3.2, 3.3
|
|
95
|
+
bundle exec appraisal rails-7-2 rake test # Ruby 3.1, 3.2, 3.3, 3.4
|
|
96
|
+
bundle exec appraisal rails-8-0 rake test # Ruby 3.2, 3.3, 3.4
|
|
97
|
+
bundle exec appraisal rails-8-1 rake test # Ruby 3.3, 3.4, 4.x
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Architecture
|
|
101
|
+
|
|
102
|
+
### Core Classes
|
|
103
|
+
|
|
104
|
+
- **Solid::Process** (`lib/solid/process.rb`) - Base class for business processes. Requires `input` block and `call` method. Returns `Success` or `Failure` outputs.
|
|
105
|
+
|
|
106
|
+
- **Solid::Model** (`lib/solid/model.rb`) - ActiveModel-based concern providing attributes, validations, and callbacks.
|
|
107
|
+
|
|
108
|
+
- **Solid::Input** (`lib/solid/input.rb`) - Input validation class, includes Solid::Model.
|
|
109
|
+
|
|
110
|
+
- **Solid::Value** (`lib/solid/value.rb`) - Immutable value objects.
|
|
111
|
+
|
|
112
|
+
- **Solid::Output** (`lib/solid/output.rb`) - Wraps solid-result gem for Success/Failure return types.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2025-01-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add support for Ruby 3.4 and 4.x.
|
|
8
|
+
|
|
9
|
+
- Add support for Rails 7.2, 8.0, and 8.1.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fix `BacktraceCleaner::BLOCKS_PATTERN` to handle Ruby 4.x backtrace format (`'Kernel#then'` instead of `` `then' ``).
|
|
14
|
+
|
|
3
15
|
## [0.4.0] - 2024-06-23
|
|
4
16
|
|
|
5
17
|
### Added
|
data/README.md
CHANGED
|
@@ -9,77 +9,242 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
## 📚 Table of Contents <!-- omit from toc -->
|
|
13
|
+
|
|
14
|
+
- [Introduction](#introduction)
|
|
15
|
+
- [Installation](#installation)
|
|
16
|
+
- [The Basic Structure](#the-basic-structure)
|
|
17
|
+
- [Further Reading](#further-reading)
|
|
18
|
+
- [Development](#development)
|
|
19
|
+
- [Contributing](#contributing)
|
|
20
|
+
- [License](#license)
|
|
21
|
+
- [Code of Conduct](#code-of-conduct)
|
|
22
|
+
- [Acknowledgments](#acknowledgments)
|
|
23
|
+
- [About](#about)
|
|
24
|
+
|
|
25
|
+
## Supported Ruby and Rails <!-- omit from toc -->
|
|
26
|
+
|
|
27
|
+
This library is tested (100% coverage) against:
|
|
28
|
+
|
|
29
|
+
| Ruby / Rails | 6.0 | 6.1 | 7.0 | 7.1 | 7.2 | 8.0 | 8.1 | Edge |
|
|
30
|
+
|--------------|-----|-----|-----|-----|-----|-----|-----|------|
|
|
31
|
+
| 2.7 | ✅ | ✅ | ✅ | ✅ | | | | |
|
|
32
|
+
| 3.0 | ✅ | ✅ | ✅ | ✅ | | | | |
|
|
33
|
+
| 3.1 | | | ✅ | ✅ | ✅ | | | |
|
|
34
|
+
| 3.2 | | | ✅ | ✅ | ✅ | ✅ | | |
|
|
35
|
+
| 3.3 | | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
36
|
+
| 3.4 | | | | | ✅ | ✅ | ✅ | ✅ |
|
|
37
|
+
| 4.x | | | | | | | ✅ | ✅ |
|
|
24
38
|
|
|
25
39
|
## Introduction
|
|
26
40
|
|
|
27
41
|
`solid-process` is a Ruby/Rails library designed to encapsulate business logic into manageable processes. It simplifies writing, testing, maintaining, and evolving your code, ensuring it remains clear and approachable as your application scales.
|
|
28
42
|
|
|
29
|
-
**
|
|
43
|
+
**Features:** (_touch to expand_)
|
|
44
|
+
|
|
45
|
+
<details><summary>1️⃣ <strong>Seamless Rails integration</strong></summary>
|
|
46
|
+
|
|
47
|
+
> Designed to complement Ruby on Rails, this library integrates smoothly without conflicting with existing framework conventions and features.
|
|
48
|
+
|
|
49
|
+
</details>
|
|
50
|
+
|
|
51
|
+
<details><summary>2️⃣ <strong>Support progressive mastery</strong></summary>
|
|
52
|
+
|
|
53
|
+
> Offers an intuitive entry point for novices while providing robust, advanced features that cater to experienced developers.
|
|
54
|
+
|
|
55
|
+
</details>
|
|
30
56
|
|
|
31
|
-
|
|
57
|
+
<details><summary>3️⃣ <strong>Promote conceptual integrity and rapid onboarding</strong></summary>
|
|
32
58
|
|
|
33
|
-
|
|
59
|
+
> By maintaining a consistent design philosophy, `solid-process` reduces the learning curve for new developers, allowing them to contribute more effectively and quickly to a codebase.
|
|
34
60
|
|
|
35
|
-
|
|
61
|
+
</details>
|
|
36
62
|
|
|
37
|
-
4
|
|
63
|
+
<details><summary>4️⃣ <strong>Enhanced observability</strong></summary>
|
|
38
64
|
|
|
39
|
-
|
|
65
|
+
> Equipped with sophisticated instrumentation mechanisms, the library enables detailed logging and tracing without compromising code readability, even when processes are nested.
|
|
40
66
|
|
|
41
|
-
|
|
67
|
+
</details>
|
|
42
68
|
|
|
43
|
-
|
|
69
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
|
70
|
+
|
|
71
|
+
### Examples <!-- omit in toc -->
|
|
72
|
+
|
|
73
|
+
Check out [Solid Rails App](https://github.com/solid-process/solid-rails-app) for a complete example of how to use `solid-process` in a Rails application. [Twelve versions (branches)](https://github.com/solid-process/solid-rails-app?tab=readme-ov-file#-repository-branches) show how the gem can be incrementally integrated, access it to see from simple services/form objects to implementing the ports and adapters (hexagonal) architectural pattern.
|
|
74
|
+
|
|
75
|
+
You can also check the [examples](examples) directory for more simple examples of how to use the gem.
|
|
76
|
+
|
|
77
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
|
44
78
|
|
|
45
79
|
## Installation
|
|
46
80
|
|
|
47
|
-
|
|
81
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
82
|
+
|
|
83
|
+
$ bundle add solid-process
|
|
84
|
+
|
|
85
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
86
|
+
|
|
87
|
+
$ gem install solid-process
|
|
88
|
+
|
|
89
|
+
And require it in your code:
|
|
90
|
+
|
|
91
|
+
require 'solid/process'
|
|
92
|
+
|
|
93
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
|
94
|
+
|
|
95
|
+
## The Basic Structure
|
|
96
|
+
|
|
97
|
+
All `Solid::Process` requires at least two things: an `input` and a `call` method.
|
|
98
|
+
|
|
99
|
+
1. The `input` is a set of attributes needed to perform the work.
|
|
100
|
+
2. The `#call` method is the entry point and where the work is done.
|
|
101
|
+
- It receives the attributes Hash (symbolized keys), defined by the `input`.
|
|
102
|
+
- It returns a `Success` or `Failure` as the output.
|
|
48
103
|
|
|
49
104
|
```ruby
|
|
50
|
-
|
|
105
|
+
class User::Creation < Solid::Process
|
|
106
|
+
input do
|
|
107
|
+
# Define the attributes needed to perform the work
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def call(attributes)
|
|
111
|
+
# Perform the work and return a Success or Failure as the output
|
|
112
|
+
end
|
|
113
|
+
end
|
|
51
114
|
```
|
|
52
115
|
|
|
53
|
-
|
|
116
|
+
#### Example <!-- omit in toc -->
|
|
54
117
|
|
|
55
|
-
```
|
|
56
|
-
|
|
118
|
+
```ruby
|
|
119
|
+
class User::Creation < Solid::Process
|
|
120
|
+
input do
|
|
121
|
+
attribute :email
|
|
122
|
+
attribute :password
|
|
123
|
+
attribute :password_confirmation
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def call(attributes)
|
|
127
|
+
user = User.create(attributes)
|
|
128
|
+
|
|
129
|
+
if user.persisted?
|
|
130
|
+
Success(:user_created, user: user)
|
|
131
|
+
else
|
|
132
|
+
Failure(:user_not_created, user: user)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
57
136
|
```
|
|
58
137
|
|
|
59
|
-
|
|
138
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
|
60
139
|
|
|
61
|
-
|
|
62
|
-
|
|
140
|
+
### Calling a Process <!-- omit from toc -->
|
|
141
|
+
|
|
142
|
+
To call a process, you can use the `call` method directly, or instantiate the class and call the `#call` method.
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
###############
|
|
146
|
+
# Direct call #
|
|
147
|
+
###############
|
|
148
|
+
|
|
149
|
+
User::Creation.call(email: 'john.doe@email.com', password: 'password', password_confirmation: 'password')
|
|
150
|
+
# => #<Solid::Output::Success type=:user_created value={:user=>#<User id: 1, ...>}>
|
|
151
|
+
|
|
152
|
+
########################
|
|
153
|
+
# Instantiate and call #
|
|
154
|
+
########################
|
|
155
|
+
|
|
156
|
+
process = User::Creation.new
|
|
157
|
+
|
|
158
|
+
process.call(email: 'john.doe@email.com', password: 'password', password_confirmation: 'password')
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
For now, it's essential to know that a process instance is stateful, and because of this, you can call it only once.
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
process = User::Creation.new
|
|
165
|
+
|
|
166
|
+
input = {email: 'john.doe@email.com', password: 'password', password_confirmation: 'password'}
|
|
167
|
+
|
|
168
|
+
process.call(input)
|
|
169
|
+
|
|
170
|
+
process.call(input)
|
|
171
|
+
# The `User::Creation#output` is already set. Use `.output` to access the result or create a new instance to call again. (Solid::Process::Error)
|
|
63
172
|
```
|
|
64
173
|
|
|
65
|
-
|
|
174
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
|
175
|
+
|
|
176
|
+
## Further Reading
|
|
177
|
+
|
|
178
|
+
1. [Key Concepts](docs/010_KEY_CONCEPTS.md)
|
|
179
|
+
2. [Basic Usage](docs/020_BASIC_USAGE.md)
|
|
180
|
+
3. [Intermediate Usage](docs/030_INTERMEDIATE_USAGE.md)
|
|
181
|
+
4. [Advanced Usage](docs/040_ADVANCED_USAGE.md)
|
|
182
|
+
5. [Error Handling](docs/050_ERROR_HANDLING.md)
|
|
183
|
+
6. [Testing](docs/060_TESTING.md)
|
|
184
|
+
7. [Instrumentation / Observability](docs/070_INSTRUMENTATION.md)
|
|
185
|
+
8. [Rails Integration](docs/080_RAILS_INTEGRATION.md)
|
|
186
|
+
9. [Internal libraries](docs/090_INTERNAL_LIBRARIES.md)
|
|
187
|
+
- Solid::Input
|
|
188
|
+
- Solid::Model
|
|
189
|
+
- Solid::Value
|
|
190
|
+
- ActiveModel validations
|
|
191
|
+
10. [Ports and Adapters (Hexagonal Architecture)](docs/100_PORTS_AND_ADAPTERS.md)
|
|
66
192
|
|
|
67
|
-
|
|
193
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
|
68
194
|
|
|
69
195
|
## Development
|
|
70
196
|
|
|
71
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake
|
|
197
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake matrix` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Run full test suite for current Ruby version
|
|
201
|
+
bin/rake matrix
|
|
202
|
+
|
|
203
|
+
# Run tests for a specific Rails version
|
|
204
|
+
bundle exec appraisal rails-8-1 rake test
|
|
205
|
+
|
|
206
|
+
# Run a single test file
|
|
207
|
+
bundle exec appraisal rails-8-1 ruby -Ilib:test test/solid/process/result_test.rb
|
|
208
|
+
|
|
209
|
+
# Lint (Ruby 3.4+)
|
|
210
|
+
bin/rake standard
|
|
211
|
+
|
|
212
|
+
# Clean install + full test suite (useful when switching Ruby versions)
|
|
213
|
+
# asdf set ruby <version>
|
|
214
|
+
bin/matrix
|
|
215
|
+
```
|
|
72
216
|
|
|
73
217
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
74
218
|
|
|
219
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
|
220
|
+
|
|
75
221
|
## Contributing
|
|
76
222
|
|
|
77
223
|
Bug reports and pull requests are welcome on GitHub at https://github.com/solid-process/solid-process. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/solid-process/solid-process/blob/main/CODE_OF_CONDUCT.md).
|
|
78
224
|
|
|
225
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
|
226
|
+
|
|
79
227
|
## License
|
|
80
228
|
|
|
81
229
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
82
230
|
|
|
231
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
|
232
|
+
|
|
83
233
|
## Code of Conduct
|
|
84
234
|
|
|
85
235
|
Everyone interacting in the Solid::Process project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/solid-process/solid-process/blob/main/CODE_OF_CONDUCT.md).
|
|
236
|
+
|
|
237
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
|
238
|
+
|
|
239
|
+
## Acknowledgments
|
|
240
|
+
|
|
241
|
+
I want to thank some people who helped me by testing and giving feedback as this project took shape, they are:
|
|
242
|
+
|
|
243
|
+
- [Diego Linhares](https://github.com/diegolinhares) and [Ralf Schmitz Bongiolo](https://github.com/mrbongiolo) they were the brave ones who worked for a few months with the first versions of the ecosystem (it was called B/CDD). Their feedback was essential for improving DX and helped me to pivot some core decisions.
|
|
244
|
+
- [Vitor Avelino](https://github.com/vitoravelino), [Tomás Coêlho](https://github.com/tomascco), [Haroldo Furtado](https://github.com/haroldofurtado) (I could repeat Ralf and Diego again) for the various feedbacks, documentation, API, support and words of encouragement.
|
|
245
|
+
|
|
246
|
+
## About
|
|
247
|
+
|
|
248
|
+
[Rodrigo Serradura](https://rodrigoserradura.com) created this project. He is the Solid Process creator and has already made similar gems like the [u-case](https://github.com/serradura/u-case) and [kind](https://github.com/serradura/kind). This gem can be used independently, but it also contains essential features that facilitate the adoption of Solid Process (the method) in code.
|
|
249
|
+
|
|
250
|
+
<p align="right"><a href="#-table-of-contents-">⬆️ back to top</a></p>
|
data/Rakefile
CHANGED
|
@@ -13,12 +13,32 @@ require "appraisal/task"
|
|
|
13
13
|
|
|
14
14
|
Appraisal::Task.new
|
|
15
15
|
|
|
16
|
-
require "standard/rake"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
require "standard/rake" if RUBY_VERSION >= "3.4"
|
|
17
|
+
|
|
18
|
+
desc "Run the full test suite in all supported Rails versions"
|
|
19
|
+
task :matrix do
|
|
20
|
+
if RUBY_VERSION < "3.1"
|
|
21
|
+
system "bundle exec appraisal rails-6-0 rake test"
|
|
22
|
+
system "bundle exec appraisal rails-6-1 rake test"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if RUBY_VERSION >= "2.7" && RUBY_VERSION < "3.4"
|
|
26
|
+
system "bundle exec appraisal rails-7-0 rake test"
|
|
27
|
+
system "bundle exec appraisal rails-7-1 rake test"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if RUBY_VERSION >= "3.1" && RUBY_VERSION < "4.0"
|
|
31
|
+
system "bundle exec appraisal rails-7-2 rake test"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if RUBY_VERSION >= "3.2" && RUBY_VERSION < "4.0"
|
|
35
|
+
system "bundle exec appraisal rails-8-0 rake test"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if RUBY_VERSION >= "3.3"
|
|
39
|
+
system "bundle exec appraisal rails-8-1 rake test"
|
|
40
|
+
system "bundle exec appraisal rails-edge rake test"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Rake::Task[:standard].invoke if RUBY_VERSION >= "3.4"
|
|
22
44
|
end
|
|
23
|
-
|
|
24
|
-
task default: %i[test]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<small>
|
|
2
|
+
|
|
3
|
+
`Previous` [Table of Contents](../README.md#further-reading) | `Next` [Basic Usage](./020_BASIC_USAGE.md)
|
|
4
|
+
|
|
5
|
+
</small>
|
|
6
|
+
|
|
7
|
+
# The Key Concepts
|
|
8
|
+
|
|
9
|
+
### What is a process?
|
|
10
|
+
|
|
11
|
+
A sequence of steps or actions to achieve a specific end. In other words, it is a series of steps that produce a result.
|
|
12
|
+
|
|
13
|
+
### What is a `Solid::Process`?
|
|
14
|
+
|
|
15
|
+
It is a class that encapsulates reusable business logic. Its main goal is to **ACT AS AN ORCHESTRATOR** who knows the order, what to use, and the steps necessary to produce an expected result.
|
|
16
|
+
|
|
17
|
+
### Emergent Design
|
|
18
|
+
|
|
19
|
+
The business rule is directly coupled with business needs. We are often unclear about these rules and how they will be implemented as code. Clarity tends to improve over time and after many maintenance cycles.
|
|
20
|
+
|
|
21
|
+
For this reason, this abstraction embraces emerging design, allowing developers to implement code in a basic structure that can evolve and become sophisticated through the learnings obtained over time.
|
|
22
|
+
|
|
23
|
+
### The Mantra
|
|
24
|
+
|
|
25
|
+
* **Make it Work**, then
|
|
26
|
+
* **Make it Better**, then
|
|
27
|
+
* **Make it Even Better**.
|
|
28
|
+
|
|
29
|
+
Using the emerging design concept, I invite you to embrace this development cycle, write the minimum necessary to implement processes and add more solid-process features based on actual needs.
|
|
30
|
+
|
|
31
|
+
<p align="right"><a href="#the-key-concepts">⬆️ back to top</a></p>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<small>
|
|
2
|
+
|
|
3
|
+
`Previous` [Key Concepts](./010_KEY_CONCEPTS.md) | `Next` [Intermediate Usage](./030_INTERMEDIATE_USAGE.md)
|
|
4
|
+
|
|
5
|
+
</small>
|
|
6
|
+
|
|
7
|
+
# Basic Usage
|
|
8
|
+
|
|
9
|
+
**Status:** 🟡 `in-progress`
|
|
10
|
+
|
|
11
|
+
In this section, we will learn how to create a simple process to register a user.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
class User::Registration < Solid::Process
|
|
15
|
+
input do
|
|
16
|
+
attribute :email
|
|
17
|
+
attribute :password
|
|
18
|
+
attribute :password_confirmation
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call(attributes)
|
|
22
|
+
user = User.new(attributes)
|
|
23
|
+
|
|
24
|
+
return Failure(:invalid_user, user:) if user.invalid?
|
|
25
|
+
|
|
26
|
+
ActiveRecord::Base.transaction do
|
|
27
|
+
user.save!
|
|
28
|
+
|
|
29
|
+
account = Account.create!(uuid: SecureRandom.uuid)
|
|
30
|
+
|
|
31
|
+
account.memberships.create!(user: user, role: :owner)
|
|
32
|
+
|
|
33
|
+
account.task_lists.inbox.create!
|
|
34
|
+
|
|
35
|
+
user.create_token!
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
UserMailer.with(
|
|
39
|
+
user: user,
|
|
40
|
+
token: user.generate_token_for(:email_confirmation)
|
|
41
|
+
).email_confirmation.deliver_later
|
|
42
|
+
|
|
43
|
+
Success(:user_registered, user: user)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
```
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<small>
|
|
2
|
+
|
|
3
|
+
`Previous` [Basic Usage](./020_BASIC_USAGE.md) | `Next` [Advanced Usage](./040_ADVANCED_USAGE.md)
|
|
4
|
+
|
|
5
|
+
</small>
|
|
6
|
+
|
|
7
|
+
# Intermediate Usage
|
|
8
|
+
|
|
9
|
+
**Status:** 🟡 `in-progress`
|
|
10
|
+
|
|
11
|
+
In this section, we will learn how to use steps to express the process in a more structured way.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
class User::Registration < Solid::Process
|
|
15
|
+
input do
|
|
16
|
+
attribute :email, :string
|
|
17
|
+
attribute :password, :string
|
|
18
|
+
attribute :password_confirmation, :string
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call(attributes)
|
|
22
|
+
rollback_on_failure {
|
|
23
|
+
Given(attributes)
|
|
24
|
+
.and_then(:create_user)
|
|
25
|
+
.and_then(:create_user_account)
|
|
26
|
+
.and_then(:create_user_inbox)
|
|
27
|
+
.and_then(:create_user_token)
|
|
28
|
+
}
|
|
29
|
+
.and_then(:send_email_confirmation)
|
|
30
|
+
.and_expose(:user_registered, [:user])
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def create_user(email:, password:, password_confirmation:, **)
|
|
36
|
+
user = User.create(email:, password:, password_confirmation:)
|
|
37
|
+
|
|
38
|
+
return Continue(user:) if user.persisted?
|
|
39
|
+
|
|
40
|
+
input.errors.merge!(user.errors)
|
|
41
|
+
|
|
42
|
+
Failure(:invalid_input, input:)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def create_user_account(user:, **)
|
|
46
|
+
account = Account.create!(uuid: SecureRandom.uuid)
|
|
47
|
+
|
|
48
|
+
account.memberships.create!(user:, role: :owner)
|
|
49
|
+
|
|
50
|
+
Continue(account:)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def create_user_inbox(account:, **)
|
|
54
|
+
account.task_lists.inbox.create!
|
|
55
|
+
|
|
56
|
+
Continue()
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def create_user_token(user:, **)
|
|
60
|
+
user.create_token!
|
|
61
|
+
|
|
62
|
+
Continue()
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def send_email_confirmation(user:, **)
|
|
66
|
+
UserMailer.with(
|
|
67
|
+
user:,
|
|
68
|
+
token: user.generate_token_for(:email_confirmation)
|
|
69
|
+
).email_confirmation.deliver_later
|
|
70
|
+
|
|
71
|
+
Continue()
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<small>
|
|
2
|
+
|
|
3
|
+
`Previous` [Intermediate Usage](./030_INTERMEDIATE_USAGE.md) | `Next` [Error Handling](./050_ERROR_HANDLING.md)
|
|
4
|
+
|
|
5
|
+
</small>
|
|
6
|
+
|
|
7
|
+
# Advanced Usage
|
|
8
|
+
|
|
9
|
+
**Status:** 🟡 `in-progress`
|
|
10
|
+
|
|
11
|
+
In this section, we will learn how to use input normalization and validation, dependencies, and nested processes.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
class User::Registration < Solid::Process
|
|
15
|
+
deps do
|
|
16
|
+
attribute :mailer, default: UserMailer
|
|
17
|
+
attribute :token_creation, default: User::Token::Creation
|
|
18
|
+
attribute :task_list_creation, default: Account::Task::List::Creation
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
input do
|
|
22
|
+
attribute :email, :string
|
|
23
|
+
attribute :password, :string
|
|
24
|
+
attribute :password_confirmation, :string
|
|
25
|
+
|
|
26
|
+
before_validation do
|
|
27
|
+
self.email = email.downcase.strip
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
with_options presence: true do
|
|
31
|
+
validates :email, format: User::Email::REGEXP
|
|
32
|
+
validates :password, confirmation: true, length: {minimum: User::Password::MINIMUM_LENGTH}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def call(attributes)
|
|
37
|
+
rollback_on_failure {
|
|
38
|
+
Given(attributes)
|
|
39
|
+
.and_then(:check_if_email_is_taken)
|
|
40
|
+
.and_then(:create_user)
|
|
41
|
+
.and_then(:create_user_account)
|
|
42
|
+
.and_then(:create_user_inbox)
|
|
43
|
+
.and_then(:create_user_token)
|
|
44
|
+
}
|
|
45
|
+
.and_then(:send_email_confirmation)
|
|
46
|
+
.and_expose(:user_registered, [:user])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def check_if_email_is_taken(email:, **)
|
|
52
|
+
input.errors.add(:email, "has already been taken") if User.exists?(email:)
|
|
53
|
+
|
|
54
|
+
input.errors.any? ? Failure(:invalid_input, input:) : Continue()
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def create_user(email:, password:, password_confirmation:, **)
|
|
58
|
+
user = User.create(email:, password:, password_confirmation:)
|
|
59
|
+
|
|
60
|
+
return Continue(user:) if user.persisted?
|
|
61
|
+
|
|
62
|
+
input.errors.merge!(user.errors)
|
|
63
|
+
|
|
64
|
+
Failure(:invalid_input, input:)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def create_user_account(user:, **)
|
|
68
|
+
account = Account.create!(uuid: SecureRandom.uuid)
|
|
69
|
+
|
|
70
|
+
account.memberships.create!(user:, role: :owner)
|
|
71
|
+
|
|
72
|
+
Continue(account:)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def create_user_inbox(account:, **)
|
|
76
|
+
case deps.task_list_creation.call(account:, inbox: true)
|
|
77
|
+
in Solid::Success(task_list:) then Continue()
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def create_user_token(user:, **)
|
|
82
|
+
case deps.token_creation.call(user:)
|
|
83
|
+
in Solid::Success(token:) then Continue()
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def send_email_confirmation(user:, **)
|
|
88
|
+
deps.mailer.with(
|
|
89
|
+
user:,
|
|
90
|
+
token: user.generate_token_for(:email_confirmation)
|
|
91
|
+
).email_confirmation.deliver_later
|
|
92
|
+
|
|
93
|
+
Continue()
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
```
|
data/docs/060_TESTING.md
ADDED
|
@@ -9,7 +9,7 @@ class Solid::Process::BacktraceCleaner < ActiveSupport::BacktraceCleaner
|
|
|
9
9
|
|
|
10
10
|
private
|
|
11
11
|
|
|
12
|
-
BLOCKS_PATTERN = /in [`']block in|in `then'|internal:kernel|block \(\d+ levels?\) in/.freeze
|
|
12
|
+
BLOCKS_PATTERN = /in [`']block in|in [`'](?:Kernel#)?then'|internal:kernel|block \(\d+ levels?\) in/.freeze
|
|
13
13
|
|
|
14
14
|
def add_blocks_silencer
|
|
15
15
|
add_silencer { |line| line.match?(BLOCKS_PATTERN) }
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
class Solid::Process::EventLogs::BasicLoggerListener
|
|
4
|
-
include ActiveSupport::Configurable
|
|
5
4
|
include Solid::Result::EventLogs::Listener
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
class_attribute :logger, :backtrace_cleaner
|
|
8
7
|
|
|
9
8
|
self.logger = ActiveSupport::Logger.new($stdout)
|
|
10
9
|
self.backtrace_cleaner = Solid::Process::BacktraceCleaner.new
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid-process
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rodrigo Serradura
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: solid-result
|
|
@@ -59,11 +59,22 @@ executables: []
|
|
|
59
59
|
extensions: []
|
|
60
60
|
extra_rdoc_files: []
|
|
61
61
|
files:
|
|
62
|
+
- ".claude/CLAUDE.md"
|
|
62
63
|
- CHANGELOG.md
|
|
63
64
|
- CODE_OF_CONDUCT.md
|
|
64
65
|
- LICENSE.txt
|
|
65
66
|
- README.md
|
|
66
67
|
- Rakefile
|
|
68
|
+
- docs/010_KEY_CONCEPTS.md
|
|
69
|
+
- docs/020_BASIC_USAGE.md
|
|
70
|
+
- docs/030_INTERMEDIATE_USAGE.md
|
|
71
|
+
- docs/040_ADVANCED_USAGE.md
|
|
72
|
+
- docs/050_ERROR_HANDLING.md
|
|
73
|
+
- docs/060_TESTING.md
|
|
74
|
+
- docs/070_INSTRUMENTATION.md
|
|
75
|
+
- docs/080_RAILS_INTEGRATION.md
|
|
76
|
+
- docs/090_INTERNAL_LIBRARIES.md
|
|
77
|
+
- docs/100_PORTS_AND_ADAPTERS.md
|
|
67
78
|
- examples/business_processes/.rubocop.yml
|
|
68
79
|
- examples/business_processes/.ruby-version
|
|
69
80
|
- examples/business_processes/.standard.yml
|
|
@@ -110,7 +121,8 @@ metadata:
|
|
|
110
121
|
allowed_push_host: https://rubygems.org
|
|
111
122
|
homepage_uri: https://github.com/serradura/solid-process
|
|
112
123
|
source_code_uri: https://github.com/serradura/solid-process
|
|
113
|
-
changelog_uri: https://github.com/
|
|
124
|
+
changelog_uri: https://github.com/solid-process/solid-process/blob/main/CHANGELOG.md
|
|
125
|
+
rubygems_mfa_required: 'true'
|
|
114
126
|
post_install_message:
|
|
115
127
|
rdoc_options: []
|
|
116
128
|
require_paths:
|
|
@@ -126,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
126
138
|
- !ruby/object:Gem::Version
|
|
127
139
|
version: '0'
|
|
128
140
|
requirements: []
|
|
129
|
-
rubygems_version: 3.
|
|
141
|
+
rubygems_version: 3.2.33
|
|
130
142
|
signing_key:
|
|
131
143
|
specification_version: 4
|
|
132
144
|
summary: Write business logic for Ruby/Rails that scales.
|