servactory 2.16.1 → 3.0.0.rc2

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -9
  3. data/config/locales/de.yml +134 -0
  4. data/config/locales/en.yml +15 -0
  5. data/config/locales/es.yml +134 -0
  6. data/config/locales/fr.yml +134 -0
  7. data/config/locales/it.yml +134 -0
  8. data/config/locales/ru.yml +15 -0
  9. data/lib/generators/README.md +45 -0
  10. data/lib/generators/servactory/base.rb +82 -0
  11. data/lib/generators/servactory/extension/USAGE +54 -0
  12. data/lib/generators/servactory/extension/extension_generator.rb +41 -0
  13. data/lib/generators/servactory/extension/templates/extension.rb.tt +62 -0
  14. data/lib/generators/servactory/install/USAGE +27 -0
  15. data/lib/generators/servactory/install/install_generator.rb +94 -0
  16. data/lib/generators/servactory/{templates/services/application_service/base.rb → install/templates/application_service/base.rb.tt} +29 -19
  17. data/lib/generators/servactory/{templates/services/application_service/exceptions.rb → install/templates/application_service/exceptions.rb.tt} +1 -1
  18. data/lib/generators/servactory/{templates/services/application_service/result.rb → install/templates/application_service/result.rb.tt} +1 -1
  19. data/lib/generators/servactory/rspec/USAGE +46 -0
  20. data/lib/generators/servactory/rspec/rspec_generator.rb +95 -0
  21. data/lib/generators/servactory/rspec/templates/service_spec.rb.tt +58 -0
  22. data/lib/generators/servactory/service/USAGE +51 -0
  23. data/lib/generators/servactory/service/service_generator.rb +56 -0
  24. data/lib/generators/servactory/service/templates/service.rb.tt +22 -0
  25. data/lib/servactory/actions/collection.rb +56 -1
  26. data/lib/servactory/actions/dsl.rb +11 -11
  27. data/lib/servactory/actions/tools/rules.rb +1 -1
  28. data/lib/servactory/actions/tools/runner.rb +111 -28
  29. data/lib/servactory/base.rb +1 -7
  30. data/lib/servactory/configuration/actions/aliases/collection.rb +5 -0
  31. data/lib/servactory/configuration/actions/rescue_handlers/collection.rb +5 -0
  32. data/lib/servactory/configuration/actions/shortcuts/collection.rb +5 -0
  33. data/lib/servactory/configuration/collection_mode/class_names_collection.rb +5 -0
  34. data/lib/servactory/configuration/config.rb +36 -0
  35. data/lib/servactory/configuration/configurable.rb +95 -0
  36. data/lib/servactory/configuration/dsl.rb +3 -27
  37. data/lib/servactory/configuration/factory.rb +20 -20
  38. data/lib/servactory/configuration/hash_mode/class_names_collection.rb +5 -0
  39. data/lib/servactory/configuration/option_helpers/option_helpers_collection.rb +5 -0
  40. data/lib/servactory/context/warehouse/inputs.rb +2 -2
  41. data/lib/servactory/context/workspace/inputs.rb +2 -2
  42. data/lib/servactory/context/workspace/internals.rb +2 -2
  43. data/lib/servactory/context/workspace/outputs.rb +2 -2
  44. data/lib/servactory/context/workspace.rb +11 -7
  45. data/lib/servactory/dsl.rb +10 -8
  46. data/lib/servactory/maintenance/attributes/tools/validation.rb +1 -1
  47. data/lib/servactory/result.rb +2 -2
  48. data/lib/servactory/test_kit/fake_type.rb +23 -0
  49. data/lib/servactory/test_kit/result.rb +45 -0
  50. data/lib/servactory/test_kit/rspec/helpers/argument_matchers.rb +97 -0
  51. data/lib/servactory/test_kit/rspec/helpers/concerns/error_messages.rb +179 -0
  52. data/lib/servactory/test_kit/rspec/helpers/concerns/service_class_validation.rb +74 -0
  53. data/lib/servactory/test_kit/rspec/helpers/fluent.rb +110 -0
  54. data/lib/servactory/test_kit/rspec/helpers/input_validator.rb +149 -0
  55. data/lib/servactory/test_kit/rspec/helpers/legacy.rb +228 -0
  56. data/lib/servactory/test_kit/rspec/helpers/mock_executor.rb +256 -0
  57. data/lib/servactory/test_kit/rspec/helpers/output_validator.rb +121 -0
  58. data/lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb +422 -0
  59. data/lib/servactory/test_kit/rspec/helpers/service_mock_config.rb +129 -0
  60. data/lib/servactory/test_kit/rspec/helpers.rb +51 -84
  61. data/lib/servactory/test_kit/rspec/matchers/base/attribute_matcher.rb +324 -0
  62. data/lib/servactory/test_kit/rspec/matchers/base/submatcher.rb +133 -0
  63. data/lib/servactory/test_kit/rspec/matchers/base/submatcher_context.rb +101 -0
  64. data/lib/servactory/test_kit/rspec/matchers/base/submatcher_registry.rb +205 -0
  65. data/lib/servactory/test_kit/rspec/matchers/concerns/attribute_data_access.rb +100 -0
  66. data/lib/servactory/test_kit/rspec/matchers/concerns/error_message_builder.rb +106 -0
  67. data/lib/servactory/test_kit/rspec/matchers/concerns/value_comparison.rb +97 -0
  68. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +89 -219
  69. data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +74 -166
  70. data/lib/servactory/test_kit/rspec/matchers/have_service_output_matcher.rb +238 -0
  71. data/lib/servactory/test_kit/rspec/matchers/result/be_failure_service_matcher.rb +257 -0
  72. data/lib/servactory/test_kit/rspec/matchers/result/be_success_service_matcher.rb +185 -0
  73. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/default_submatcher.rb +81 -0
  74. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/optional_submatcher.rb +62 -0
  75. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/required_submatcher.rb +93 -0
  76. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/valid_with_submatcher.rb +271 -0
  77. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/consists_of_submatcher.rb +85 -0
  78. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/inclusion_submatcher.rb +120 -0
  79. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/message_submatcher.rb +115 -0
  80. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/must_submatcher.rb +82 -0
  81. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/schema_submatcher.rb +102 -0
  82. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/target_submatcher.rb +125 -0
  83. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/types_submatcher.rb +77 -0
  84. data/lib/servactory/test_kit/rspec/matchers.rb +126 -285
  85. data/lib/servactory/test_kit/utils/faker.rb +62 -2
  86. data/lib/servactory/tool_kit/dynamic_options/consists_of.rb +166 -0
  87. data/lib/servactory/tool_kit/dynamic_options/format.rb +195 -8
  88. data/lib/servactory/tool_kit/dynamic_options/inclusion.rb +219 -17
  89. data/lib/servactory/tool_kit/dynamic_options/max.rb +143 -0
  90. data/lib/servactory/tool_kit/dynamic_options/min.rb +143 -0
  91. data/lib/servactory/tool_kit/dynamic_options/multiple_of.rb +157 -8
  92. data/lib/servactory/tool_kit/dynamic_options/must.rb +194 -0
  93. data/lib/servactory/tool_kit/dynamic_options/schema.rb +226 -2
  94. data/lib/servactory/tool_kit/dynamic_options/target.rb +248 -0
  95. data/lib/servactory/version.rb +4 -4
  96. data/lib/servactory.rb +6 -0
  97. metadata +57 -48
  98. data/lib/generators/servactory/install_generator.rb +0 -21
  99. data/lib/generators/servactory/rspec_generator.rb +0 -88
  100. data/lib/generators/servactory/service_generator.rb +0 -49
  101. data/lib/servactory/configuration/setup.rb +0 -97
  102. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +0 -68
  103. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +0 -73
  104. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/message_matcher.rb +0 -91
  105. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +0 -72
  106. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/schema_matcher.rb +0 -92
  107. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/types_matcher.rb +0 -72
  108. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/default_matcher.rb +0 -69
  109. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/optional_matcher.rb +0 -63
  110. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/required_matcher.rb +0 -81
  111. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb +0 -199
@@ -1,32 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ApplicationService
4
- class Base
5
- include Servactory::DSL
3
+ module <%= namespace %>
4
+ class Base < Servactory::Base
5
+ <% unless minimal? -%>
6
6
 
7
7
  # More information: https://servactory.com/guide/extensions
8
- # include Servactory::DSL.with_extensions(
9
- # ApplicationService::Extensions::YourExtension::DSL
10
- # )
8
+ # extensions do
9
+ # before :actions, <%= namespace %>::Extensions::Authorization::DSL
10
+ # after :actions, <%= namespace %>::Extensions::Publishable::DSL
11
+ # end
11
12
 
12
- fail_on! ActiveRecord::RecordInvalid
13
+ # fail_on! ActiveRecord::RecordInvalid
14
+ # fail_on!(
15
+ # CustomException,
16
+ # with: ->(exception:) { ... }
17
+ # )
13
18
 
14
19
  # More information: https://servactory.com/guide/configuration
20
+ <% end -%>
15
21
  configuration do
16
- input_exception_class ApplicationService::Exceptions::Input
17
- internal_exception_class ApplicationService::Exceptions::Internal
18
- output_exception_class ApplicationService::Exceptions::Output
22
+ input_exception_class <%= namespace %>::Exceptions::Input
23
+ internal_exception_class <%= namespace %>::Exceptions::Internal
24
+ output_exception_class <%= namespace %>::Exceptions::Output
19
25
 
20
- failure_class ApplicationService::Exceptions::Failure
26
+ failure_class <%= namespace %>::Exceptions::Failure
21
27
 
22
- result_class ApplicationService::Result
28
+ result_class <%= namespace %>::Result
29
+ <% unless minimal? -%>
23
30
 
24
31
  # input_option_helpers(
25
32
  # [
26
33
  # Servactory::ToolKit::DynamicOptions::Format.use,
27
34
  # Servactory::ToolKit::DynamicOptions::Min.use,
28
35
  # Servactory::ToolKit::DynamicOptions::Max.use,
29
- # ApplicationService::DynamicOptions::CustomEq.use
36
+ # Servactory::ToolKit::DynamicOptions::MultipleOf.use,
37
+ # Servactory::ToolKit::DynamicOptions::Target.use,
38
+ # <%= namespace %>::DynamicOptions::CustomEq.use
30
39
  # ]
31
40
  # )
32
41
 
@@ -35,7 +44,9 @@ module ApplicationService
35
44
  # Servactory::ToolKit::DynamicOptions::Format.use,
36
45
  # Servactory::ToolKit::DynamicOptions::Min.use,
37
46
  # Servactory::ToolKit::DynamicOptions::Max.use,
38
- # ApplicationService::DynamicOptions::CustomEq.use
47
+ # Servactory::ToolKit::DynamicOptions::MultipleOf.use,
48
+ # Servactory::ToolKit::DynamicOptions::Target.use,
49
+ # <%= namespace %>::DynamicOptions::CustomEq.use
39
50
  # ]
40
51
  # )
41
52
 
@@ -44,14 +55,14 @@ module ApplicationService
44
55
  # Servactory::ToolKit::DynamicOptions::Format.use,
45
56
  # Servactory::ToolKit::DynamicOptions::Min.use,
46
57
  # Servactory::ToolKit::DynamicOptions::Max.use,
47
- # ApplicationService::DynamicOptions::CustomEq.use
58
+ # Servactory::ToolKit::DynamicOptions::MultipleOf.use,
59
+ # Servactory::ToolKit::DynamicOptions::Target.use,
60
+ # <%= namespace %>::DynamicOptions::CustomEq.use
48
61
  # ]
49
62
  # )
50
63
 
51
64
  # collection_mode_class_names [ActiveRecord::Relation]
52
65
 
53
- # hash_mode_class_names [CustomHash]
54
-
55
66
  # action_shortcuts(
56
67
  # %i[assign build create save],
57
68
  # {
@@ -64,9 +75,8 @@ module ApplicationService
64
75
 
65
76
  # action_aliases %i[do_it!]
66
77
 
67
- # i18n_root_key :servactory
68
-
69
78
  # predicate_methods_enabled false
79
+ <% end -%>
70
80
  end
71
81
  end
72
82
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ApplicationService
3
+ module <%= namespace %>
4
4
  module Exceptions
5
5
  class Input < Servactory::Exceptions::Input; end
6
6
  class Output < Servactory::Exceptions::Output; end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ApplicationService
3
+ module <%= namespace %>
4
4
  class Result < Servactory::Result; end
5
5
  end
@@ -0,0 +1,46 @@
1
+ Description:
2
+ Generates an RSpec spec file for a Servactory service.
3
+
4
+ Usage:
5
+ rails generate servactory:rspec NAME [inputs] [options]
6
+
7
+ Arguments:
8
+ NAME The name of the service (e.g., Users::Create, ProcessOrder)
9
+ inputs Input attributes with optional types (e.g., email:string, user:User)
10
+
11
+ Options:
12
+ --skip-pending Skip pending placeholder
13
+ --call-method=METHOD Primary call method: call or call! (default: call!)
14
+ --path=PATH Path to service files (default: app/services)
15
+ Spec path is auto-derived: app/X -> spec/X, lib/X -> spec/X
16
+
17
+ Type Formats:
18
+ Lowercase types are normalized to Ruby classes:
19
+ string -> String, integer -> Integer, boolean -> [TrueClass, FalseClass]
20
+
21
+ Capitalized Ruby class names are preserved as-is:
22
+ String, Integer, Hash, Array
23
+
24
+ Custom types (models, enums, Structs) are preserved:
25
+ User, Admin::Role, Billing::PaymentStatus
26
+
27
+ Untyped inputs default to String.
28
+
29
+ Examples:
30
+ rails generate servactory:rspec Users::Create email:string name:string
31
+ Creates spec/services/users/create_spec.rb with typed inputs
32
+
33
+ rails generate servactory:rspec Users::Create email:String name:String
34
+ Creates spec/services/users/create_spec.rb with Ruby class types
35
+
36
+ rails generate servactory:rspec ProcessOrder user:User event:Event
37
+ Creates spec with custom model/class type inputs
38
+
39
+ rails generate servactory:rspec Users::Create --path=lib/my_gem/services
40
+ Creates spec/my_gem/services/users/create_spec.rb for gem/library use
41
+
42
+ rails generate servactory:rspec ProcessOrder --call-method=call
43
+ Creates spec using .call instead of .call!
44
+
45
+ rails generate servactory:rspec CacheData --skip-pending
46
+ Creates spec without pending placeholder
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+
5
+ module Servactory
6
+ module Generators
7
+ class RspecGenerator < Servactory::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :inputs, type: :array, default: [], banner: "name:type"
11
+
12
+ class_option :call_method,
13
+ type: :string,
14
+ default: "call!",
15
+ enum: %w[call call!],
16
+ desc: "Primary call method (call or call!)"
17
+
18
+ class_option :path,
19
+ type: :string,
20
+ default: "app/services",
21
+ desc: "Path to service files (specs auto-generated to spec/...)"
22
+
23
+ class_option :skip_pending,
24
+ type: :boolean,
25
+ default: false,
26
+ desc: "Skip pending placeholder"
27
+
28
+ def create_spec_file
29
+ template "service_spec.rb.tt", spec_file_path
30
+ end
31
+
32
+ private
33
+
34
+ def spec_file_path
35
+ "#{specs_path}/#{file_path}_spec.rb"
36
+ end
37
+
38
+ def specs_path
39
+ # Convert services path to specs path:
40
+ # app/services → spec/services
41
+ # lib/foo → spec/foo
42
+ # engines/core/app/services → engines/core/spec/services
43
+ # engines/admin/lib/services → engines/admin/spec/services
44
+ path = services_path
45
+
46
+ # Handle Rails engine paths first
47
+ return path.sub(%r{/app/}, "/spec/") if path.match?(%r{^engines/[^/]+/app/})
48
+
49
+ return path.sub(%r{/lib/}, "/spec/") if path.match?(%r{^engines/[^/]+/lib/})
50
+
51
+ # Standard app/ and lib/ paths
52
+ path
53
+ .sub(%r{^app/}, "spec/")
54
+ .sub(%r{^lib/}, "spec/")
55
+ end
56
+
57
+ def services_path
58
+ options[:path]
59
+ end
60
+
61
+ def call_method
62
+ options[:call_method]
63
+ end
64
+
65
+ def skip_pending?
66
+ options[:skip_pending]
67
+ end
68
+
69
+ def inputs_with_examples
70
+ parsed_inputs.map do |input|
71
+ input.merge(example: example_value_for_type(input[:type]))
72
+ end
73
+ end
74
+
75
+ def example_value_for_type(type_string) # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
76
+ case type_string
77
+ when "Integer" then "1"
78
+ when "Float" then "1.0"
79
+ when "TrueClass", "[TrueClass, FalseClass]" then "true"
80
+ when "FalseClass" then "false"
81
+ when "Array" then "[]"
82
+ when "Hash" then "{}"
83
+ when "Symbol" then ":example"
84
+ when "Date" then "Date.current"
85
+ when "DateTime" then "DateTime.current"
86
+ when "Time" then "Time.current"
87
+ when "NilClass" then "nil"
88
+ when "BigDecimal" then 'BigDecimal("1.0")'
89
+ else
90
+ '"Some value"'
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe <%= class_name %>, type: :service do
4
+ <% unless skip_pending? -%>
5
+ pending "add some examples to (or delete) #{__FILE__}"
6
+
7
+ <% end -%>
8
+ describe ".<%= call_method %>" do
9
+ subject(:perform) { described_class.<%= call_method %>(**attributes) }
10
+
11
+ <% if inputs_with_examples.any? -%>
12
+ let(:attributes) do
13
+ {
14
+ <% inputs_with_examples.each_with_index do |input, index| -%>
15
+ <%= input[:name] %>: <%= input[:name] %><%= index < inputs_with_examples.size - 1 ? ',' : '' %>
16
+ <% end -%>
17
+ }
18
+ end
19
+
20
+ <% inputs_with_examples.each do |input| -%>
21
+ let(:<%= input[:name] %>) { <%= input[:example] %> }
22
+ <% end -%>
23
+ <% else -%>
24
+ let(:attributes) do
25
+ {
26
+ # TODO: Add input attributes
27
+ }
28
+ end
29
+ <% end -%>
30
+ <% if inputs_with_examples.any? -%>
31
+
32
+ describe "validations" do
33
+ describe "inputs" do
34
+ <% inputs_with_examples.each do |input| -%>
35
+ it do
36
+ expect { perform }.to(
37
+ have_input(:<%= input[:name] %>)
38
+ .valid_with(attributes)
39
+ .type(<%= input[:type] %>)
40
+ .required
41
+ )
42
+ end
43
+
44
+ <% end -%>
45
+ end
46
+ end
47
+ <% end -%>
48
+
49
+ describe "and the data required for work is also valid" do
50
+ it { expect(perform).to be_success_service }
51
+ end
52
+
53
+ describe "but the data required for work is invalid" do
54
+ # Provide a reason why the data is invalid and then use this:
55
+ # it { expect(perform).to be_failure_service }
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,51 @@
1
+ Description:
2
+ Generates a new Servactory service class.
3
+
4
+ Usage:
5
+ rails generate servactory:service NAME [inputs] [options]
6
+
7
+ Arguments:
8
+ NAME The name of the service (e.g., Users::Create, ProcessOrder)
9
+ inputs Input attributes with optional types (e.g., email:string, user:User)
10
+
11
+ Options:
12
+ --base-class=CLASS Base class for the service (default: ApplicationService::Base)
13
+ --skip-output Skip default output declaration
14
+ --path=PATH Path to generate service files (default: app/services)
15
+
16
+ Type Formats:
17
+ Lowercase types are normalized to Ruby classes:
18
+ string → String, integer → Integer, boolean → [TrueClass, FalseClass]
19
+
20
+ Capitalized Ruby class names are preserved as-is:
21
+ String, Integer, Hash, Array
22
+
23
+ Custom types (models, enums, Structs) are preserved:
24
+ User, Admin::Role, Billing::PaymentStatus
25
+
26
+ Untyped inputs default to String.
27
+
28
+ Examples:
29
+ rails generate servactory:service Users::Create
30
+ Creates app/services/users/create.rb with default structure
31
+
32
+ rails generate servactory:service Users::Create --path=lib/my_gem/services
33
+ Creates lib/my_gem/services/users/create.rb for gem/library use
34
+
35
+ rails generate servactory:service Users::Create email:string name:string
36
+ Creates service with typed inputs (lowercase types normalized to Ruby classes)
37
+
38
+ rails generate servactory:service Users::Create email:String name:String
39
+ Creates service with typed inputs (capitalized Ruby class names)
40
+
41
+ rails generate servactory:service ProcessOrder user:User event:Event
42
+ Creates service with custom model/class type inputs
43
+
44
+ rails generate servactory:service ProcessOrder role:Admin::Users::Role
45
+ Creates service with nested namespace type
46
+
47
+ rails generate servactory:service SendEmail recipient
48
+ Creates service with untyped input (defaults to String)
49
+
50
+ rails generate servactory:service CacheData key:String --skip-output
51
+ Creates service without default output declaration
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base"
4
+
5
+ module Servactory
6
+ module Generators
7
+ class ServiceGenerator < Servactory::Generators::NamedBase
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :inputs, type: :array, default: [], banner: "name:type"
11
+
12
+ class_option :base_class,
13
+ type: :string,
14
+ default: "ApplicationService::Base",
15
+ desc: "Base class for the service"
16
+
17
+ class_option :path,
18
+ type: :string,
19
+ default: "app/services",
20
+ desc: "Path to generate service files"
21
+
22
+ class_option :skip_output,
23
+ type: :boolean,
24
+ default: false,
25
+ desc: "Skip default output declaration"
26
+
27
+ def create_service
28
+ template "service.rb.tt", service_file_path
29
+ end
30
+
31
+ private
32
+
33
+ def service_file_path
34
+ "#{services_path}/#{file_path}.rb"
35
+ end
36
+
37
+ def services_path
38
+ options[:path]
39
+ end
40
+
41
+ def base_class_name
42
+ options[:base_class]
43
+ end
44
+
45
+ def skip_output?
46
+ options[:skip_output]
47
+ end
48
+
49
+ def input_declarations
50
+ parsed_inputs.map do |input|
51
+ "input :#{input[:name]}, type: #{input[:type]}"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= class_name %> < <%= base_class_name %>
4
+ <% input_declarations.each do |declaration| -%>
5
+ <%= declaration %>
6
+ <% end -%>
7
+ <% unless skip_output? -%>
8
+
9
+ output :data, type: String
10
+ <% end -%>
11
+
12
+ make :assign_data
13
+
14
+ private
15
+
16
+ def assign_data
17
+ # TODO: Implement service logic
18
+ <% unless skip_output? -%>
19
+ outputs.data = "done"
20
+ <% end -%>
21
+ end
22
+ end
@@ -2,15 +2,70 @@
2
2
 
3
3
  module Servactory
4
4
  module Actions
5
+ # Mutable collection manager for Action objects.
6
+ #
7
+ # ## Purpose
8
+ #
9
+ # Stores Action objects and provides query methods to retrieve actions
10
+ # sorted by position. Wraps a Set for unique action storage with
11
+ # position-based ordering capabilities.
12
+ #
13
+ # ## Usage
14
+ #
15
+ # ```ruby
16
+ # actions = Servactory::Actions::Collection.new
17
+ # actions << Action.new(:first, position: 1)
18
+ # actions << Action.new(:second, position: 2)
19
+ #
20
+ # actions.sorted_by_position # => Collection with sorted actions
21
+ # actions.empty? # => false
22
+ # actions.size # => 2
23
+ # ```
24
+ #
25
+ # ## Integration
26
+ #
27
+ # Used by Servactory::Actions::Stages::Stage to store actions within
28
+ # a stage, and by Servactory::Actions::Tools::Runner to execute
29
+ # actions in the correct order.
5
30
  class Collection
6
31
  extend Forwardable
7
32
 
8
- def_delegators :@collection, :<<, :each, :to_h, :sort_by
33
+ # @!method <<(action)
34
+ # Adds an action to the collection.
35
+ # @param action [Action] The action to add
36
+ # @return [Set] The updated collection
37
+ # @!method each
38
+ # Iterates over all actions in the collection.
39
+ # @yield [Action] Each action in the collection
40
+ # @!method to_h
41
+ # Converts collection to a hash.
42
+ # @return [Hash] Hash representation
43
+ # @!method sort_by
44
+ # Sorts actions by the given block.
45
+ # @yield [Action] Each action for comparison
46
+ # @return [Array<Action>] Sorted array of actions
47
+ # @!method size
48
+ # Returns the number of actions in the collection.
49
+ # @return [Integer] Number of actions
50
+ # @!method empty?
51
+ # Checks if the collection is empty.
52
+ # @return [Boolean] true if no actions registered
53
+ def_delegators :@collection, :<<, :each, :to_h, :sort_by, :size, :empty?
9
54
 
55
+ # Creates a new actions collection.
56
+ #
57
+ # @param collection [Set] Initial collection of actions (default: empty Set)
10
58
  def initialize(collection = Set.new)
11
59
  @collection = collection
12
60
  end
13
61
 
62
+ # Returns a new collection with actions sorted by position.
63
+ #
64
+ # @return [Collection] New collection with actions in position order
65
+ #
66
+ # @example
67
+ # sorted = actions.sorted_by_position
68
+ # sorted.each { |action| puts action.position }
14
69
  def sorted_by_position
15
70
  Collection.new(sort_by(&:position))
16
71
  end
@@ -41,9 +41,9 @@ module Servactory
41
41
 
42
42
  instance_eval(&block)
43
43
 
44
- @current_stage = nil
44
+ collection_of_stages << @current_stage unless @current_stage.actions.empty?
45
45
 
46
- nil
46
+ @current_stage = nil
47
47
  end
48
48
 
49
49
  def wrap_in(wrapper)
@@ -75,15 +75,15 @@ module Servactory
75
75
  def make(name, position: nil, **options)
76
76
  position = position.presence || next_position
77
77
 
78
- current_stage = @current_stage.presence || Stages::Stage.new(position:)
79
-
80
- current_stage.actions << Action.new(
81
- name,
82
- position:,
83
- **options
84
- )
85
-
86
- collection_of_stages << current_stage
78
+ if @current_stage.present?
79
+ # Inside stage block - just add action, stage will be added at block end
80
+ @current_stage.actions << Action.new(name, position:, **options)
81
+ else
82
+ # Outside stage block - create new stage and add to collection
83
+ new_stage = Stages::Stage.new(position:)
84
+ new_stage.actions << Action.new(name, position:, **options)
85
+ collection_of_stages << new_stage
86
+ end
87
87
  end
88
88
 
89
89
  def method_missing(name, *args, &block)
@@ -56,7 +56,7 @@ module Servactory
56
56
  end
57
57
 
58
58
  def raise_message_with!(formatted_text)
59
- raise @context.class.config.failure_class.new(
59
+ raise @context.config.failure_class.new(
60
60
  type: :base,
61
61
  message: @context.send(:servactory_service_info).translate(
62
62
  "methods.cannot_be_overwritten",