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
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+
5
+ module LightServices
6
+ module Generators
7
+ class ServiceGenerator < ::Rails::Generators::Base
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :name, type: :string, required: true,
11
+ desc: "The name of the service (e.g., user/create or CreateUser)"
12
+
13
+ class_option :args, type: :array, default: [],
14
+ desc: "List of arguments for the service"
15
+ class_option :steps, type: :array, default: [],
16
+ desc: "List of steps for the service"
17
+ class_option :outputs, type: :array, default: [],
18
+ desc: "List of outputs for the service"
19
+ class_option :skip_spec, type: :boolean, default: false,
20
+ desc: "Skip creating the spec file"
21
+ class_option :parent, type: :string, default: "ApplicationService",
22
+ desc: "Parent class for the service"
23
+
24
+ desc "Creates a new service class"
25
+
26
+ def create_service_file
27
+ template "service.rb.tt", "app/services/#{file_path}.rb"
28
+ end
29
+
30
+ def create_spec_file
31
+ return if options[:skip_spec]
32
+ return unless rspec_installed?
33
+
34
+ template "service_spec.rb.tt", "spec/services/#{file_path}_spec.rb"
35
+ end
36
+
37
+ private
38
+
39
+ def file_path
40
+ name.underscore
41
+ end
42
+
43
+ def class_name
44
+ name.camelize
45
+ end
46
+
47
+ def parent_class
48
+ options[:parent]
49
+ end
50
+
51
+ def arguments
52
+ options[:args]
53
+ end
54
+
55
+ def steps
56
+ options[:steps]
57
+ end
58
+
59
+ def outputs
60
+ options[:outputs]
61
+ end
62
+
63
+ def rspec_installed?
64
+ File.directory?(File.join(destination_root, "spec"))
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= class_name %> < <%= parent_class %>
4
+ <% if arguments.any? -%>
5
+ # Arguments
6
+ <% arguments.each do |arg| -%>
7
+ arg :<%= arg %>
8
+ <% end -%>
9
+
10
+ <% end -%>
11
+ <% if steps.any? -%>
12
+ # Steps
13
+ <% steps.each do |step| -%>
14
+ step :<%= step %>
15
+ <% end -%>
16
+
17
+ <% end -%>
18
+ <% if outputs.any? -%>
19
+ # Outputs
20
+ <% outputs.each do |output| -%>
21
+ output :<%= output %>
22
+ <% end -%>
23
+
24
+ <% end -%>
25
+ <% if steps.empty? -%>
26
+ # step :step_a
27
+ # step :step_b
28
+
29
+ <% end -%>
30
+ private
31
+
32
+ <% if steps.any? -%>
33
+ <% steps.each_with_index do |step, index| -%>
34
+ def <%= step %>
35
+ # TODO: Implement <%= step %>
36
+ end
37
+ <%= "\n" unless index == steps.length - 1 -%>
38
+ <% end -%>
39
+ <% else -%>
40
+ # def step_a
41
+ # # TODO: Implement service logic
42
+ # end
43
+
44
+ # def step_b
45
+ # # TODO: Implement service logic
46
+ # end
47
+ <% end -%>
48
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+
5
+ # TODO: Add test implementation
6
+ RSpec.describe <%= class_name %>, type: :service do
7
+ <% if arguments.any? -%>
8
+ describe "arguments" do
9
+ <% arguments.each do |arg| -%>
10
+ it { is_expected.to define_argument(:<%= arg %>) }
11
+ <% end -%>
12
+ end
13
+
14
+ <% end -%>
15
+ <% if steps.any? -%>
16
+ describe "steps" do
17
+ <% steps.each do |step| -%>
18
+ it { is_expected.to define_step(:<%= step %>) }
19
+ <% end -%>
20
+ end
21
+
22
+ <% end -%>
23
+ <% if outputs.any? -%>
24
+ describe "outputs" do
25
+ <% outputs.each do |output| -%>
26
+ it { is_expected.to define_output(:<%= output %>) }
27
+ <% end -%>
28
+ end
29
+
30
+ <% end -%>
31
+ describe "#run" do
32
+ subject(:service) { described_class.run(args) }
33
+
34
+ let(:args) { {} }
35
+
36
+ it "succeeds" do
37
+ expect(service).to be_success
38
+ end
39
+ end
40
+ end
@@ -1,38 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "benchmark"
4
-
3
+ require "light/services/constants"
5
4
  require "light/services/message"
6
5
  require "light/services/messages"
7
6
  require "light/services/base_with_context"
8
7
 
9
8
  require "light/services/settings/step"
10
- require "light/services/settings/output"
11
- require "light/services/settings/argument"
9
+ require "light/services/settings/field"
10
+
11
+ require "light/services/collection"
12
12
 
13
- require "light/services/collection/base"
14
- require "light/services/collection/outputs"
15
- require "light/services/collection/arguments"
13
+ require "light/services/dsl/arguments_dsl"
14
+ require "light/services/dsl/outputs_dsl"
15
+ require "light/services/dsl/steps_dsl"
16
16
 
17
- require "light/services/class_based_collection/base"
18
- require "light/services/class_based_collection/mount"
17
+ require "light/services/concerns/execution"
18
+ require "light/services/concerns/state_management"
19
+ require "light/services/concerns/parent_service"
19
20
 
20
21
  # Base class for all service objects
21
22
  module Light
22
23
  module Services
23
24
  class Base
24
- # Includes
25
- extend ClassBasedCollection::Mount
26
-
27
- # Settings
28
- mount_class_based_collection :steps, item_class: Settings::Step, shortcut: :step
29
- mount_class_based_collection :outputs, item_class: Settings::Output, shortcut: :output
30
- mount_class_based_collection :arguments, item_class: Settings::Argument, shortcut: :arg, allow_redefine: true
31
-
32
- # Arguments
33
- arg :verbose, default: false
34
- arg :benchmark, default: false
35
- arg :deepness, default: 0, context: true
25
+ include Callbacks
26
+ include Dsl::ArgumentsDsl
27
+ include Dsl::OutputsDsl
28
+ include Dsl::StepsDsl
29
+ include Concerns::Execution
30
+ include Concerns::StateManagement
31
+ include Concerns::ParentService
36
32
 
37
33
  # Getters
38
34
  attr_reader :outputs, :arguments, :errors, :warnings
@@ -41,8 +37,8 @@ module Light
41
37
  @config = Light::Services.config.merge(self.class.class_config || {}).merge(config)
42
38
  @parent_service = parent_service
43
39
 
44
- @outputs = Collection::Outputs.new(self)
45
- @arguments = Collection::Arguments.new(self, args)
40
+ @outputs = Collection::Base.new(self, CollectionTypes::OUTPUTS)
41
+ @arguments = Collection::Base.new(self, CollectionTypes::ARGUMENTS, args.dup)
46
42
 
47
43
  @done = false
48
44
  @launched_steps = []
@@ -77,20 +73,14 @@ module Light
77
73
 
78
74
  def call
79
75
  load_defaults_and_validate
80
- log_header if benchmark? || verbose?
81
76
 
82
- time = Benchmark.ms do
83
- run_steps
84
- run_steps_with_always
77
+ run_callbacks(:before_service_run, self)
85
78
 
86
- copy_warnings_to_parent_service
87
- copy_errors_to_parent_service
79
+ run_callbacks(:around_service_run, self) do
80
+ execute_service
88
81
  end
89
82
 
90
- return unless benchmark
91
-
92
- log "🟢 Finished #{self.class} in #{time}ms"
93
- puts
83
+ run_service_result_callbacks
94
84
  rescue StandardError => e
95
85
  run_steps_with_always
96
86
  raise e
@@ -115,87 +105,7 @@ module Light
115
105
  service = service_or_config.is_a?(Hash) ? nil : service_or_config
116
106
  config = service_or_config unless service
117
107
 
118
- BaseWithContext.new(self, service, config)
119
- end
120
- end
121
-
122
- # TODO: Add possibility to specify logger
123
- def log(message)
124
- puts "#{' ' * deepness}→ #{message}"
125
- end
126
-
127
- private
128
-
129
- def initialize_errors
130
- @errors = Messages.new(
131
- break_on_add: @config[:break_on_error],
132
- raise_on_add: @config[:raise_on_error],
133
- rollback_on_add: @config[:use_transactions] && @config[:rollback_on_error]
134
- )
135
- end
136
-
137
- def initialize_warnings
138
- @warnings = Messages.new(
139
- break_on_add: @config[:break_on_warning],
140
- raise_on_add: @config[:raise_on_warning],
141
- rollback_on_add: @config[:use_transactions] && @config[:rollback_on_warning]
142
- )
143
- end
144
-
145
- def run_steps
146
- within_transaction do
147
- self.class.steps.each do |name, step|
148
- @launched_steps << name if step.run(self, benchmark: benchmark)
149
-
150
- break if @errors.break? || @warnings.break?
151
- end
152
- end
153
- end
154
-
155
- # Run steps with parameter `always` if they weren't launched because of errors/warnings
156
- def run_steps_with_always
157
- self.class.steps.each do |name, step|
158
- next if !step.always || @launched_steps.include?(name)
159
-
160
- @launched_steps << name if step.run(self)
161
- end
162
- end
163
-
164
- def copy_warnings_to_parent_service
165
- return if !@parent_service || !@config[:load_warnings]
166
-
167
- @parent_service.warnings.copy_from(
168
- @warnings,
169
- break: @config[:self_break_on_warning],
170
- rollback: @config[:self_rollback_on_warning]
171
- )
172
- end
173
-
174
- def copy_errors_to_parent_service
175
- return if !@parent_service || !@config[:load_errors]
176
-
177
- @parent_service.errors.copy_from(
178
- @errors,
179
- break: @config[:self_break_on_error],
180
- rollback: @config[:self_rollback_on_error]
181
- )
182
- end
183
-
184
- def load_defaults_and_validate
185
- @outputs.load_defaults
186
- @arguments.load_defaults
187
- @arguments.validate!
188
- end
189
-
190
- def log_header
191
- log "🏎 Run service #{self.class}"
192
- end
193
-
194
- def within_transaction(&block)
195
- if @config[:use_transactions] && defined?(ActiveRecord::Base)
196
- ActiveRecord::Base.transaction(requires_new: true, &block)
197
- else
198
- yield
108
+ BaseWithContext.new(self, service, config.dup)
199
109
  end
200
110
  end
201
111
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # This class allows to run service object with context (parent class and custom config)
3
+ # This class allows running a service object with context (parent class and custom config)
4
4
  module Light
5
5
  module Services
6
6
  class BaseWithContext
@@ -26,8 +26,7 @@ module Light
26
26
  private
27
27
 
28
28
  def extend_arguments(args)
29
- # TODO: Do we need `.dup` here?
30
- args = @parent_service.arguments.extend_with_context(args) if @parent_service
29
+ args = @parent_service.arguments.dup.extend_with_context(args) if @parent_service
31
30
  args[:deepness] += 1 if args[:deepness]
32
31
 
33
32
  args
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Light
4
+ module Services
5
+ module Callbacks
6
+ EVENTS = [
7
+ :before_step_run,
8
+ :after_step_run,
9
+ :around_step_run,
10
+ :on_step_success,
11
+ :on_step_failure,
12
+ :on_step_crash,
13
+ :before_service_run,
14
+ :after_service_run,
15
+ :around_service_run,
16
+ :on_service_success,
17
+ :on_service_failure,
18
+ ].freeze
19
+
20
+ def self.included(base)
21
+ base.extend(ClassMethods)
22
+ end
23
+
24
+ module ClassMethods
25
+ # Define DSL methods for each callback event
26
+ EVENTS.each do |event|
27
+ define_method(event) do |method_name = nil, &block|
28
+ callback = method_name || block
29
+ raise ArgumentError, "#{event} requires a method name (symbol) or a block" unless callback
30
+
31
+ unless callback.is_a?(Symbol) || callback.is_a?(Proc)
32
+ raise ArgumentError,
33
+ "#{event} callback must be a Symbol or Proc"
34
+ end
35
+
36
+ callbacks_for(event) << callback
37
+ end
38
+ end
39
+
40
+ # Get all callbacks for a specific event (including inherited ones)
41
+ def callbacks_for(event)
42
+ @callbacks ||= {}
43
+ @callbacks[event] ||= []
44
+ end
45
+
46
+ # Get all callbacks including inherited ones
47
+ def all_callbacks_for(event)
48
+ if superclass.respond_to?(:all_callbacks_for)
49
+ inherited = superclass.all_callbacks_for(event)
50
+ else
51
+ inherited = []
52
+ end
53
+
54
+ inherited + callbacks_for(event)
55
+ end
56
+ end
57
+
58
+ # Run callbacks for a given event
59
+ # For around callbacks, yields to the block
60
+ # For other callbacks, just executes them in order
61
+ def run_callbacks(event, *args, &block)
62
+ callbacks = self.class.all_callbacks_for(event)
63
+
64
+ if event.to_s.start_with?("around_")
65
+ run_around_callbacks(callbacks, args, &block)
66
+ else
67
+ run_simple_callbacks(callbacks, args)
68
+ yield if block_given?
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def run_simple_callbacks(callbacks, args)
75
+ callbacks.each do |callback|
76
+ execute_callback(callback, args)
77
+ end
78
+ end
79
+
80
+ def run_around_callbacks(callbacks, args, &block)
81
+ return yield if callbacks.empty?
82
+
83
+ # Build a chain of around callbacks
84
+ chain = callbacks.reverse.reduce(block) do |next_block, callback|
85
+ proc { execute_callback(callback, args, &next_block) }
86
+ end
87
+
88
+ chain.call
89
+ end
90
+
91
+ def execute_callback(callback, args, &block)
92
+ case callback
93
+ in Symbol
94
+ block_given? ? send(callback, *args, &block) : send(callback, *args)
95
+ in Proc
96
+ block_given? ? instance_exec(*args, block, &callback) : instance_exec(*args, &callback)
97
+ else
98
+ raise ArgumentError, "Callback must be a Symbol or Proc, got #{callback.class}"
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constants"
4
+
5
+ # Collection to store arguments and outputs values
6
+ module Light
7
+ module Services
8
+ module Collection
9
+ class Base
10
+ extend Forwardable
11
+
12
+ def_delegators :@storage, :key?, :to_h
13
+
14
+ def initialize(instance, collection_type, storage = {})
15
+ validate_collection_type!(collection_type)
16
+
17
+ @instance = instance
18
+ @collection_type = collection_type
19
+ @storage = storage
20
+
21
+ return if storage.is_a?(Hash)
22
+
23
+ raise Light::Services::ArgTypeError, "#{instance.class} - #{collection_type} must be a Hash"
24
+ end
25
+
26
+ def set(key, value)
27
+ @storage[key] = value
28
+ end
29
+
30
+ def get(key)
31
+ @storage[key]
32
+ end
33
+
34
+ def [](key)
35
+ get(key)
36
+ end
37
+
38
+ def []=(key, value)
39
+ set(key, value)
40
+ end
41
+
42
+ def load_defaults
43
+ settings_collection.each do |name, settings|
44
+ next if !settings.default_exists || key?(name)
45
+
46
+ if settings.default.is_a?(Proc)
47
+ set(name, @instance.instance_exec(&settings.default))
48
+ else
49
+ set(name, Utils.deep_dup(settings.default))
50
+ end
51
+ end
52
+ end
53
+
54
+ def validate!
55
+ settings_collection.each do |name, field|
56
+ next if field.optional && (!key?(name) || get(name).nil?)
57
+
58
+ # validate_type! returns the (possibly coerced) value
59
+ coerced_value = field.validate_type!(get(name))
60
+ # Store the coerced value back (supports dry-types coercion)
61
+ set(name, coerced_value) if coerced_value != get(name)
62
+ end
63
+ end
64
+
65
+ # Extend args with context values (only for arguments)
66
+ def extend_with_context(args)
67
+ return args unless @collection_type == CollectionTypes::ARGUMENTS
68
+
69
+ settings_collection.each do |name, field|
70
+ next if !field.context || args.key?(name) || !key?(name)
71
+
72
+ args[field.name] = get(name)
73
+ end
74
+
75
+ args
76
+ end
77
+
78
+ private
79
+
80
+ def validate_collection_type!(type)
81
+ return if CollectionTypes::ALL.include?(type)
82
+
83
+ raise ArgumentError,
84
+ "collection_type must be one of #{CollectionTypes::ALL.join(', ')}, got: #{type.inspect}"
85
+ end
86
+
87
+ def settings_collection
88
+ @instance.class.public_send(@collection_type)
89
+ end
90
+ end
91
+
92
+ # Aliases for backwards compatibility
93
+ Arguments = Base
94
+ Outputs = Base
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Light
4
+ module Services
5
+ module Concerns
6
+ # Handles service execution logic including steps and validation
7
+ module Execution
8
+ private
9
+
10
+ # Execute the main service logic
11
+ def execute_service
12
+ self.class.validate_steps!
13
+ run_steps
14
+ run_steps_with_always
15
+ @outputs.validate! if success?
16
+
17
+ copy_warnings_to_parent_service
18
+ copy_errors_to_parent_service
19
+ end
20
+
21
+ # Run all service result callbacks based on success/failure
22
+ def run_service_result_callbacks
23
+ run_callbacks(:after_service_run, self)
24
+
25
+ if success?
26
+ run_callbacks(:on_service_success, self)
27
+ else
28
+ run_callbacks(:on_service_failure, self)
29
+ end
30
+ end
31
+
32
+ # Run normal steps within transaction
33
+ def run_steps
34
+ within_transaction do
35
+ # Cache steps once for both normal and always execution
36
+ @cached_steps = self.class.steps
37
+
38
+ @cached_steps.each do |name, step|
39
+ @launched_steps << name if step.run(self)
40
+
41
+ break if @errors.break? || @warnings.break?
42
+ end
43
+ end
44
+ end
45
+
46
+ # Run steps with parameter `always` if they weren't launched because of errors/warnings
47
+ def run_steps_with_always
48
+ # Use cached steps from run_steps, or get them if run_steps wasn't called
49
+ steps_to_check = @cached_steps || self.class.steps
50
+
51
+ steps_to_check.each do |name, step|
52
+ next if !step.always || @launched_steps.include?(name)
53
+
54
+ @launched_steps << name if step.run(self)
55
+ end
56
+ end
57
+
58
+ # Load defaults for outputs and arguments, then validate arguments
59
+ def load_defaults_and_validate
60
+ @outputs.load_defaults
61
+ @arguments.load_defaults
62
+ @arguments.validate!
63
+ end
64
+
65
+ # Execute block within transaction if configured
66
+ def within_transaction(&block)
67
+ if @config[:use_transactions] && defined?(ActiveRecord::Base)
68
+ ActiveRecord::Base.transaction(requires_new: true, &block)
69
+ else
70
+ yield
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Light
4
+ module Services
5
+ module Concerns
6
+ # Handles copying errors and warnings to parent services
7
+ module ParentService
8
+ private
9
+
10
+ # Copy warnings from this service to parent service
11
+ def copy_warnings_to_parent_service
12
+ return if !@parent_service || !@config[:load_warnings]
13
+
14
+ @parent_service.warnings.copy_from(
15
+ @warnings,
16
+ break: @config[:self_break_on_warning],
17
+ rollback: @config[:self_rollback_on_warning],
18
+ )
19
+ end
20
+
21
+ # Copy errors from this service to parent service
22
+ def copy_errors_to_parent_service
23
+ return if !@parent_service || !@config[:load_errors]
24
+
25
+ @parent_service.errors.copy_from(
26
+ @errors,
27
+ break: @config[:self_break_on_error],
28
+ rollback: @config[:self_rollback_on_error],
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Light
4
+ module Services
5
+ module Concerns
6
+ # Manages service state including errors and warnings initialization
7
+ module StateManagement
8
+ private
9
+
10
+ # Initialize errors collection with configuration
11
+ def initialize_errors
12
+ @errors = Messages.new(
13
+ break_on_add: @config[:break_on_error],
14
+ raise_on_add: @config[:raise_on_error],
15
+ rollback_on_add: @config[:use_transactions] && @config[:rollback_on_error],
16
+ )
17
+ end
18
+
19
+ # Initialize warnings collection with configuration
20
+ def initialize_warnings
21
+ @warnings = Messages.new(
22
+ break_on_add: @config[:break_on_warning],
23
+ raise_on_add: @config[:raise_on_warning],
24
+ rollback_on_add: @config[:use_transactions] && @config[:rollback_on_warning],
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end