light-services 3.0.0 → 3.1.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/.rubocop.yml +7 -1
- data/CHANGELOG.md +15 -0
- data/CLAUDE.md +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +11 -11
- data/docs/arguments.md +23 -0
- data/docs/concepts.md +2 -2
- data/docs/configuration.md +36 -0
- data/docs/errors.md +31 -1
- data/docs/outputs.md +23 -0
- data/docs/quickstart.md +1 -1
- data/docs/readme.md +12 -11
- data/docs/rubocop.md +285 -0
- data/docs/ruby-lsp.md +133 -0
- data/docs/steps.md +62 -8
- data/docs/summary.md +2 -0
- data/docs/testing.md +1 -1
- data/lib/light/services/base.rb +109 -7
- data/lib/light/services/base_with_context.rb +23 -1
- data/lib/light/services/callbacks.rb +59 -5
- data/lib/light/services/collection.rb +50 -2
- data/lib/light/services/concerns/execution.rb +3 -0
- data/lib/light/services/config.rb +83 -3
- data/lib/light/services/constants.rb +3 -0
- data/lib/light/services/dsl/arguments_dsl.rb +1 -0
- data/lib/light/services/dsl/outputs_dsl.rb +1 -0
- data/lib/light/services/dsl/validation.rb +30 -0
- data/lib/light/services/exceptions.rb +19 -1
- data/lib/light/services/message.rb +28 -3
- data/lib/light/services/messages.rb +74 -2
- data/lib/light/services/rubocop/cop/light_services/argument_type_required.rb +52 -0
- data/lib/light/services/rubocop/cop/light_services/condition_method_exists.rb +173 -0
- data/lib/light/services/rubocop/cop/light_services/deprecated_methods.rb +113 -0
- data/lib/light/services/rubocop/cop/light_services/dsl_order.rb +176 -0
- data/lib/light/services/rubocop/cop/light_services/missing_private_keyword.rb +102 -0
- data/lib/light/services/rubocop/cop/light_services/no_direct_instantiation.rb +66 -0
- data/lib/light/services/rubocop/cop/light_services/output_type_required.rb +52 -0
- data/lib/light/services/rubocop/cop/light_services/step_method_exists.rb +109 -0
- data/lib/light/services/rubocop.rb +12 -0
- data/lib/light/services/settings/field.rb +33 -5
- data/lib/light/services/settings/step.rb +23 -5
- data/lib/light/services/version.rb +1 -1
- data/lib/ruby_lsp/light_services/addon.rb +36 -0
- data/lib/ruby_lsp/light_services/definition.rb +132 -0
- data/lib/ruby_lsp/light_services/indexing_enhancement.rb +263 -0
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 14b80318915db3dc20bd40365dc9ad00160938bb6836c1cda14bfba5301c7d7a
|
|
4
|
+
data.tar.gz: 80515890786ed055972e5b52bbc6cdee8e79b85d4fa1227b217f4b746c9fb899
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 454b6f2cd8fdf8615bc29c6b69e16fe704a3b19abd8f7e48fbd1e848505038db013e41396399dd5caa721d7179ae1bc3a6a99989a3adfc0f6868e9fe49a63c85
|
|
7
|
+
data.tar.gz: 50dbf071f72a242c0c36daeb83db94db5826dca4e3e2bbe7a47f9c81006b4d2c2266731715e1948b6528df86633735839f6eee525abde302a76b7a046e484b72
|
data/.rubocop.yml
CHANGED
|
@@ -58,6 +58,9 @@ RSpec/MultipleDescribes:
|
|
|
58
58
|
RSpec/MultipleExpectations:
|
|
59
59
|
Max: 10
|
|
60
60
|
|
|
61
|
+
RSpec/MultipleMemoizedHelpers:
|
|
62
|
+
Max: 10
|
|
63
|
+
|
|
61
64
|
RSpec/NestedGroups:
|
|
62
65
|
Max: 5
|
|
63
66
|
|
|
@@ -101,4 +104,7 @@ Style/TrailingCommaInHashLiteral:
|
|
|
101
104
|
EnforcedStyleForMultiline: consistent_comma
|
|
102
105
|
|
|
103
106
|
Style/WordArray:
|
|
104
|
-
EnforcedStyle: brackets
|
|
107
|
+
EnforcedStyle: brackets
|
|
108
|
+
|
|
109
|
+
Style/NumericPredicate:
|
|
110
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.1.0 (2025-12-13)
|
|
4
|
+
|
|
5
|
+
### Breaking changes
|
|
6
|
+
|
|
7
|
+
- Enforce arguments and output types by default. Add `config.require_type = false` to your config to disable this behavior.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `stop!` and `stopped?` methods for early exit (renamed from `done!` and `done?`)
|
|
12
|
+
- `stop_immediately!` method for immediate execution halt within the current step
|
|
13
|
+
- `done!` and `done?` are deprecated, but remain available as aliases for backward compatibility
|
|
14
|
+
- Ruby LSP support with step navigation and indexing
|
|
15
|
+
- Rubocop cops `StepMethodExists`, `ConditionMethodExists`, `DslOrder`, `MissingPrivateKeyword`, `NoDirectInstantiation`, `ArgumentTypeRequired`, `OutputTypeRequired`, `DeprecatedMethods`
|
|
16
|
+
- Comprehensive YARD documentation
|
|
17
|
+
|
|
3
18
|
## 3.0.0 (2025-12-12)
|
|
4
19
|
|
|
5
20
|
### Breaking changes
|
data/CLAUDE.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -36,15 +36,15 @@ rails generate light_services:install
|
|
|
36
36
|
```ruby
|
|
37
37
|
class GreetService < Light::Services::Base
|
|
38
38
|
# Arguments
|
|
39
|
-
arg :name
|
|
40
|
-
arg :age
|
|
39
|
+
arg :name, type: String
|
|
40
|
+
arg :age, type: Integer
|
|
41
41
|
|
|
42
42
|
# Steps
|
|
43
43
|
step :build_message
|
|
44
44
|
step :send_message
|
|
45
45
|
|
|
46
46
|
# Outputs
|
|
47
|
-
output :message
|
|
47
|
+
output :message, type: String
|
|
48
48
|
|
|
49
49
|
private
|
|
50
50
|
|
|
@@ -58,14 +58,14 @@ class GreetService < Light::Services::Base
|
|
|
58
58
|
end
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
## Advanced Example
|
|
61
|
+
## Advanced Example (with dry-types and conditions)
|
|
62
62
|
|
|
63
63
|
```ruby
|
|
64
64
|
class User::ResetPassword < Light::Services::Base
|
|
65
|
-
# Arguments
|
|
66
|
-
arg :user, type: User, optional: true
|
|
67
|
-
arg :email, type: String, optional: true
|
|
68
|
-
arg :send_email, type:
|
|
65
|
+
# Arguments with dry-types for advanced validation and coercion
|
|
66
|
+
arg :user, type: Types.Instance(User), optional: true
|
|
67
|
+
arg :email, type: Types::Coercible::String, optional: true
|
|
68
|
+
arg :send_email, type: Types::Params::Bool, default: true
|
|
69
69
|
|
|
70
70
|
# Steps
|
|
71
71
|
step :validate
|
|
@@ -74,9 +74,9 @@ class User::ResetPassword < Light::Services::Base
|
|
|
74
74
|
step :save_reset_token
|
|
75
75
|
step :send_reset_email, if: :send_email?
|
|
76
76
|
|
|
77
|
-
# Outputs
|
|
78
|
-
output :user, type: User
|
|
79
|
-
output :reset_token, type: String
|
|
77
|
+
# Outputs with dry-types
|
|
78
|
+
output :user, type: Types.Instance(User)
|
|
79
|
+
output :reset_token, type: Types::Strict::String
|
|
80
80
|
|
|
81
81
|
private
|
|
82
82
|
|
data/docs/arguments.md
CHANGED
|
@@ -55,6 +55,29 @@ class HappyBirthdayService < ApplicationService
|
|
|
55
55
|
end
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
### Type Enforcement (Enabled by Default)
|
|
59
|
+
|
|
60
|
+
By default, all arguments must have a `type` option. This helps catch type-related bugs early and makes your services self-documenting.
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
class MyService < ApplicationService
|
|
64
|
+
arg :name, type: String # ✓ Valid
|
|
65
|
+
arg :age # ✗ Raises MissingTypeError
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
To disable type enforcement for a specific service:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
class LegacyService < ApplicationService
|
|
73
|
+
config require_type: false
|
|
74
|
+
|
|
75
|
+
arg :name # Allowed when require_type is disabled
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
See the [Configuration documentation](configuration.md) for more details.
|
|
80
|
+
|
|
58
81
|
### dry-types Support
|
|
59
82
|
|
|
60
83
|
Light Services supports [dry-types](https://dry-rb.org/gems/dry-types) for advanced type validation and coercion. When using dry-types, values are automatically coerced to the expected type.
|
data/docs/concepts.md
CHANGED
|
@@ -23,14 +23,14 @@ When you call `MyService.run(args)`, the following happens:
|
|
|
23
23
|
│ │ - Run after_step_run / on_step_success │ │
|
|
24
24
|
│ │ - Skip if condition (if:/unless:) not met │ │
|
|
25
25
|
│ │ - Stop if errors.break? is true │ │
|
|
26
|
-
│ │ - Stop if
|
|
26
|
+
│ │ - Stop if stop! was called │ │
|
|
27
27
|
│ ├─────────────────────────────────────────────────────┤ │
|
|
28
28
|
│ │ 7. On error → Rollback transaction │ │
|
|
29
29
|
│ │ On success → Commit transaction │ │
|
|
30
30
|
│ └─────────────────────────────────────────────────────┘ │
|
|
31
31
|
│ 8. End around_service_run callback │
|
|
32
32
|
├─────────────────────────────────────────────────────────────┤
|
|
33
|
-
│ 9. Run steps marked with always: true (unless
|
|
33
|
+
│ 9. Run steps marked with always: true (unless stop! called) │
|
|
34
34
|
│ 10. Validate output types (if success) │
|
|
35
35
|
│ 11. Copy errors/warnings to parent service (if in context) │
|
|
36
36
|
│ 12. Run after_service_run callback │
|
data/docs/configuration.md
CHANGED
|
@@ -8,6 +8,9 @@ Configure Light Services globally using an initializer. For Rails applications,
|
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
10
|
Light::Services.configure do |config|
|
|
11
|
+
# Type enforcement
|
|
12
|
+
config.require_type = true # Require type option for all arguments and outputs
|
|
13
|
+
|
|
11
14
|
# Transaction settings
|
|
12
15
|
config.use_transactions = true # Wrap each service in a database transaction
|
|
13
16
|
|
|
@@ -29,6 +32,7 @@ end
|
|
|
29
32
|
|
|
30
33
|
| Option | Default | Description |
|
|
31
34
|
|--------|---------|-------------|
|
|
35
|
+
| `require_type` | `true` | Raises `Light::Services::MissingTypeError` when defining arguments or outputs without a `type` option |
|
|
32
36
|
| `use_transactions` | `true` | Wraps service execution in `ActiveRecord::Base.transaction` |
|
|
33
37
|
| `load_errors` | `true` | Propagates errors to parent service when using `.with(self)` |
|
|
34
38
|
| `break_on_error` | `true` | Stops executing remaining steps when an error is added |
|
|
@@ -138,6 +142,38 @@ class BackgroundTaskService < ApplicationService
|
|
|
138
142
|
end
|
|
139
143
|
```
|
|
140
144
|
|
|
145
|
+
### Type Enforcement (Enabled by Default)
|
|
146
|
+
|
|
147
|
+
By default, all arguments and outputs must have a `type` option. This helps catch type-related bugs early and makes your services self-documenting.
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
class User::Create < ApplicationService
|
|
151
|
+
arg :name, type: String # ✓ Valid
|
|
152
|
+
arg :email # ✗ Raises MissingTypeError
|
|
153
|
+
output :user, type: User # ✓ Valid
|
|
154
|
+
output :token # ✗ Raises MissingTypeError
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
To disable type enforcement globally (not recommended):
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
Light::Services.configure do |config|
|
|
162
|
+
config.require_type = false
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Or disable for specific services:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
class LegacyService < ApplicationService
|
|
170
|
+
config require_type: false
|
|
171
|
+
|
|
172
|
+
arg :data # Allowed when require_type is disabled
|
|
173
|
+
output :result # Allowed when require_type is disabled
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
141
177
|
## Disabling Transactions
|
|
142
178
|
|
|
143
179
|
If you're not using ActiveRecord or want to manage transactions yourself:
|
data/docs/errors.md
CHANGED
|
@@ -201,10 +201,40 @@ Light Services defines several exception classes for different error scenarios:
|
|
|
201
201
|
| Exception | Description |
|
|
202
202
|
|-----------|-------------|
|
|
203
203
|
| `Light::Services::Error` | Base exception class for all Light Services errors |
|
|
204
|
-
| `Light::Services::ArgTypeError` | Raised when an argument type validation fails |
|
|
204
|
+
| `Light::Services::ArgTypeError` | Raised when an argument or output type validation fails |
|
|
205
205
|
| `Light::Services::ReservedNameError` | Raised when using a reserved name for arguments, outputs, or steps |
|
|
206
206
|
| `Light::Services::InvalidNameError` | Raised when using an invalid name format |
|
|
207
207
|
| `Light::Services::NoStepsError` | Raised when a service has no steps defined and no `run` method |
|
|
208
|
+
| `Light::Services::MissingTypeError` | Raised when defining an argument or output without a `type` option when `require_type` is enabled |
|
|
209
|
+
|
|
210
|
+
### MissingTypeError
|
|
211
|
+
|
|
212
|
+
This exception is raised when you define an argument or output without a `type` option. Since `require_type` is enabled by default, all arguments and outputs must have a type.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
class MyService < ApplicationService
|
|
216
|
+
arg :name # => raises Light::Services::MissingTypeError
|
|
217
|
+
end
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
To fix this, add a `type` option to all arguments and outputs:
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
class MyService < ApplicationService
|
|
224
|
+
arg :name, type: String
|
|
225
|
+
output :result, type: Hash
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
If you need to disable type enforcement for legacy services, you can use the `config` method:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
class LegacyService < ApplicationService
|
|
233
|
+
config require_type: false
|
|
234
|
+
|
|
235
|
+
arg :data # Allowed when require_type is disabled
|
|
236
|
+
end
|
|
237
|
+
```
|
|
208
238
|
|
|
209
239
|
### NoStepsError
|
|
210
240
|
|
data/docs/outputs.md
CHANGED
|
@@ -78,6 +78,29 @@ class AI::Chat < ApplicationService
|
|
|
78
78
|
end
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
### Type Enforcement (Enabled by Default)
|
|
82
|
+
|
|
83
|
+
By default, all outputs must have a `type` option. This helps catch type-related bugs early and makes your services self-documenting.
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
class MyService < ApplicationService
|
|
87
|
+
output :result, type: Hash # ✓ Valid
|
|
88
|
+
output :data # ✗ Raises MissingTypeError
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
To disable type enforcement for a specific service:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
class LegacyService < ApplicationService
|
|
96
|
+
config require_type: false
|
|
97
|
+
|
|
98
|
+
output :data # Allowed when require_type is disabled
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
See the [Configuration documentation](configuration.md) for more details.
|
|
103
|
+
|
|
81
104
|
### dry-types Support
|
|
82
105
|
|
|
83
106
|
Outputs also support [dry-types](https://dry-rb.org/gems/dry-types) for advanced type validation and coercion.
|
data/docs/quickstart.md
CHANGED
data/docs/readme.md
CHANGED
|
@@ -13,6 +13,7 @@ Light Services is a simple yet powerful way to organize business logic in Ruby a
|
|
|
13
13
|
- ⚠️ **Error Handling**: Collect errors from steps and handle them your way
|
|
14
14
|
- 🔗 **Context**: Run multiple services sequentially within the same context
|
|
15
15
|
- 🧪 **RSpec Matchers**: Built-in RSpec matchers for expressive service tests
|
|
16
|
+
- 🔍 **RuboCop Integration**: Custom cops to enforce best practices at lint time
|
|
16
17
|
- 🌐 **Framework Agnostic**: Compatible with Rails, Hanami, or any Ruby framework
|
|
17
18
|
- 🧩 **Modularity**: Isolate and test your services with ease
|
|
18
19
|
- ✅ **100% Test Coverage**: Thoroughly tested and reliable
|
|
@@ -23,15 +24,15 @@ Light Services is a simple yet powerful way to organize business logic in Ruby a
|
|
|
23
24
|
```ruby
|
|
24
25
|
class GreetService < Light::Services::Base
|
|
25
26
|
# Arguments
|
|
26
|
-
arg :name
|
|
27
|
-
arg :age
|
|
27
|
+
arg :name, type: String
|
|
28
|
+
arg :age, type: Integer
|
|
28
29
|
|
|
29
30
|
# Steps
|
|
30
31
|
step :build_message
|
|
31
32
|
step :send_message
|
|
32
33
|
|
|
33
34
|
# Outputs
|
|
34
|
-
output :message
|
|
35
|
+
output :message, type: String
|
|
35
36
|
|
|
36
37
|
private
|
|
37
38
|
|
|
@@ -45,14 +46,14 @@ class GreetService < Light::Services::Base
|
|
|
45
46
|
end
|
|
46
47
|
```
|
|
47
48
|
|
|
48
|
-
## Advanced Example
|
|
49
|
+
## Advanced Example (with dry-types and conditions)
|
|
49
50
|
|
|
50
51
|
```ruby
|
|
51
52
|
class User::ResetPassword < Light::Services::Base
|
|
52
|
-
# Arguments
|
|
53
|
-
arg :user, type: User, optional: true
|
|
54
|
-
arg :email, type: String, optional: true
|
|
55
|
-
arg :send_email, type:
|
|
53
|
+
# Arguments with dry-types for advanced validation and coercion
|
|
54
|
+
arg :user, type: Types.Instance(User), optional: true
|
|
55
|
+
arg :email, type: Types::Coercible::String, optional: true
|
|
56
|
+
arg :send_email, type: Types::Params::Bool, default: true
|
|
56
57
|
|
|
57
58
|
# Steps
|
|
58
59
|
step :validate
|
|
@@ -61,9 +62,9 @@ class User::ResetPassword < Light::Services::Base
|
|
|
61
62
|
step :save_reset_token
|
|
62
63
|
step :send_reset_email, if: :send_email?
|
|
63
64
|
|
|
64
|
-
# Outputs
|
|
65
|
-
output :user, type: User
|
|
66
|
-
output :reset_token, type: String
|
|
65
|
+
# Outputs with dry-types
|
|
66
|
+
output :user, type: Types.Instance(User)
|
|
67
|
+
output :reset_token, type: Types::Strict::String
|
|
67
68
|
|
|
68
69
|
private
|
|
69
70
|
|
data/docs/rubocop.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# RuboCop Integration
|
|
2
|
+
|
|
3
|
+
Light Services provides custom RuboCop cops to help enforce best practices in your service definitions.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Add this to your `.rubocop.yml`:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
require:
|
|
11
|
+
- light/services/rubocop
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Available Cops
|
|
15
|
+
|
|
16
|
+
### LightServices/ArgumentTypeRequired
|
|
17
|
+
|
|
18
|
+
Ensures all `arg` declarations include a `type:` option.
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
# bad
|
|
22
|
+
arg :user_id
|
|
23
|
+
arg :params, default: {}
|
|
24
|
+
|
|
25
|
+
# good
|
|
26
|
+
arg :user_id, type: Integer
|
|
27
|
+
arg :params, type: Hash, default: {}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### LightServices/OutputTypeRequired
|
|
31
|
+
|
|
32
|
+
Ensures all `output` declarations include a `type:` option.
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
# bad
|
|
36
|
+
output :result
|
|
37
|
+
output :data, optional: true
|
|
38
|
+
|
|
39
|
+
# good
|
|
40
|
+
output :result, type: Hash
|
|
41
|
+
output :data, type: Hash, optional: true
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### LightServices/StepMethodExists
|
|
45
|
+
|
|
46
|
+
Ensures all `step` declarations have a corresponding method defined.
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# bad
|
|
50
|
+
class MyService < ApplicationService
|
|
51
|
+
step :validate
|
|
52
|
+
step :process # missing method
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def validate; end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# good
|
|
60
|
+
class MyService < ApplicationService
|
|
61
|
+
step :validate
|
|
62
|
+
step :process
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def validate; end
|
|
67
|
+
def process; end
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Configuration:** Use `ExcludedSteps` for inherited steps:
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
LightServices/StepMethodExists:
|
|
75
|
+
ExcludedSteps:
|
|
76
|
+
- initialize_entity
|
|
77
|
+
- assign_attributes
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### LightServices/ConditionMethodExists
|
|
81
|
+
|
|
82
|
+
Ensures symbol conditions (`:if`, `:unless`) have corresponding methods defined.
|
|
83
|
+
|
|
84
|
+
This cop automatically recognizes predicate methods generated by `arg` and `output` declarations (e.g., `arg :user` creates `user?`).
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
# bad
|
|
88
|
+
class MyService < ApplicationService
|
|
89
|
+
step :notify, if: :should_notify? # missing method
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def notify; end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# good - explicit method
|
|
97
|
+
class MyService < ApplicationService
|
|
98
|
+
step :notify, if: :should_notify?
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def notify; end
|
|
103
|
+
def should_notify?; true; end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# good - predicate from arg/output
|
|
107
|
+
class MyService < ApplicationService
|
|
108
|
+
arg :user, type: User, optional: true
|
|
109
|
+
|
|
110
|
+
step :greet, if: :user? # user? is auto-generated
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def greet; end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Configuration:** Use `ExcludedMethods` for inherited condition methods:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
LightServices/ConditionMethodExists:
|
|
122
|
+
ExcludedMethods:
|
|
123
|
+
- admin?
|
|
124
|
+
- guest?
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### LightServices/DslOrder
|
|
128
|
+
|
|
129
|
+
Enforces consistent ordering of DSL declarations: `config` → `arg` → `step` → `output`
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
# bad
|
|
133
|
+
class MyService < ApplicationService
|
|
134
|
+
step :process
|
|
135
|
+
arg :name, type: String
|
|
136
|
+
config raise_on_error: true
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# good
|
|
140
|
+
class MyService < ApplicationService
|
|
141
|
+
config raise_on_error: true
|
|
142
|
+
|
|
143
|
+
arg :name, type: String
|
|
144
|
+
|
|
145
|
+
step :process
|
|
146
|
+
|
|
147
|
+
output :result, type: Hash
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### LightServices/MissingPrivateKeyword
|
|
152
|
+
|
|
153
|
+
Ensures step methods are defined as private.
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
# bad
|
|
157
|
+
class MyService < ApplicationService
|
|
158
|
+
step :process
|
|
159
|
+
|
|
160
|
+
def process # should be private
|
|
161
|
+
# implementation
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# good
|
|
166
|
+
class MyService < ApplicationService
|
|
167
|
+
step :process
|
|
168
|
+
|
|
169
|
+
private
|
|
170
|
+
|
|
171
|
+
def process
|
|
172
|
+
# implementation
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### LightServices/NoDirectInstantiation
|
|
178
|
+
|
|
179
|
+
Prevents direct instantiation of service classes with `.new`.
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
# bad
|
|
183
|
+
UserService.new(name: "John")
|
|
184
|
+
|
|
185
|
+
# good
|
|
186
|
+
UserService.run(name: "John")
|
|
187
|
+
UserService.run!(name: "John")
|
|
188
|
+
UserService.call(name: "John")
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Configuration:** Customize the pattern for service class detection:
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
LightServices/NoDirectInstantiation:
|
|
195
|
+
ServicePattern: 'Service$' # default: matches classes ending with "Service"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### LightServices/DeprecatedMethods
|
|
199
|
+
|
|
200
|
+
Detects deprecated `done!` and `done?` method calls and suggests using `stop!` and `stopped?` instead. Includes autocorrection.
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
# bad
|
|
204
|
+
class MyService < ApplicationService
|
|
205
|
+
step :process
|
|
206
|
+
|
|
207
|
+
private
|
|
208
|
+
|
|
209
|
+
def process
|
|
210
|
+
done! if condition_met?
|
|
211
|
+
return if done?
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# good
|
|
216
|
+
class MyService < ApplicationService
|
|
217
|
+
step :process
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
def process
|
|
222
|
+
stop! if condition_met?
|
|
223
|
+
return if stopped?
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Configuration:** Customize the pattern for service class detection:
|
|
229
|
+
|
|
230
|
+
```yaml
|
|
231
|
+
LightServices/DeprecatedMethods:
|
|
232
|
+
ServicePattern: 'Service$' # default: matches classes ending with "Service"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Configuration
|
|
236
|
+
|
|
237
|
+
Full configuration example:
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
require:
|
|
241
|
+
- light/services/rubocop
|
|
242
|
+
|
|
243
|
+
LightServices/ArgumentTypeRequired:
|
|
244
|
+
Enabled: true
|
|
245
|
+
|
|
246
|
+
LightServices/OutputTypeRequired:
|
|
247
|
+
Enabled: true
|
|
248
|
+
|
|
249
|
+
LightServices/StepMethodExists:
|
|
250
|
+
Enabled: true
|
|
251
|
+
ExcludedSteps: []
|
|
252
|
+
|
|
253
|
+
LightServices/ConditionMethodExists:
|
|
254
|
+
Enabled: true
|
|
255
|
+
ExcludedMethods: []
|
|
256
|
+
|
|
257
|
+
LightServices/DslOrder:
|
|
258
|
+
Enabled: true
|
|
259
|
+
|
|
260
|
+
LightServices/MissingPrivateKeyword:
|
|
261
|
+
Enabled: true
|
|
262
|
+
|
|
263
|
+
LightServices/NoDirectInstantiation:
|
|
264
|
+
Enabled: true
|
|
265
|
+
ServicePattern: 'Service$'
|
|
266
|
+
|
|
267
|
+
LightServices/DeprecatedMethods:
|
|
268
|
+
Enabled: true
|
|
269
|
+
ServicePattern: 'Service$'
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
To disable a cop for specific files:
|
|
273
|
+
|
|
274
|
+
```yaml
|
|
275
|
+
LightServices/ArgumentTypeRequired:
|
|
276
|
+
Exclude:
|
|
277
|
+
- 'spec/**/*'
|
|
278
|
+
- 'test/**/*'
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## What's Next?
|
|
282
|
+
|
|
283
|
+
Learn more about testing your services:
|
|
284
|
+
|
|
285
|
+
[Next: Testing](testing.md)
|