This page demonstrates real-world process implementations from the Solid::Process test suite and examples. The UserCreation, Account::OwnerCreation, and User::Token::Creation processes illustrate patterns for input validation, dependency injection, error handling, database transactions, and process composition.
These examples show how to structure business logic using Solid::Process primitives: the input DSL, deps DSL, Steps DSL (Given/and_then/Continue/and_expose), and rollback_on_failure transactions. For framework concepts, see page 2. For testing strategies, see page 7.1.
The example processes demonstrate hierarchical composition where higher-level processes orchestrate lower-level processes through dependency injection:
Sources: examples/business_processes/app/models/account/owner_creation.rb1-53 examples/business_processes/app/models/user/creation.rb1-62 examples/business_processes/app/models/user/token/creation.rb1-39
| Process | Orchestrates | Input Attributes | Success Type | Value Keys |
|---|---|---|---|---|
Account::OwnerCreation | User::Creation | uuid, owner | :account_owner_created | user, account |
User::Creation | User::Token::Creation | uuid, name, email, password, password_confirmation | :user_created | user, token |
User::Token::Creation | None (leaf process) | user, executed_at | :token_created or :token_already_exists | token |
Sources: examples/business_processes/app/models/account/owner_creation.rb5-6 examples/business_processes/app/models/user/creation.rb5-6 examples/business_processes/app/models/user/token/creation.rb5-11
The Account::OwnerCreation class demonstrates the orchestrator pattern - a process that coordinates multiple sub-processes and database operations within a transaction. It shows dependency injection, nested process composition, and error wrapping.
Sources: examples/business_processes/app/models/account/owner_creation.rb21-28 examples/business_processes/app/models/account/owner_creation.rb32-38
The input block demonstrates validation of multiple data types and attribute normalization via before_validation:
Sources: examples/business_processes/app/models/account/owner_creation.rb9-19
The deps block declares a user_creation dependency with a default implementation, enabling test isolation:
Sources: examples/business_processes/app/models/account/owner_creation.rb5-7
The call method demonstrates wrapping Steps DSL chains in rollback_on_failure to ensure atomic database operations:
| Step Method | Purpose | Returns | Failure Type |
|---|---|---|---|
create_owner | Calls nested deps.user_creation process | Continue(user:, user_token:) | Failure(:invalid_owner, ...) |
create_account | Creates Account record with UUID | Continue(account:) | Failure(:invalid_account, errors:) |
link_owner_to_account | Creates Member join record | Continue() | Exception (rolled back) |
The final and_expose(:account_owner_created, [:user, :account]) creates a success result containing only the specified keys.
Sources: examples/business_processes/app/models/account/owner_creation.rb21-28 examples/business_processes/app/models/account/owner_creation.rb32-51
The User::Creation class demonstrates multi-step validation and composition. It validates email uniqueness outside the transaction, then creates the user and token inside a transaction. This pattern avoids unnecessary database locks.
Sources: examples/business_processes/app/models/user/creation.rb28-38 examples/business_processes/app/models/user/creation.rb43-59
The process includes sophisticated input cleaning and validation:
Sources: examples/business_processes/app/models/user/creation.rb9-26
| Method | Executes | Returns on Success | Returns on Failure |
|---|---|---|---|
validate_email_uniqueness | User.exists?(email:) | Continue() | Failure(:email_already_taken) |
create_user | User.create!(...) + password encryption | Continue(user:) | Failure(:invalid_record, errors:) |
create_user_token | deps.token_creation.call(user:) | Continue(token:) | Exception (propagated) |
The create_user step shows the attribute extraction pattern using except to filter attributes before persistence, and BCrypt password hashing before saving.
Sources: examples/business_processes/app/models/user/creation.rb43-59
The User::Token::Creation class demonstrates the idempotency pattern. It checks for an existing token and returns success immediately if found, avoiding duplicate token creation. This is a leaf process with no dependencies.
Sources: examples/business_processes/app/models/user/token/creation.rb13-17 examples/business_processes/app/models/user/token/creation.rb22-37
The create_token_if_not_exists method demonstrates secure token generation with expiration dates:
| Field | Value | Purpose |
|---|---|---|
access_token | SecureRandom.hex(24) | 48-character random hex string |
refresh_token | SecureRandom.hex(24) | 48-character random hex string |
access_token_expires_at | executed_at + 15.days | Short-lived access token |
refresh_token_expires_at | executed_at + 30.days | Long-lived refresh token |
Returns Continue(token:) on success or Failure(:token_creation_failed, errors:) if persistence fails.
Sources: examples/business_processes/app/models/user/token/creation.rb22-37
Sources: examples/business_processes/app/models/user/token/creation.rb5-11
The example processes demonstrate three distinct transaction and error handling strategies:
| Process | Rollback Pattern | Scope | Trigger |
|---|---|---|---|
Account::OwnerCreation | rollback_on_failure wrapper | Entire chain | Any step failure |
User::Creation | Nested rollback_on_failure | User + token creation only | Create/token failures |
User::Token::Creation | No rollback (idempotent) | N/A | N/A |
Sources: examples/business_processes/app/models/account/owner_creation.rb22-27 examples/business_processes/app/models/user/creation.rb31-36
The create_owner step method shows how to handle nested process results using Ruby 3.1+ pattern matching:
This pattern:
deps.user_creation.call(owner)user:, token:):invalid_ownerThe error wrapping creates hierarchical failures like Failure(:invalid_owner, email_already_taken: {...}), preserving context from nested processes.
Sources: examples/business_processes/app/models/account/owner_creation.rb33-38
The test suite demonstrates how dependency injection enables isolated testing of composed processes.
The test file test/solid/process/dependencies/result_test.rb shows testing a process with injectable dependencies:
This pattern allows testing with real dependencies in integration tests while enabling mocks in unit tests.
Sources: test/solid/process/dependencies/result_test.rb42-69
When dependencies fail validation, the process returns Failure(:invalid_dependencies, dependencies:) without executing the call method.
Sources: test/solid/process/dependencies/result_test.rb21-40
The test file test/solid/process/result_test.rb demonstrates testing simpler processes without dependencies:
The Failure(:invalid_input, input:) result provides access to the input object with validation errors.
Sources: test/solid/process/result_test.rb48-70
Business logic failures (distinct from validation failures) use custom failure types like :email_already_taken.
Sources: test/solid/process/result_test.rb72-84
The test suite includes a RuntimeBreaker utility for simulating failures at specific points in the process execution:
This allows testing of rollback behavior and error handling at precise execution points:
ACCOUNT_CREATION - Interrupts account creationUSER_CREATION - Interrupts user creationUSER_TOKEN_CREATION - Interrupts token creationSources: test/support/001_runtime_breaker.rb1-19 test/support/052_user_creation.rb47 test/support/051_user_token_creation.rb28
Refresh this wiki