light-services 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14b80318915db3dc20bd40365dc9ad00160938bb6836c1cda14bfba5301c7d7a
4
- data.tar.gz: 80515890786ed055972e5b52bbc6cdee8e79b85d4fa1227b217f4b746c9fb899
3
+ metadata.gz: a7b40ae2a9930c6ee5831c6d5f64f84aeab383b4f35295a41faf8ae9e0735c82
4
+ data.tar.gz: 8bb7c192556ec28c904bd931abbf502c2533b39137c2d7d989469c11c61cd70d
5
5
  SHA512:
6
- metadata.gz: 454b6f2cd8fdf8615bc29c6b69e16fe704a3b19abd8f7e48fbd1e848505038db013e41396399dd5caa721d7179ae1bc3a6a99989a3adfc0f6868e9fe49a63c85
7
- data.tar.gz: 50dbf071f72a242c0c36daeb83db94db5826dca4e3e2bbe7a47f9c81006b4d2c2266731715e1948b6528df86633735839f6eee525abde302a76b7a046e484b72
6
+ metadata.gz: 671d913a895af97d0c37e45d21e930794317d60fac03bd46df3e43ef3dc6190617b5b5522f445f57143d96434104df636f5d29cf9c6c35d60604f61f54781dfa
7
+ data.tar.gz: eacef0ee6679653963d6b09a6c660c9ee503e8edc3df04411547f36e07bea91210d2683ff02ed1ee6d96924062c6e1620f95b87959e40ac80e78a211c7048f96
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.1.1 (2025-12-13)
4
+
5
+ ### Added
6
+
7
+ - Better IDE support for callbacks DSL
8
+
3
9
  ## 3.1.0 (2025-12-13)
4
10
 
5
11
  ### Breaking changes
@@ -1,8 +1,13 @@
1
- # Table of contents
1
+ # Summary​
2
+
3
+ ## Introduction
2
4
 
3
5
  * [Light Services](README.md)
4
6
  * [Quickstart](quickstart.md)
5
7
  * [Concepts](concepts.md)
8
+
9
+ ## Deep Dive
10
+
6
11
  * [Arguments](arguments.md)
7
12
  * [Steps](steps.md)
8
13
  * [Outputs](outputs.md)
@@ -14,6 +19,9 @@
14
19
  * [Rails Generators](generators.md)
15
20
  * [RuboCop Integration](rubocop.md)
16
21
  * [Ruby LSP Integration](ruby-lsp.md)
22
+
23
+ ## Examples
24
+
17
25
  * [Best Practices](best-practices.md)
18
26
  * [Recipes](recipes.md)
19
27
  * [CRUD](crud.md)
data/docs/concepts.md CHANGED
@@ -7,39 +7,39 @@ This section covers the core concepts of Light Services: **Arguments**, **Steps*
7
7
  When you call `MyService.run(args)`, the following happens:
8
8
 
9
9
  ```
10
- ┌─────────────────────────────────────────────────────────────┐
10
+ ┌──────────────────────────────────────────────────────────────┐
11
11
  │ Service.run(args) │
12
- ├─────────────────────────────────────────────────────────────┤
12
+ ├──────────────────────────────────────────────────────────────┤
13
13
  │ 1. Load default values for arguments and outputs │
14
14
  │ 2. Validate argument types │
15
15
  │ 3. Run before_service_run callbacks │
16
- ├─────────────────────────────────────────────────────────────┤
16
+ ├──────────────────────────────────────────────────────────────┤
17
17
  │ 4. Begin around_service_run callback │
18
18
  │ 5. Begin database transaction (if use_transactions: true) │
19
- │ ┌─────────────────────────────────────────────────────┐
20
- │ │ 6. Execute steps in order │
21
- │ │ - Run before_step_run / around_step_run │
22
- │ │ - Execute step method │
23
- │ │ - Run after_step_run / on_step_success │
24
- │ │ - Skip if condition (if:/unless:) not met │
25
- │ │ - Stop if errors.break? is true │
26
- │ │ - Stop if stop! was called │
27
- │ ├─────────────────────────────────────────────────────┤
28
- │ │ 7. On error → Rollback transaction │
29
- │ │ On success → Commit transaction │
30
- │ └─────────────────────────────────────────────────────┘
19
+ │ ┌─────────────────────────────────────────────────────┐
20
+ │ │ 6. Execute steps in order │
21
+ │ │ - Run before_step_run / around_step_run │
22
+ │ │ - Execute step method │
23
+ │ │ - Run after_step_run / on_step_success │
24
+ │ │ - Skip if condition (if:/unless:) not met │
25
+ │ │ - Stop if errors.break? is true │
26
+ │ │ - Stop if stop! was called │
27
+ │ ├─────────────────────────────────────────────────────┤
28
+ │ │ 7. On error → Rollback transaction │
29
+ │ │ On success → Commit transaction │
30
+ │ └─────────────────────────────────────────────────────┘
31
31
  │ 8. End around_service_run callback │
32
- ├─────────────────────────────────────────────────────────────┤
32
+ ├──────────────────────────────────────────────────────────────┤
33
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 │
37
37
  │ 13. Run on_service_success or on_service_failure callback │
38
- ├─────────────────────────────────────────────────────────────┤
38
+ ├──────────────────────────────────────────────────────────────┤
39
39
  │ 14. Return service instance │
40
40
  │ - service.success? / service.failed? │
41
41
  │ - service.outputs / service.errors │
42
- └─────────────────────────────────────────────────────────────┘
42
+ └──────────────────────────────────────────────────────────────┘
43
43
  ```
44
44
 
45
45
  ## Arguments
@@ -43,6 +43,7 @@ module Light
43
43
  # result.success? # => true
44
44
  # result.user # => #<User id: 1, name: "John">
45
45
  class Base
46
+ extend CallbackDsl
46
47
  include Callbacks
47
48
  include Dsl::ArgumentsDsl
48
49
  include Dsl::OutputsDsl
@@ -48,64 +48,6 @@ module Light
48
48
  :on_service_failure,
49
49
  ].freeze
50
50
 
51
- def self.included(base)
52
- base.extend(ClassMethods)
53
- end
54
-
55
- # Class methods for registering callbacks.
56
- #
57
- # Each callback event has a corresponding class method:
58
- # - {before_step_run} - before each step executes
59
- # - {after_step_run} - after each step executes
60
- # - {around_step_run} - wraps step execution (must yield)
61
- # - {on_step_success} - when a step completes without adding errors
62
- # - {on_step_failure} - when a step adds errors
63
- # - {on_step_crash} - when a step raises an exception
64
- # - {before_service_run} - before the service starts
65
- # - {after_service_run} - after the service completes
66
- # - {around_service_run} - wraps service execution (must yield)
67
- # - {on_service_success} - when service completes without errors
68
- # - {on_service_failure} - when service completes with errors
69
- module ClassMethods
70
- # Define DSL methods for each callback event
71
- EVENTS.each do |event|
72
- define_method(event) do |method_name = nil, &block|
73
- callback = method_name || block
74
- raise ArgumentError, "#{event} requires a method name (symbol) or a block" unless callback
75
-
76
- unless callback.is_a?(Symbol) || callback.is_a?(Proc)
77
- raise ArgumentError,
78
- "#{event} callback must be a Symbol or Proc"
79
- end
80
-
81
- callbacks_for(event) << callback
82
- end
83
- end
84
-
85
- # Get callbacks defined in this class for a specific event.
86
- #
87
- # @param event [Symbol] the callback event name
88
- # @return [Array<Symbol, Proc>] callbacks for this event
89
- def callbacks_for(event)
90
- @callbacks ||= {}
91
- @callbacks[event] ||= []
92
- end
93
-
94
- # Get all callbacks for an event including inherited ones.
95
- #
96
- # @param event [Symbol] the callback event name
97
- # @return [Array<Symbol, Proc>] all callbacks for this event
98
- def all_callbacks_for(event)
99
- if superclass.respond_to?(:all_callbacks_for)
100
- inherited = superclass.all_callbacks_for(event)
101
- else
102
- inherited = []
103
- end
104
-
105
- inherited + callbacks_for(event)
106
- end
107
- end
108
-
109
51
  # Run all callbacks for a given event.
110
52
  #
111
53
  # @param event [Symbol] the callback event name
@@ -153,5 +95,261 @@ module Light
153
95
  end
154
96
  end
155
97
  end
98
+
99
+ # Class methods for registering callbacks.
100
+ # Extend this module in your service class to get callback DSL methods.
101
+ #
102
+ # @example
103
+ # class MyService < Light::Services::Base
104
+ # before_service_run :setup
105
+ # after_service_run :cleanup
106
+ # end
107
+ module CallbackDsl
108
+ # Registers a callback to run before each step executes.
109
+ #
110
+ # @param method_name [Symbol, nil] name of the instance method to call
111
+ # @yield [service, step_name] block to execute if no method name provided
112
+ # @yieldparam service [Light::Services::Base] the service instance
113
+ # @yieldparam step_name [Symbol] the name of the step about to run
114
+ # @return [void]
115
+ # @raise [ArgumentError] if neither method name nor block is provided
116
+ #
117
+ # @example With method name
118
+ # before_step_run :log_step_start
119
+ #
120
+ # @example With block
121
+ # before_step_run { |service, step_name| puts "Starting #{step_name}" }
122
+ def before_step_run(method_name = nil, &block)
123
+ register_callback(:before_step_run, method_name, &block)
124
+ end
125
+
126
+ # Registers a callback to run after each step executes.
127
+ #
128
+ # @param method_name [Symbol, nil] name of the instance method to call
129
+ # @yield [service, step_name] block to execute if no method name provided
130
+ # @yieldparam service [Light::Services::Base] the service instance
131
+ # @yieldparam step_name [Symbol] the name of the step that just ran
132
+ # @return [void]
133
+ # @raise [ArgumentError] if neither method name nor block is provided
134
+ #
135
+ # @example With method name
136
+ # after_step_run :log_step_complete
137
+ #
138
+ # @example With block
139
+ # after_step_run { |service, step_name| puts "Finished #{step_name}" }
140
+ def after_step_run(method_name = nil, &block)
141
+ register_callback(:after_step_run, method_name, &block)
142
+ end
143
+
144
+ # Registers an around callback that wraps each step execution.
145
+ # The callback must yield to execute the step.
146
+ #
147
+ # @param method_name [Symbol, nil] name of the instance method to call
148
+ # @yield [service, step_name] block to execute if no method name provided
149
+ # @yieldparam service [Light::Services::Base] the service instance
150
+ # @yieldparam step_name [Symbol] the name of the step being wrapped
151
+ # @return [void]
152
+ # @raise [ArgumentError] if neither method name nor block is provided
153
+ #
154
+ # @example With method name
155
+ # around_step_run :with_step_timing
156
+ #
157
+ # def with_step_timing(service, step_name)
158
+ # start = Time.now
159
+ # yield
160
+ # puts "#{step_name} took #{Time.now - start}s"
161
+ # end
162
+ def around_step_run(method_name = nil, &block)
163
+ register_callback(:around_step_run, method_name, &block)
164
+ end
165
+
166
+ # Registers a callback to run when a step completes successfully (without adding errors).
167
+ #
168
+ # @param method_name [Symbol, nil] name of the instance method to call
169
+ # @yield [service, step_name] block to execute if no method name provided
170
+ # @yieldparam service [Light::Services::Base] the service instance
171
+ # @yieldparam step_name [Symbol] the name of the successful step
172
+ # @return [void]
173
+ # @raise [ArgumentError] if neither method name nor block is provided
174
+ #
175
+ # @example With method name
176
+ # on_step_success :track_step_success
177
+ #
178
+ # @example With block
179
+ # on_step_success { |service, step_name| Analytics.track("step.success", step: step_name) }
180
+ def on_step_success(method_name = nil, &block)
181
+ register_callback(:on_step_success, method_name, &block)
182
+ end
183
+
184
+ # Registers a callback to run when a step fails (adds errors).
185
+ #
186
+ # @param method_name [Symbol, nil] name of the instance method to call
187
+ # @yield [service, step_name] block to execute if no method name provided
188
+ # @yieldparam service [Light::Services::Base] the service instance
189
+ # @yieldparam step_name [Symbol] the name of the failed step
190
+ # @return [void]
191
+ # @raise [ArgumentError] if neither method name nor block is provided
192
+ #
193
+ # @example With method name
194
+ # on_step_failure :handle_step_error
195
+ #
196
+ # @example With block
197
+ # on_step_failure { |service, step_name| Rails.logger.error("Step #{step_name} failed") }
198
+ def on_step_failure(method_name = nil, &block)
199
+ register_callback(:on_step_failure, method_name, &block)
200
+ end
201
+
202
+ # Registers a callback to run when a step raises an exception.
203
+ #
204
+ # @param method_name [Symbol, nil] name of the instance method to call
205
+ # @yield [service, step_name, exception] block to execute if no method name provided
206
+ # @yieldparam service [Light::Services::Base] the service instance
207
+ # @yieldparam step_name [Symbol] the name of the crashed step
208
+ # @yieldparam exception [Exception] the exception that was raised
209
+ # @return [void]
210
+ # @raise [ArgumentError] if neither method name nor block is provided
211
+ #
212
+ # @example With method name
213
+ # on_step_crash :report_crash
214
+ #
215
+ # @example With block
216
+ # on_step_crash { |service, step_name, error| Sentry.capture_exception(error) }
217
+ def on_step_crash(method_name = nil, &block)
218
+ register_callback(:on_step_crash, method_name, &block)
219
+ end
220
+
221
+ # Registers a callback to run before the service starts executing.
222
+ #
223
+ # @param method_name [Symbol, nil] name of the instance method to call
224
+ # @yield [service] block to execute if no method name provided
225
+ # @yieldparam service [Light::Services::Base] the service instance
226
+ # @return [void]
227
+ # @raise [ArgumentError] if neither method name nor block is provided
228
+ #
229
+ # @example With method name
230
+ # before_service_run :log_start
231
+ #
232
+ # @example With block
233
+ # before_service_run { |service| Rails.logger.info("Starting #{service.class.name}") }
234
+ def before_service_run(method_name = nil, &block)
235
+ register_callback(:before_service_run, method_name, &block)
236
+ end
237
+
238
+ # Registers a callback to run after the service completes (regardless of success/failure).
239
+ #
240
+ # @param method_name [Symbol, nil] name of the instance method to call
241
+ # @yield [service] block to execute if no method name provided
242
+ # @yieldparam service [Light::Services::Base] the service instance
243
+ # @return [void]
244
+ # @raise [ArgumentError] if neither method name nor block is provided
245
+ #
246
+ # @example With method name
247
+ # after_service_run :cleanup
248
+ #
249
+ # @example With block
250
+ # after_service_run { |service| Rails.logger.info("Done!") }
251
+ def after_service_run(method_name = nil, &block)
252
+ register_callback(:after_service_run, method_name, &block)
253
+ end
254
+
255
+ # Registers an around callback that wraps the entire service execution.
256
+ # The callback must yield to execute the service.
257
+ #
258
+ # @param method_name [Symbol, nil] name of the instance method to call
259
+ # @yield [service] block to execute if no method name provided
260
+ # @yieldparam service [Light::Services::Base] the service instance
261
+ # @return [void]
262
+ # @raise [ArgumentError] if neither method name nor block is provided
263
+ #
264
+ # @example With method name
265
+ # around_service_run :with_timing
266
+ #
267
+ # def with_timing(service)
268
+ # start = Time.now
269
+ # yield
270
+ # puts "Took #{Time.now - start}s"
271
+ # end
272
+ def around_service_run(method_name = nil, &block)
273
+ register_callback(:around_service_run, method_name, &block)
274
+ end
275
+
276
+ # Registers a callback to run when the service completes successfully (without errors).
277
+ #
278
+ # @param method_name [Symbol, nil] name of the instance method to call
279
+ # @yield [service] block to execute if no method name provided
280
+ # @yieldparam service [Light::Services::Base] the service instance
281
+ # @return [void]
282
+ # @raise [ArgumentError] if neither method name nor block is provided
283
+ #
284
+ # @example With method name
285
+ # on_service_success :send_notification
286
+ #
287
+ # @example With block
288
+ # on_service_success { |service| NotificationMailer.success(service.user).deliver_later }
289
+ def on_service_success(method_name = nil, &block)
290
+ register_callback(:on_service_success, method_name, &block)
291
+ end
292
+
293
+ # Registers a callback to run when the service completes with errors.
294
+ #
295
+ # @param method_name [Symbol, nil] name of the instance method to call
296
+ # @yield [service] block to execute if no method name provided
297
+ # @yieldparam service [Light::Services::Base] the service instance
298
+ # @return [void]
299
+ # @raise [ArgumentError] if neither method name nor block is provided
300
+ #
301
+ # @example With method name
302
+ # on_service_failure :log_error
303
+ #
304
+ # @example With block
305
+ # on_service_failure { |service| Rails.logger.error(service.errors.full_messages) }
306
+ def on_service_failure(method_name = nil, &block)
307
+ register_callback(:on_service_failure, method_name, &block)
308
+ end
309
+
310
+ # Get callbacks defined in this class for a specific event.
311
+ #
312
+ # @param event [Symbol] the callback event name
313
+ # @return [Array<Symbol, Proc>] callbacks for this event
314
+ def callbacks_for(event)
315
+ @callbacks ||= {}
316
+ @callbacks[event] ||= []
317
+ end
318
+
319
+ # Get all callbacks for an event including inherited ones.
320
+ #
321
+ # @param event [Symbol] the callback event name
322
+ # @return [Array<Symbol, Proc>] all callbacks for this event
323
+ def all_callbacks_for(event)
324
+ if superclass.respond_to?(:all_callbacks_for)
325
+ inherited = superclass.all_callbacks_for(event)
326
+ else
327
+ inherited = []
328
+ end
329
+
330
+ inherited + callbacks_for(event)
331
+ end
332
+
333
+ private
334
+
335
+ # Registers a callback for a given event.
336
+ #
337
+ # @param event [Symbol] the callback event name
338
+ # @param method_name [Symbol, nil] name of the instance method to call
339
+ # @yield block to execute if no method name provided
340
+ # @return [void]
341
+ # @raise [ArgumentError] if neither method name nor block is provided
342
+ # @api private
343
+ def register_callback(event, method_name = nil, &block)
344
+ callback = method_name || block
345
+ raise ArgumentError, "#{event} requires a method name (symbol) or a block" unless callback
346
+
347
+ unless callback.is_a?(Symbol) || callback.is_a?(Proc)
348
+ raise ArgumentError, "#{event} callback must be a Symbol or Proc"
349
+ end
350
+
351
+ callbacks_for(event) << callback
352
+ end
353
+ end
156
354
  end
157
355
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Light
4
4
  module Services
5
- VERSION = "3.1.0"
5
+ VERSION = "3.1.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: light-services
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kodkod
@@ -34,6 +34,8 @@ files:
34
34
  - Rakefile
35
35
  - bin/console
36
36
  - bin/setup
37
+ - docs/README.md
38
+ - docs/SUMMARY.md
37
39
  - docs/arguments.md
38
40
  - docs/best-practices.md
39
41
  - docs/callbacks.md
@@ -46,13 +48,11 @@ files:
46
48
  - docs/outputs.md
47
49
  - docs/pundit-authorization.md
48
50
  - docs/quickstart.md
49
- - docs/readme.md
50
51
  - docs/recipes.md
51
52
  - docs/rubocop.md
52
53
  - docs/ruby-lsp.md
53
54
  - docs/service-rendering.md
54
55
  - docs/steps.md
55
- - docs/summary.md
56
56
  - docs/testing.md
57
57
  - lib/generators/light_services/install/USAGE
58
58
  - lib/generators/light_services/install/install_generator.rb
File without changes