operandi 5.0.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.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.cursor/rules/services/RULE.md +269 -0
  3. data/.cursor/rules/services-rspec/RULE.md +354 -0
  4. data/.github/dependabot.yml +11 -0
  5. data/.github/workflows/ci.yml +77 -0
  6. data/.gitignore +25 -0
  7. data/.rspec +3 -0
  8. data/.rubocop.yml +134 -0
  9. data/.ruby-version +1 -0
  10. data/.vscode/cspell.json +18 -0
  11. data/.vscode/project-words.txt +12 -0
  12. data/AGENTS.md +139 -0
  13. data/CHANGELOG.md +111 -0
  14. data/CLAUDE.md +139 -0
  15. data/CODE_OF_CONDUCT.md +74 -0
  16. data/Gemfile +28 -0
  17. data/Gemfile.lock +149 -0
  18. data/LICENSE.txt +21 -0
  19. data/README.md +172 -0
  20. data/Rakefile +8 -0
  21. data/bin/console +15 -0
  22. data/bin/setup +8 -0
  23. data/config/default.yml +57 -0
  24. data/docs/README.md +105 -0
  25. data/docs/SUMMARY.md +31 -0
  26. data/docs/arguments.md +275 -0
  27. data/docs/best-practices.md +153 -0
  28. data/docs/callbacks.md +475 -0
  29. data/docs/concepts.md +79 -0
  30. data/docs/configuration.md +218 -0
  31. data/docs/context.md +128 -0
  32. data/docs/crud.md +525 -0
  33. data/docs/errors.md +331 -0
  34. data/docs/generators.md +250 -0
  35. data/docs/outputs.md +150 -0
  36. data/docs/pundit-authorization.md +320 -0
  37. data/docs/quickstart.md +133 -0
  38. data/docs/recipes.md +14 -0
  39. data/docs/rubocop.md +430 -0
  40. data/docs/ruby-lsp.md +121 -0
  41. data/docs/service-rendering.md +222 -0
  42. data/docs/sorbet-runtime.md +283 -0
  43. data/docs/steps.md +438 -0
  44. data/docs/tapioca.md +188 -0
  45. data/docs/testing.md +548 -0
  46. data/lib/generators/operandi/install/USAGE +15 -0
  47. data/lib/generators/operandi/install/install_generator.rb +45 -0
  48. data/lib/generators/operandi/install/templates/application_service.rb.tt +8 -0
  49. data/lib/generators/operandi/install/templates/application_service_spec.rb.tt +7 -0
  50. data/lib/generators/operandi/install/templates/initializer.rb.tt +30 -0
  51. data/lib/generators/operandi/service/USAGE +21 -0
  52. data/lib/generators/operandi/service/service_generator.rb +80 -0
  53. data/lib/generators/operandi/service/templates/service.rb.tt +48 -0
  54. data/lib/generators/operandi/service/templates/service_spec.rb.tt +40 -0
  55. data/lib/operandi/base.rb +230 -0
  56. data/lib/operandi/base_with_context.rb +57 -0
  57. data/lib/operandi/callbacks.rb +353 -0
  58. data/lib/operandi/collection.rb +166 -0
  59. data/lib/operandi/concerns/execution.rb +80 -0
  60. data/lib/operandi/concerns/parent_service.rb +32 -0
  61. data/lib/operandi/concerns/state_management.rb +34 -0
  62. data/lib/operandi/config.rb +142 -0
  63. data/lib/operandi/constants.rb +96 -0
  64. data/lib/operandi/dsl/arguments_dsl.rb +83 -0
  65. data/lib/operandi/dsl/outputs_dsl.rb +79 -0
  66. data/lib/operandi/dsl/steps_dsl.rb +206 -0
  67. data/lib/operandi/dsl/validation.rb +171 -0
  68. data/lib/operandi/exceptions.rb +66 -0
  69. data/lib/operandi/message.rb +52 -0
  70. data/lib/operandi/messages.rb +185 -0
  71. data/lib/operandi/rspec/matchers/define_argument.rb +172 -0
  72. data/lib/operandi/rspec/matchers/define_output.rb +145 -0
  73. data/lib/operandi/rspec/matchers/define_step.rb +223 -0
  74. data/lib/operandi/rspec/matchers/execute_step.rb +228 -0
  75. data/lib/operandi/rspec/matchers/have_error_on.rb +144 -0
  76. data/lib/operandi/rspec/matchers/have_warning_on.rb +146 -0
  77. data/lib/operandi/rspec/matchers/trigger_callback.rb +136 -0
  78. data/lib/operandi/rspec.rb +15 -0
  79. data/lib/operandi/rubocop/cop/operandi/argument_type_required.rb +52 -0
  80. data/lib/operandi/rubocop/cop/operandi/condition_method_exists.rb +173 -0
  81. data/lib/operandi/rubocop/cop/operandi/deprecated_accessors.rb +113 -0
  82. data/lib/operandi/rubocop/cop/operandi/deprecated_methods.rb +113 -0
  83. data/lib/operandi/rubocop/cop/operandi/dsl_order.rb +181 -0
  84. data/lib/operandi/rubocop/cop/operandi/missing_private_keyword.rb +102 -0
  85. data/lib/operandi/rubocop/cop/operandi/no_direct_instantiation.rb +66 -0
  86. data/lib/operandi/rubocop/cop/operandi/no_hash_argument.rb +101 -0
  87. data/lib/operandi/rubocop/cop/operandi/output_type_required.rb +52 -0
  88. data/lib/operandi/rubocop/cop/operandi/prefer_fail_method.rb +112 -0
  89. data/lib/operandi/rubocop/cop/operandi/prefer_optional_over_default_nil.rb +124 -0
  90. data/lib/operandi/rubocop/cop/operandi/redundant_optional.rb +103 -0
  91. data/lib/operandi/rubocop/cop/operandi/reserved_name.rb +56 -0
  92. data/lib/operandi/rubocop/cop/operandi/step_method_exists.rb +134 -0
  93. data/lib/operandi/rubocop.rb +22 -0
  94. data/lib/operandi/settings/field.rb +147 -0
  95. data/lib/operandi/settings/step.rb +105 -0
  96. data/lib/operandi/utils.rb +36 -0
  97. data/lib/operandi/version.rb +5 -0
  98. data/lib/operandi.rb +11 -0
  99. data/lib/ruby_lsp/operandi/addon.rb +37 -0
  100. data/lib/ruby_lsp/operandi/definition.rb +134 -0
  101. data/lib/ruby_lsp/operandi/indexing_enhancement.rb +224 -0
  102. data/lib/tapioca/dsl/compilers/operandi.rb +378 -0
  103. data/operandi.gemspec +33 -0
  104. data/rbi/operandi.rbi +197 -0
  105. data/sorbet/cache/data.mdb +0 -0
  106. data/sorbet/cache/lock.mdb +0 -0
  107. metadata +151 -0
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ # Common
2
+ /.bundle/
3
+ /.yardoc
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
11
+
12
+ # RSpec failure tracking
13
+ .rspec_status
14
+
15
+ # Ignore IDE files
16
+ /.idea
17
+ *.iml
18
+ .vscode/settings.json
19
+
20
+ # Ignore gem files
21
+ *.gem
22
+
23
+ # Ignore mac files
24
+ *.DS_Store
25
+
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,134 @@
1
+ plugins:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+ - rubocop-performance
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 3.1
8
+ DisplayCopNames: true
9
+ DisplayStyleGuide: true
10
+ NewCops: enable
11
+
12
+ Layout/FirstArrayElementIndentation:
13
+ EnforcedStyle: consistent
14
+
15
+ Layout/FirstHashElementIndentation:
16
+ EnforcedStyle: consistent
17
+
18
+ Layout/MultilineMethodCallIndentation:
19
+ EnforcedStyle: indented
20
+
21
+ Layout/SpaceInsideArrayLiteralBrackets:
22
+ EnforcedStyle: no_space
23
+
24
+ Layout/FirstMethodArgumentLineBreak:
25
+ Enabled: true
26
+ AllowMultilineFinalElement: true
27
+
28
+ Layout/MultilineMethodArgumentLineBreaks:
29
+ Enabled: true
30
+ AllowMultilineFinalElement: true
31
+
32
+ Layout/FirstMethodParameterLineBreak:
33
+ Enabled: true
34
+ AllowMultilineFinalElement: true
35
+
36
+ Layout/MultilineMethodParameterLineBreaks:
37
+ Enabled: true
38
+ AllowMultilineFinalElement: true
39
+
40
+ Layout/MultilineHashKeyLineBreaks:
41
+ Enabled: true
42
+ AllowMultilineFinalElement: true
43
+
44
+ Layout/MultilineArrayLineBreaks:
45
+ Enabled: true
46
+ AllowMultilineFinalElement: true
47
+
48
+ Lint/EmptyFile:
49
+ Enabled: true
50
+
51
+ Metrics/AbcSize:
52
+ Exclude:
53
+ - lib/operandi/dsl/steps_dsl.rb
54
+ - lib/operandi/messages.rb
55
+ Max: 20
56
+
57
+ Metrics/BlockLength:
58
+ Exclude:
59
+ - spec/**/*_spec.rb
60
+
61
+ Metrics/ClassLength:
62
+ Max: 150
63
+
64
+ Metrics/CyclomaticComplexity:
65
+ Max: 10
66
+
67
+ Metrics/MethodLength:
68
+ Max: 20
69
+
70
+ Metrics/PerceivedComplexity:
71
+ Max: 10
72
+
73
+ Naming/PredicatePrefix:
74
+ Enabled: false
75
+
76
+ RSpec/ExampleLength:
77
+ Max: 20
78
+
79
+ RSpec/MultipleDescribes:
80
+ Enabled: false
81
+
82
+ RSpec/MultipleExpectations:
83
+ Max: 10
84
+
85
+ RSpec/MultipleMemoizedHelpers:
86
+ Max: 10
87
+
88
+ RSpec/NestedGroups:
89
+ Max: 5
90
+
91
+ Security/Eval:
92
+ Exclude:
93
+ - spec/operandi/error_spec.rb
94
+
95
+ Style/ClassAndModuleChildren:
96
+ Exclude:
97
+ - spec/data/services/**/*.rb
98
+
99
+ Style/ClassVars:
100
+ Enabled: false
101
+
102
+ Style/ConditionalAssignment:
103
+ Enabled: false
104
+
105
+ Style/Documentation:
106
+ Enabled: false
107
+
108
+ Style/GuardClause:
109
+ Enabled: false
110
+
111
+ Style/OptionalBooleanParameter:
112
+ Exclude:
113
+ - lib/operandi/rspec/matchers/**/*.rb
114
+
115
+ Style/StringLiterals:
116
+ EnforcedStyle: double_quotes
117
+
118
+ Style/SymbolArray:
119
+ EnforcedStyle: brackets
120
+
121
+ Style/TrailingCommaInArguments:
122
+ EnforcedStyleForMultiline: diff_comma
123
+
124
+ Style/TrailingCommaInArrayLiteral:
125
+ EnforcedStyleForMultiline: consistent_comma
126
+
127
+ Style/TrailingCommaInHashLiteral:
128
+ EnforcedStyleForMultiline: consistent_comma
129
+
130
+ Style/WordArray:
131
+ EnforcedStyle: brackets
132
+
133
+ Style/NumericPredicate:
134
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 4.0.3
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
3
+ "version": "0.2",
4
+ "dictionaryDefinitions": [
5
+ {
6
+ "name": "project-words",
7
+ "path": "./project-words.txt",
8
+ "addWords": true
9
+ }
10
+ ],
11
+ "dictionaries": [
12
+ "project-words"
13
+ ],
14
+ "ignorePaths": [
15
+ ".idea",
16
+ ".vscode"
17
+ ]
18
+ }
@@ -0,0 +1,12 @@
1
+ activerecord
2
+ andrewmcodes
3
+ bindir
4
+ delegators
5
+ Hanami
6
+ IVAR
7
+ Kodkod
8
+ kwargs
9
+ libsqlite
10
+ nilable
11
+ noreturn
12
+ rubocop
data/AGENTS.md ADDED
@@ -0,0 +1,139 @@
1
+ # AGENTS.md
2
+
3
+ This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
4
+
5
+ ## About Operandi
6
+
7
+ Operandi is a Ruby gem providing a service architecture pattern for organizing business logic. Services are defined as classes with `arguments`, `steps`, and `outputs`, featuring transactions, inheritance, error handling, and context sharing.
8
+
9
+ ## Development Commands
10
+
11
+ ### Testing
12
+ ```bash
13
+ # Run all tests
14
+ bundle exec rspec
15
+
16
+ # Run specific test file
17
+ bundle exec rspec spec/path/to/file_spec.rb
18
+
19
+ # Run with coverage (uses SimpleCov)
20
+ bundle exec rspec
21
+ ```
22
+
23
+ ### Linting
24
+ ```bash
25
+ # Run RuboCop linter
26
+ bundle exec rubocop
27
+
28
+ # Auto-fix RuboCop issues
29
+ bundle exec rubocop -a
30
+ ```
31
+
32
+ ### Build and Release
33
+ ```bash
34
+ # Build gem
35
+ bundle exec rake build
36
+
37
+ # Default task (runs tests)
38
+ bundle exec rake
39
+ ```
40
+
41
+ ## Architecture
42
+
43
+ ### Core Components
44
+
45
+ 1. **Base Service (`lib/operandi/base.rb:15`)**
46
+ - Main service class that all services inherit from
47
+ - Handles service lifecycle: initialization, execution, callbacks, error management
48
+ - Provides DSL for defining arguments, steps, and outputs
49
+ - Manages transactions and error propagation to parent services
50
+
51
+ 2. **Callbacks System (`lib/operandi/callbacks.rb:5`)**
52
+ - Supports service and step-level callbacks
53
+ - Events: `before_service_run`, `after_service_run`, `around_service_run`, `on_service_success`, `on_service_failure`
54
+ - Step events: `before_step_run`, `after_step_run`, `around_step_run`, `on_step_success`, `on_step_failure`
55
+
56
+ 3. **Settings**
57
+ - **Step (`lib/operandi/settings/step.rb:7`)**: Handles step execution with conditional logic (`if`, `unless`, `always`)
58
+ - **Field (`lib/operandi/settings/field.rb`)**: Manages argument and output validation and type checking
59
+
60
+ 4. **Messages System (`lib/operandi/messages.rb`)**
61
+ - Collects errors and warnings with options for breaking, raising, or rolling back
62
+ - Supports copying messages between parent and child services
63
+
64
+ 5. **Collection (`lib/operandi/collection.rb`)**
65
+ - Manages arguments and outputs as collections with validation and defaults
66
+ - Supports Sorbet runtime types for type validation
67
+
68
+ ### Service DSL
69
+
70
+ Services use a declarative DSL:
71
+ ```ruby
72
+ class ExampleService < Operandi::Base
73
+ # Define input arguments
74
+ arg :name, type: String
75
+ arg :age, type: Integer, optional: true, default: 25
76
+
77
+ # Define execution steps
78
+ step :validate_input
79
+ step :process_data, if: :should_process?
80
+ step :cleanup, always: true
81
+
82
+ # Define outputs
83
+ output :result, type: Hash
84
+
85
+ private
86
+
87
+ def validate_input
88
+ errors.add(:name, "required") if name.nil? || name.strip.empty?
89
+ end
90
+
91
+ def process_data
92
+ self.result = { name: name, age: age }
93
+ end
94
+
95
+ def cleanup
96
+ # Runs regardless of errors/warnings, unless stop! was called
97
+ end
98
+
99
+ def should_process?
100
+ !(name.nil? || name.strip.empty?)
101
+ end
102
+ end
103
+ ```
104
+
105
+ ### Service Execution
106
+
107
+ - Services can be run with `.run(args)` or `.run!(args)` (raises on error)
108
+ - Use `.with(service_or_context)` to chain services with shared context
109
+ - Transactions automatically rollback on errors when `use_transactions: true`
110
+ - Steps run sequentially, stopping on errors unless `always: true`
111
+
112
+ ### Error Handling
113
+
114
+ - Errors collected in `@errors` message collection
115
+ - Supports `break_on_error`, `raise_on_error`, `rollback_on_error` configuration
116
+ - Warnings work similarly with `@warnings` collection
117
+ - Parent services can inherit child errors/warnings based on configuration
118
+
119
+ ### Testing Patterns
120
+
121
+ - Services are tested using RSpec with database transactions
122
+ - Use `DatabaseCleaner` for test isolation
123
+ - Mock database models and external dependencies
124
+ - Test both success and failure scenarios
125
+ - Validate arguments, outputs, and error conditions
126
+
127
+ ## Configuration
128
+
129
+ ### RuboCop Rules
130
+ - Target Ruby version: 2.7+
131
+ - Method length max: 20 lines
132
+ - Uses double quotes for strings
133
+ - Enables trailing commas for multiline structures
134
+ - Disables documentation requirements and guard clauses
135
+
136
+ ### Database Support
137
+ - Optional ActiveRecord integration for transactions
138
+ - Uses SQLite3 for testing
139
+ - Database cleaner ensures test isolation
data/CHANGELOG.md ADDED
@@ -0,0 +1,111 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ### Added
6
+
7
+ - Add `Operandi::RuntimeError` with access to the failed service instance
8
+ - Add the associated service class to `Operandi::ArgTypeError`
9
+
10
+ ### Breaking changes
11
+
12
+ - Remove deprecated methods: `done!`, `done?`, `arguments` and `outputs` methods
13
+
14
+ ## 4.0.0 (2026-01-01)
15
+
16
+ ### Added
17
+
18
+ - Allow to specify `parent: true` for `step` to skip `StepMethodExists` Rubocop cop
19
+
20
+ ### Breaking changes
21
+
22
+ - Rename gem from `light-services to `operandi`
23
+ Reason: No folder nesting. The gem isn't really "light" anymore anyway.
24
+
25
+ - Removed dry-types support. Sorbet runtime types are now the recommended type system for advanced type validation. Plain Ruby class types continue to work as before.
26
+
27
+ - The `run` and `run!` methods now accept only keyword arguments, passing a hash as the first argument is no longer supported.
28
+
29
+ ## 3.3.1 (2025-12-16)
30
+
31
+ ### Added
32
+
33
+ - Sorbet runtime type support for `arg` and `output` (validation only, no coercion)
34
+
35
+ ## 3.3.0 (2025-12-15)
36
+
37
+ ### Added
38
+
39
+ - Sorbet and Tapioca support for `arg` and `output`
40
+
41
+ ## 3.2.1 (2025-12-15)
42
+
43
+ ### Added
44
+
45
+ - Add RuboCop cop `ReservedName`
46
+
47
+ ## 3.2.0 (2025-12-15)
48
+
49
+ ### Added
50
+
51
+ - Add Cursor rules
52
+ - Add `successful?` as an alias for `success?`
53
+ - Add RuboCop cop `PreferFailMethod` to detect `errors.add(:base, "message")` and suggest using `fail!("message")` instead
54
+
55
+ ### Breaking changes
56
+
57
+ - Service runs steps with `always: true` after `fail_immediately!` was called
58
+
59
+ ## 3.1.2 (2025-12-14)
60
+
61
+ ### Added
62
+
63
+ - Add `fail!` and `fail_immediately!` helpers
64
+
65
+ ### Changed
66
+
67
+ - Split `config.require_type` into `config.require_arg_type` and `config.require_output_type`
68
+
69
+ ## 3.1.1 (2025-12-14)
70
+
71
+ ### Added
72
+
73
+ - Better IDE support for callbacks DSL
74
+
75
+ ## 3.1.0 (2025-12-13)
76
+
77
+ ### Breaking changes
78
+
79
+ - Enforce arguments and output types by default. Use `config.require_arg_type = false` and `config.require_output_type = false` to disable this behavior. The convenience setter `config.require_type = false` sets both options at once for backward compatibility.
80
+
81
+ ### Added
82
+
83
+ - `stop!` and `stopped?` methods for early exit (renamed from `done!` and `done?`)
84
+ - `stop_immediately!` method for immediate execution halt within the current step
85
+ - `done!` and `done?` are deprecated, but remain available as aliases for backward compatibility
86
+ - Ruby LSP support with step navigation and indexing
87
+ - Rubocop cops `StepMethodExists`, `ConditionMethodExists`, `DslOrder`, `MissingPrivateKeyword`, `NoDirectInstantiation`, `ArgumentTypeRequired`, `OutputTypeRequired`, `DeprecatedMethods`
88
+ - Comprehensive YARD documentation
89
+
90
+ ## 3.0.0 (2025-12-12)
91
+
92
+ ### Breaking changes
93
+
94
+ - Removed support for symbol types (e.g., `:array`, `:hash`, `:boolean`). Use Ruby classes (e.g., `Array`, `Hash`, `[TrueClass, FalseClass]`) or dry-types
95
+ - Removed `benchmark: true` option
96
+ - Removed `verbose: true` option
97
+ - Bumped minimum supported Ruby version to **3.0**.
98
+ - Removed `errors.copy_to`
99
+
100
+ ### Added
101
+
102
+ - Output type validation
103
+ - dry-types support for arguments and outputs (with coercion and constraints)
104
+ - Callback system (service + step callbacks)
105
+ - Built-in RSpec matchers for services
106
+ - Name validation for arguments, steps, and outputs
107
+ - `run` method fallback when no steps are defined
108
+
109
+ ### Documentation
110
+
111
+ - Documentation moved into this repository and refreshed to match v3 behavior
data/CLAUDE.md ADDED
@@ -0,0 +1,139 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## About Operandi
6
+
7
+ Operandi is a Ruby gem providing a service architecture pattern for organizing business logic. Services are defined as classes with `arguments`, `steps`, and `outputs`, featuring transactions, inheritance, error handling, and context sharing.
8
+
9
+ ## Development Commands
10
+
11
+ ### Testing
12
+ ```bash
13
+ # Run all tests
14
+ bundle exec rspec
15
+
16
+ # Run specific test file
17
+ bundle exec rspec spec/path/to/file_spec.rb
18
+
19
+ # Run with coverage (uses SimpleCov)
20
+ bundle exec rspec
21
+ ```
22
+
23
+ ### Linting
24
+ ```bash
25
+ # Run RuboCop linter
26
+ bundle exec rubocop
27
+
28
+ # Auto-fix RuboCop issues
29
+ bundle exec rubocop -a
30
+ ```
31
+
32
+ ### Build and Release
33
+ ```bash
34
+ # Build gem
35
+ bundle exec rake build
36
+
37
+ # Default task (runs tests)
38
+ bundle exec rake
39
+ ```
40
+
41
+ ## Architecture
42
+
43
+ ### Core Components
44
+
45
+ 1. **Base Service (`lib/operandi/base.rb:15`)**
46
+ - Main service class that all services inherit from
47
+ - Handles service lifecycle: initialization, execution, callbacks, error management
48
+ - Provides DSL for defining arguments, steps, and outputs
49
+ - Manages transactions and error propagation to parent services
50
+
51
+ 2. **Callbacks System (`lib/operandi/callbacks.rb:5`)**
52
+ - Supports service and step-level callbacks
53
+ - Events: `before_service_run`, `after_service_run`, `around_service_run`, `on_service_success`, `on_service_failure`
54
+ - Step events: `before_step_run`, `after_step_run`, `around_step_run`, `on_step_success`, `on_step_failure`
55
+
56
+ 3. **Settings**
57
+ - **Step (`lib/operandi/settings/step.rb:7`)**: Handles step execution with conditional logic (`if`, `unless`, `always`)
58
+ - **Field (`lib/operandi/settings/field.rb`)**: Manages argument and output validation and type checking
59
+
60
+ 4. **Messages System (`lib/operandi/messages.rb`)**
61
+ - Collects errors and warnings with options for breaking, raising, or rolling back
62
+ - Supports copying messages between parent and child services
63
+
64
+ 5. **Collection (`lib/operandi/collection.rb`)**
65
+ - Manages arguments and outputs as collections with validation and defaults
66
+ - Supports Sorbet runtime types for type validation
67
+
68
+ ### Service DSL
69
+
70
+ Services use a declarative DSL:
71
+ ```ruby
72
+ class ExampleService < Operandi::Base
73
+ # Define input arguments
74
+ arg :name, type: String
75
+ arg :age, type: Integer, optional: true, default: 25
76
+
77
+ # Define execution steps
78
+ step :validate_input
79
+ step :process_data, if: :should_process?
80
+ step :cleanup, always: true
81
+
82
+ # Define outputs
83
+ output :result, type: Hash
84
+
85
+ private
86
+
87
+ def validate_input
88
+ errors.add(:name, "required") if name.nil? || name.strip.empty?
89
+ end
90
+
91
+ def process_data
92
+ self.result = { name: name, age: age }
93
+ end
94
+
95
+ def cleanup
96
+ # Runs regardless of errors/warnings, unless stop! was called
97
+ end
98
+
99
+ def should_process?
100
+ !(name.nil? || name.strip.empty?)
101
+ end
102
+ end
103
+ ```
104
+
105
+ ### Service Execution
106
+
107
+ - Services can be run with `.run(args)` or `.run!(args)` (raises on error)
108
+ - Use `.with(service_or_context)` to chain services with shared context
109
+ - Transactions automatically rollback on errors when `use_transactions: true`
110
+ - Steps run sequentially, stopping on errors unless `always: true`
111
+
112
+ ### Error Handling
113
+
114
+ - Errors collected in `@errors` message collection
115
+ - Supports `break_on_error`, `raise_on_error`, `rollback_on_error` configuration
116
+ - Warnings work similarly with `@warnings` collection
117
+ - Parent services can inherit child errors/warnings based on configuration
118
+
119
+ ### Testing Patterns
120
+
121
+ - Services are tested using RSpec with database transactions
122
+ - Use `DatabaseCleaner` for test isolation
123
+ - Mock database models and external dependencies
124
+ - Test both success and failure scenarios
125
+ - Validate arguments, outputs, and error conditions
126
+
127
+ ## Configuration
128
+
129
+ ### RuboCop Rules
130
+ - Target Ruby version: 2.7+
131
+ - Method length max: 20 lines
132
+ - Uses double quotes for strings
133
+ - Enables trailing commas for multiline structures
134
+ - Disables documentation requirements and guard clauses
135
+
136
+ ### Database Support
137
+ - Optional ActiveRecord integration for transactions
138
+ - Uses SQLite3 for testing
139
+ - Database cleaner ensures test isolation
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at emelianenko.web@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ group :test do
8
+ gem "sorbet-runtime"
9
+
10
+ gem "activerecord", ">= 8"
11
+ gem "connection_pool", "< 3"
12
+ gem "database_cleaner-active_record"
13
+ gem "sqlite3"
14
+
15
+ gem "rake"
16
+ gem "rspec"
17
+ gem "rspec-benchmark"
18
+ gem "simplecov"
19
+ gem "simplecov-cobertura"
20
+
21
+ gem "rubocop"
22
+ gem "rubocop-performance"
23
+ gem "rubocop-rake"
24
+ gem "rubocop-rspec"
25
+
26
+ # Fix OpenSSL 3.x CRL verification issues
27
+ gem "openssl", "3.3.2"
28
+ end