light-services 2.2 → 3.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.github/config/rubocop_linter_action.yml +4 -4
  3. data/.github/workflows/ci.yml +12 -12
  4. data/.gitignore +5 -0
  5. data/.rubocop.yml +77 -7
  6. data/CHANGELOG.md +23 -0
  7. data/CLAUDE.md +139 -0
  8. data/Gemfile +16 -11
  9. data/Gemfile.lock +53 -27
  10. data/README.md +76 -13
  11. data/docs/arguments.md +267 -0
  12. data/docs/best-practices.md +153 -0
  13. data/docs/callbacks.md +476 -0
  14. data/docs/concepts.md +80 -0
  15. data/docs/configuration.md +168 -0
  16. data/docs/context.md +128 -0
  17. data/docs/crud.md +525 -0
  18. data/docs/errors.md +250 -0
  19. data/docs/generators.md +250 -0
  20. data/docs/outputs.md +135 -0
  21. data/docs/pundit-authorization.md +320 -0
  22. data/docs/quickstart.md +134 -0
  23. data/docs/readme.md +100 -0
  24. data/docs/recipes.md +14 -0
  25. data/docs/service-rendering.md +222 -0
  26. data/docs/steps.md +337 -0
  27. data/docs/summary.md +19 -0
  28. data/docs/testing.md +549 -0
  29. data/lib/generators/light_services/install/USAGE +15 -0
  30. data/lib/generators/light_services/install/install_generator.rb +41 -0
  31. data/lib/generators/light_services/install/templates/application_service.rb.tt +8 -0
  32. data/lib/generators/light_services/install/templates/application_service_spec.rb.tt +7 -0
  33. data/lib/generators/light_services/install/templates/initializer.rb.tt +30 -0
  34. data/lib/generators/light_services/service/USAGE +21 -0
  35. data/lib/generators/light_services/service/service_generator.rb +68 -0
  36. data/lib/generators/light_services/service/templates/service.rb.tt +48 -0
  37. data/lib/generators/light_services/service/templates/service_spec.rb.tt +40 -0
  38. data/lib/light/services/base.rb +24 -114
  39. data/lib/light/services/base_with_context.rb +2 -3
  40. data/lib/light/services/callbacks.rb +103 -0
  41. data/lib/light/services/collection.rb +97 -0
  42. data/lib/light/services/concerns/execution.rb +76 -0
  43. data/lib/light/services/concerns/parent_service.rb +34 -0
  44. data/lib/light/services/concerns/state_management.rb +30 -0
  45. data/lib/light/services/config.rb +4 -18
  46. data/lib/light/services/constants.rb +97 -0
  47. data/lib/light/services/dsl/arguments_dsl.rb +84 -0
  48. data/lib/light/services/dsl/outputs_dsl.rb +80 -0
  49. data/lib/light/services/dsl/steps_dsl.rb +205 -0
  50. data/lib/light/services/dsl/validation.rb +132 -0
  51. data/lib/light/services/exceptions.rb +7 -2
  52. data/lib/light/services/messages.rb +19 -31
  53. data/lib/light/services/rspec/matchers/define_argument.rb +174 -0
  54. data/lib/light/services/rspec/matchers/define_output.rb +147 -0
  55. data/lib/light/services/rspec/matchers/define_step.rb +225 -0
  56. data/lib/light/services/rspec/matchers/execute_step.rb +230 -0
  57. data/lib/light/services/rspec/matchers/have_error_on.rb +148 -0
  58. data/lib/light/services/rspec/matchers/have_warning_on.rb +148 -0
  59. data/lib/light/services/rspec/matchers/trigger_callback.rb +138 -0
  60. data/lib/light/services/rspec.rb +15 -0
  61. data/lib/light/services/settings/field.rb +86 -0
  62. data/lib/light/services/settings/step.rb +31 -16
  63. data/lib/light/services/utils.rb +38 -0
  64. data/lib/light/services/version.rb +1 -1
  65. data/lib/light/services.rb +2 -0
  66. data/light-services.gemspec +6 -8
  67. metadata +54 -26
  68. data/lib/light/services/class_based_collection/base.rb +0 -86
  69. data/lib/light/services/class_based_collection/mount.rb +0 -33
  70. data/lib/light/services/collection/arguments.rb +0 -34
  71. data/lib/light/services/collection/base.rb +0 -59
  72. data/lib/light/services/collection/outputs.rb +0 -16
  73. data/lib/light/services/settings/argument.rb +0 -68
  74. data/lib/light/services/settings/output.rb +0 -34
data/docs/testing.md ADDED
@@ -0,0 +1,549 @@
1
+ # Testing
2
+
3
+ Testing Light Services is straightforward. This guide covers strategies for unit testing your services effectively.
4
+
5
+ ## Basic Service Testing
6
+
7
+ ### Testing a Simple Service
8
+
9
+ ```ruby
10
+ # app/services/greet_service.rb
11
+ class GreetService < ApplicationService
12
+ arg :name, type: String
13
+
14
+ step :greet
15
+
16
+ output :message
17
+
18
+ private
19
+
20
+ def greet
21
+ self.message = "Hello, #{name}!"
22
+ end
23
+ end
24
+ ```
25
+
26
+ ```ruby
27
+ # spec/services/greet_service_spec.rb
28
+ RSpec.describe GreetService do
29
+ describe ".run" do
30
+ it "returns a greeting message" do
31
+ service = described_class.run(name: "John")
32
+
33
+ expect(service).to be_success
34
+ expect(service.message).to eq("Hello, John!")
35
+ end
36
+ end
37
+ end
38
+ ```
39
+
40
+ ## Testing Success and Failure
41
+
42
+ ```ruby
43
+ RSpec.describe User::Create do
44
+ describe ".run" do
45
+ context "with valid attributes" do
46
+ let(:attributes) { { email: "test@example.com", password: "password123" } }
47
+
48
+ it "creates a user" do
49
+ service = described_class.run(attributes: attributes)
50
+
51
+ expect(service).to be_success
52
+ expect(service.user).to be_persisted
53
+ expect(service.user.email).to eq("test@example.com")
54
+ end
55
+ end
56
+
57
+ context "with invalid attributes" do
58
+ let(:attributes) { { email: "", password: "" } }
59
+
60
+ it "returns errors" do
61
+ service = described_class.run(attributes: attributes)
62
+
63
+ expect(service).to be_failed
64
+ expect(service.errors[:email]).to be_present
65
+ end
66
+ end
67
+ end
68
+ end
69
+ ```
70
+
71
+ ## Testing with Context
72
+
73
+ When services use context to share data, test both scenarios:
74
+
75
+ ```ruby
76
+ RSpec.describe Comment::Create do
77
+ let(:current_user) { create(:user) }
78
+ let(:post) { create(:post) }
79
+
80
+ describe ".run" do
81
+ context "with current_user in context" do
82
+ it "creates a comment by the current user" do
83
+ service = described_class.run(
84
+ current_user: current_user,
85
+ post_id: post.id,
86
+ text: "Great post!"
87
+ )
88
+
89
+ expect(service).to be_success
90
+ expect(service.comment.user).to eq(current_user)
91
+ end
92
+ end
93
+
94
+ context "without current_user" do
95
+ it "fails with authorization error" do
96
+ service = described_class.run(
97
+ current_user: nil,
98
+ post_id: post.id,
99
+ text: "Great post!"
100
+ )
101
+
102
+ expect(service).to be_failed
103
+ expect(service.errors[:authorization]).to be_present
104
+ end
105
+ end
106
+ end
107
+ end
108
+ ```
109
+
110
+ ## Testing Child Services in Context
111
+
112
+ When testing services that call other services via `.with(self)`:
113
+
114
+ ```ruby
115
+ RSpec.describe Order::Create do
116
+ let(:current_user) { create(:user) }
117
+ let(:product) { create(:product, price: 100) }
118
+
119
+ it "creates order and order items in the same transaction" do
120
+ service = described_class.run(
121
+ current_user: current_user,
122
+ items: [{ product_id: product.id, quantity: 2 }]
123
+ )
124
+
125
+ expect(service).to be_success
126
+ expect(service.order.order_items.count).to eq(1)
127
+ expect(service.order.total).to eq(200)
128
+ end
129
+
130
+ it "rolls back everything if child service fails" do
131
+ # Stub the child service to fail
132
+ allow_any_instance_of(OrderItem::Create).to receive(:validate).and_wrap_original do |method, *args|
133
+ method.receiver.errors.add(:base, "Simulated failure")
134
+ end
135
+
136
+ expect {
137
+ described_class.run(
138
+ current_user: current_user,
139
+ items: [{ product_id: product.id, quantity: 2 }]
140
+ )
141
+ }.not_to change(Order, :count)
142
+ end
143
+ end
144
+ ```
145
+
146
+ ## Testing Conditional Steps
147
+
148
+ ```ruby
149
+ RSpec.describe User::Register do
150
+ describe "conditional steps" do
151
+ context "when send_welcome_email is true" do
152
+ it "sends a welcome email" do
153
+ expect {
154
+ described_class.run(
155
+ email: "test@example.com",
156
+ password: "password123",
157
+ send_welcome_email: true
158
+ )
159
+ }.to have_enqueued_mail(UserMailer, :welcome)
160
+ end
161
+ end
162
+
163
+ context "when send_welcome_email is false" do
164
+ it "does not send a welcome email" do
165
+ expect {
166
+ described_class.run(
167
+ email: "test@example.com",
168
+ password: "password123",
169
+ send_welcome_email: false
170
+ )
171
+ }.not_to have_enqueued_mail(UserMailer, :welcome)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ ```
177
+
178
+ ## Testing Early Exit with done!
179
+
180
+ ```ruby
181
+ RSpec.describe User::FindOrCreate do
182
+ describe "when user exists" do
183
+ let!(:existing_user) { create(:user, email: "existing@example.com") }
184
+
185
+ it "returns existing user without creating new one" do
186
+ expect {
187
+ service = described_class.run(email: "existing@example.com")
188
+ expect(service.user).to eq(existing_user)
189
+ }.not_to change(User, :count)
190
+ end
191
+ end
192
+
193
+ describe "when user does not exist" do
194
+ it "creates a new user" do
195
+ expect {
196
+ service = described_class.run(email: "new@example.com")
197
+ expect(service.user.email).to eq("new@example.com")
198
+ }.to change(User, :count).by(1)
199
+ end
200
+ end
201
+ end
202
+ ```
203
+
204
+ ## Testing Configuration Overrides
205
+
206
+ ```ruby
207
+ RSpec.describe MyService do
208
+ describe "with raise_on_error config" do
209
+ it "raises exception when configured" do
210
+ expect {
211
+ described_class.run({ invalid: true }, { raise_on_error: true })
212
+ }.to raise_error(Light::Services::Error)
213
+ end
214
+
215
+ it "collects errors by default" do
216
+ service = described_class.run(invalid: true)
217
+
218
+ expect(service).to be_failed
219
+ expect { service }.not_to raise_error
220
+ end
221
+ end
222
+ end
223
+ ```
224
+
225
+ ## Testing run! vs run
226
+
227
+ ```ruby
228
+ RSpec.describe Payment::Process do
229
+ context "using run" do
230
+ it "returns failed service on error" do
231
+ service = described_class.run(amount: -100)
232
+
233
+ expect(service).to be_failed
234
+ expect(service.errors[:amount]).to include("must be positive")
235
+ end
236
+ end
237
+
238
+ context "using run!" do
239
+ it "raises exception on error" do
240
+ expect {
241
+ described_class.run!(amount: -100)
242
+ }.to raise_error(Light::Services::Error, /Amount must be positive/)
243
+ end
244
+ end
245
+ end
246
+ ```
247
+
248
+ ## Testing Warnings
249
+
250
+ ```ruby
251
+ RSpec.describe DataImport do
252
+ it "completes with warnings for skipped records" do
253
+ service = described_class.run(data: mixed_valid_invalid_data)
254
+
255
+ expect(service).to be_success # Warnings don't fail the service
256
+ expect(service.warnings?).to be true
257
+ expect(service.warnings[:skipped]).to include("Row 3: invalid format")
258
+ end
259
+ end
260
+ ```
261
+
262
+ ## Mocking External Services
263
+
264
+ ```ruby
265
+ RSpec.describe Payment::Charge do
266
+ let(:stripe_client) { instance_double(Stripe::PaymentIntent) }
267
+
268
+ before do
269
+ allow(Stripe::PaymentIntent).to receive(:create).and_return(stripe_client)
270
+ allow(stripe_client).to receive(:id).and_return("pi_123")
271
+ end
272
+
273
+ it "processes payment successfully" do
274
+ service = described_class.run(amount: 1000, card_token: "tok_visa")
275
+
276
+ expect(service).to be_success
277
+ expect(service.payment_intent_id).to eq("pi_123")
278
+ end
279
+
280
+ context "when Stripe fails" do
281
+ before do
282
+ allow(Stripe::PaymentIntent).to receive(:create)
283
+ .and_raise(Stripe::CardError.new("Card declined", nil, nil))
284
+ end
285
+
286
+ it "handles the error gracefully" do
287
+ service = described_class.run(amount: 1000, card_token: "tok_declined")
288
+
289
+ expect(service).to be_failed
290
+ expect(service.errors[:payment]).to include("Card declined")
291
+ end
292
+ end
293
+ end
294
+ ```
295
+
296
+ ## Testing Argument Validation
297
+
298
+ ```ruby
299
+ RSpec.describe MyService do
300
+ describe "argument validation" do
301
+ it "requires name argument" do
302
+ expect {
303
+ described_class.run(name: nil)
304
+ }.to raise_error(Light::Services::ArgTypeError)
305
+ end
306
+
307
+ it "validates argument type" do
308
+ expect {
309
+ described_class.run(name: 123) # expects String
310
+ }.to raise_error(Light::Services::ArgTypeError, /must be a String/)
311
+ end
312
+
313
+ it "accepts optional arguments as nil" do
314
+ service = described_class.run(name: "John", nickname: nil)
315
+ expect(service).to be_success
316
+ end
317
+ end
318
+ end
319
+ ```
320
+
321
+ ## Shared Examples for CRUD Services
322
+
323
+ ```ruby
324
+ # spec/support/shared_examples/crud_service.rb
325
+ RSpec.shared_examples "a create service" do |model_class|
326
+ let(:valid_attributes) { attributes_for(model_class.name.underscore.to_sym) }
327
+ let(:current_user) { create(:user) }
328
+
329
+ it "creates a record" do
330
+ expect {
331
+ described_class.run(current_user: current_user, attributes: valid_attributes)
332
+ }.to change(model_class, :count).by(1)
333
+ end
334
+
335
+ it "returns the created record" do
336
+ service = described_class.run(current_user: current_user, attributes: valid_attributes)
337
+ expect(service.record).to be_a(model_class)
338
+ expect(service.record).to be_persisted
339
+ end
340
+ end
341
+
342
+ # Usage in specs
343
+ RSpec.describe Post::Create do
344
+ it_behaves_like "a create service", Post
345
+ end
346
+ ```
347
+
348
+ ## Test Helpers
349
+
350
+ Create a helper module for common service testing patterns:
351
+
352
+ ```ruby
353
+ # spec/support/service_helpers.rb
354
+ module ServiceHelpers
355
+ def expect_service_success(service)
356
+ expect(service).to be_success, -> { "Expected success but got errors: #{service.errors.to_h}" }
357
+ end
358
+
359
+ def expect_service_failure(service, key = nil)
360
+ expect(service).to be_failed
361
+ expect(service.errors[key]).to be_present if key
362
+ end
363
+ end
364
+
365
+ RSpec.configure do |config|
366
+ config.include ServiceHelpers, type: :service
367
+ end
368
+ ```
369
+
370
+ ## Custom RSpec Matchers
371
+
372
+ Light Services provides built-in RSpec matchers to make testing more expressive and readable.
373
+
374
+ ### Setup
375
+
376
+ Add this to your `spec/spec_helper.rb` or `spec/rails_helper.rb`:
377
+
378
+ ```ruby
379
+ require 'light/services/rspec'
380
+ ```
381
+
382
+ This automatically includes all matchers in your RSpec tests.
383
+
384
+ ### DSL Definition Matchers
385
+
386
+ #### `define_argument`
387
+
388
+ Test that a service defines specific arguments:
389
+
390
+ ```ruby
391
+ RSpec.describe User::Create do
392
+ it { expect(described_class).to define_argument(:email) }
393
+ it { expect(described_class).to define_argument(:email).with_type(String) }
394
+ it { expect(described_class).to define_argument(:role).optional }
395
+ it { expect(described_class).to define_argument(:status).with_default("pending") }
396
+ it { expect(described_class).to define_argument(:current_user).with_context }
397
+
398
+ # Chaining modifiers
399
+ it { expect(described_class).to define_argument(:age).with_type(Integer).with_default(18) }
400
+ end
401
+ ```
402
+
403
+ #### `define_output`
404
+
405
+ Test that a service defines specific outputs:
406
+
407
+ ```ruby
408
+ RSpec.describe User::Create do
409
+ it { expect(described_class).to define_output(:user) }
410
+ it { expect(described_class).to define_output(:user).with_type(User) }
411
+ it { expect(described_class).to define_output(:token).optional }
412
+ it { expect(described_class).to define_output(:count).with_default(0) }
413
+ end
414
+ ```
415
+
416
+ #### `define_step` and `define_steps`
417
+
418
+ Test that a service defines specific steps:
419
+
420
+ ```ruby
421
+ RSpec.describe User::Create do
422
+ # Single step
423
+ it { expect(described_class).to define_step(:validate) }
424
+ it { expect(described_class).to define_step(:cleanup).with_always(true) }
425
+ it { expect(described_class).to define_step(:notify).with_if(:should_notify?) }
426
+ it { expect(described_class).to define_step(:skip_log).with_unless(:logging_enabled?) }
427
+
428
+ # Multiple steps
429
+ it { expect(described_class).to define_steps(:validate, :process, :save) }
430
+
431
+ # Steps in order
432
+ it { expect(described_class).to define_steps_in_order(:validate, :process, :save) }
433
+ end
434
+ ```
435
+
436
+ ### Error and Warning Matchers
437
+
438
+ #### `have_error_on` and `have_errors_on`
439
+
440
+ Test service errors:
441
+
442
+ ```ruby
443
+ RSpec.describe User::Create do
444
+ context "with invalid email" do
445
+ let(:service) { described_class.run(email: "") }
446
+
447
+ it { expect(service).to have_error_on(:email) }
448
+ it { expect(service).to have_error_on(:email).with_message("can't be blank") }
449
+ it { expect(service).to have_error_on(:email).with_message(/blank/) } # regex
450
+
451
+ # Multiple errors (requires break_on_error: false)
452
+ it { expect(service).to have_errors_on(:email, :password) }
453
+ end
454
+ end
455
+ ```
456
+
457
+ #### `have_warning_on` and `have_warnings_on`
458
+
459
+ Test service warnings:
460
+
461
+ ```ruby
462
+ RSpec.describe Data::Import do
463
+ context "with deprecated format" do
464
+ let(:service) { described_class.run(format: "csv_v1") }
465
+
466
+ it { expect(service).to have_warning_on(:format) }
467
+ it { expect(service).to have_warning_on(:format).with_message("format is deprecated") }
468
+ end
469
+ end
470
+ ```
471
+
472
+ ### Execution Matchers
473
+
474
+ These matchers require step tracking. Add this to your service:
475
+
476
+ ```ruby
477
+ class ApplicationService < Light::Services::Base
478
+ output :executed_steps, default: -> { [] }
479
+
480
+ after_step_run do |service, step_name|
481
+ service.executed_steps << step_name
482
+ end
483
+ end
484
+ ```
485
+
486
+ #### `execute_step` and `skip_step`
487
+
488
+ ```ruby
489
+ RSpec.describe Order::Process do
490
+ context "with premium user" do
491
+ let(:service) { described_class.run(user: premium_user) }
492
+
493
+ it { expect(service).to execute_step(:apply_discount) }
494
+ it { expect(service).to skip_step(:show_ads) }
495
+ end
496
+ end
497
+ ```
498
+
499
+ #### `execute_steps` and `execute_steps_in_order`
500
+
501
+ ```ruby
502
+ RSpec.describe Order::Process do
503
+ let(:service) { described_class.run(order: order) }
504
+
505
+ it { expect(service).to execute_steps(:validate, :charge, :fulfill) }
506
+ it { expect(service).to execute_steps_in_order(:validate, :charge, :fulfill) }
507
+ end
508
+ ```
509
+
510
+ ### Callback Matchers
511
+
512
+ These matchers require callback tracking. Add this to your service:
513
+
514
+ ```ruby
515
+ class ApplicationService < Light::Services::Base
516
+ output :callback_log, default: -> { [] }
517
+
518
+ before_service_run { |service| service.callback_log << :before_service_run }
519
+ after_service_run { |service| service.callback_log << :after_service_run }
520
+ on_service_success { |service| service.callback_log << :on_service_success }
521
+ on_service_failure { |service| service.callback_log << :on_service_failure }
522
+
523
+ after_step_run { |service, step| service.callback_log << [:after_step_run, step] }
524
+ end
525
+ ```
526
+
527
+ #### `trigger_callback`
528
+
529
+ ```ruby
530
+ RSpec.describe Order::Process do
531
+ context "when successful" do
532
+ let(:service) { described_class.run(order: valid_order) }
533
+
534
+ it { expect(service).to trigger_callback(:before_service_run) }
535
+ it { expect(service).to trigger_callback(:on_service_success) }
536
+ it { expect(service).not_to trigger_callback(:on_service_failure) }
537
+
538
+ # Step-specific callbacks
539
+ it { expect(service).to trigger_callback(:after_step_run).for_step(:validate) }
540
+ end
541
+ end
542
+ ```
543
+
544
+ ## What's Next?
545
+
546
+ Learn best practices for organizing your services:
547
+
548
+ [Next: Best Practices](best-practices.md)
549
+
@@ -0,0 +1,15 @@
1
+ Description:
2
+ Installs Light::Services into your Rails application by creating
3
+ an ApplicationService base class and an optional initializer.
4
+
5
+ Example:
6
+ bin/rails generate light_services:install
7
+
8
+ This will create:
9
+ app/services/application_service.rb
10
+ config/initializers/light_services.rb
11
+ spec/services/application_service_spec.rb (if RSpec is detected)
12
+
13
+ Options:
14
+ --skip-initializer Skip creating the initializer file
15
+ --skip-spec Skip creating the spec file
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module LightServices
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ class_option :skip_initializer, type: :boolean, default: false,
11
+ desc: "Skip creating the initializer file"
12
+ class_option :skip_spec, type: :boolean, default: false,
13
+ desc: "Skip creating the spec file"
14
+
15
+ desc "Creates ApplicationService and initializer for Light::Services"
16
+
17
+ def create_application_service
18
+ template "application_service.rb.tt", "app/services/application_service.rb"
19
+ end
20
+
21
+ def create_initializer
22
+ return if options[:skip_initializer]
23
+
24
+ template "initializer.rb.tt", "config/initializers/light_services.rb"
25
+ end
26
+
27
+ def create_spec_file
28
+ return if options[:skip_spec]
29
+ return unless rspec_installed?
30
+
31
+ template "application_service_spec.rb.tt", "spec/services/application_service_spec.rb"
32
+ end
33
+
34
+ private
35
+
36
+ def rspec_installed?
37
+ File.directory?(File.join(destination_root, "spec"))
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationService < Light::Services::Base
4
+ # Add common arguments, callbacks, or helpers shared across all services.
5
+ #
6
+ # Example: Add a context argument for the current user
7
+ # arg :current_user, type: User, optional: true, context: true
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+
5
+ RSpec.describe ApplicationService, type: :service do
6
+ # Add specs for shared behavior defined in ApplicationService
7
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ Light::Services.configure do |config|
4
+ # Whether to wrap service execution in a database transaction.
5
+ # config.use_transactions = true
6
+
7
+ # Whether to copy errors to parent service in chain.
8
+ # config.load_errors = true
9
+
10
+ # Whether to stop executing steps when an error is added.
11
+ # config.break_on_error = true
12
+
13
+ # Whether to raise Light::Services::Error when service fails.
14
+ # config.raise_on_error = false
15
+
16
+ # Whether to rollback the transaction when an error is added.
17
+ # config.rollback_on_error = true
18
+
19
+ # Whether to load warnings from the context.
20
+ # config.load_warnings = true
21
+
22
+ # Whether to stop executing steps when a warning is added.
23
+ # config.break_on_warning = false
24
+
25
+ # Whether to raise Light::Services::Error when service has warnings.
26
+ # config.raise_on_warning = false
27
+
28
+ # Whether to rollback the transaction when a warning is added.
29
+ # config.rollback_on_warning = false
30
+ end
@@ -0,0 +1,21 @@
1
+ Description:
2
+ Creates a new service class that inherits from ApplicationService.
3
+ Supports namespaced services (e.g., user/create creates User::Create).
4
+
5
+ Example:
6
+ bin/rails generate light_services:service user/create
7
+
8
+ This will create:
9
+ app/services/user/create.rb
10
+ spec/services/user/create_spec.rb (if RSpec is detected)
11
+
12
+ bin/rails generate light_services:service CreateOrder --args=user product --steps=validate process --outputs=order
13
+
14
+ This will create a service with predefined arguments, steps, and outputs.
15
+
16
+ Options:
17
+ --args List of arguments for the service (e.g., --args=user product)
18
+ --steps List of steps for the service (e.g., --steps=validate process)
19
+ --outputs List of outputs for the service (e.g., --outputs=result)
20
+ --skip-spec Skip creating the spec file
21
+ --parent Parent class (default: ApplicationService)