servactory 2.16.1 → 3.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +38 -9
- data/config/locales/de.yml +134 -0
- data/config/locales/en.yml +15 -0
- data/config/locales/es.yml +134 -0
- data/config/locales/fr.yml +134 -0
- data/config/locales/it.yml +134 -0
- data/config/locales/ru.yml +15 -0
- data/lib/generators/README.md +45 -0
- data/lib/generators/servactory/base.rb +82 -0
- data/lib/generators/servactory/extension/USAGE +54 -0
- data/lib/generators/servactory/extension/extension_generator.rb +41 -0
- data/lib/generators/servactory/extension/templates/extension.rb.tt +62 -0
- data/lib/generators/servactory/install/USAGE +27 -0
- data/lib/generators/servactory/install/install_generator.rb +94 -0
- data/lib/generators/servactory/{templates/services/application_service/base.rb → install/templates/application_service/base.rb.tt} +29 -19
- data/lib/generators/servactory/{templates/services/application_service/exceptions.rb → install/templates/application_service/exceptions.rb.tt} +1 -1
- data/lib/generators/servactory/{templates/services/application_service/result.rb → install/templates/application_service/result.rb.tt} +1 -1
- data/lib/generators/servactory/rspec/USAGE +46 -0
- data/lib/generators/servactory/rspec/rspec_generator.rb +95 -0
- data/lib/generators/servactory/rspec/templates/service_spec.rb.tt +58 -0
- data/lib/generators/servactory/service/USAGE +51 -0
- data/lib/generators/servactory/service/service_generator.rb +56 -0
- data/lib/generators/servactory/service/templates/service.rb.tt +22 -0
- data/lib/servactory/actions/collection.rb +56 -1
- data/lib/servactory/actions/dsl.rb +11 -11
- data/lib/servactory/actions/tools/rules.rb +1 -1
- data/lib/servactory/actions/tools/runner.rb +111 -28
- data/lib/servactory/base.rb +1 -7
- data/lib/servactory/configuration/actions/aliases/collection.rb +5 -0
- data/lib/servactory/configuration/actions/rescue_handlers/collection.rb +5 -0
- data/lib/servactory/configuration/actions/shortcuts/collection.rb +5 -0
- data/lib/servactory/configuration/collection_mode/class_names_collection.rb +5 -0
- data/lib/servactory/configuration/config.rb +36 -0
- data/lib/servactory/configuration/configurable.rb +95 -0
- data/lib/servactory/configuration/dsl.rb +3 -27
- data/lib/servactory/configuration/factory.rb +20 -20
- data/lib/servactory/configuration/hash_mode/class_names_collection.rb +5 -0
- data/lib/servactory/configuration/option_helpers/option_helpers_collection.rb +5 -0
- data/lib/servactory/context/warehouse/inputs.rb +2 -2
- data/lib/servactory/context/workspace/inputs.rb +2 -2
- data/lib/servactory/context/workspace/internals.rb +2 -2
- data/lib/servactory/context/workspace/outputs.rb +2 -2
- data/lib/servactory/context/workspace.rb +11 -7
- data/lib/servactory/dsl.rb +10 -8
- data/lib/servactory/maintenance/attributes/tools/validation.rb +1 -1
- data/lib/servactory/result.rb +2 -2
- data/lib/servactory/stroma/dsl.rb +118 -0
- data/lib/servactory/stroma/entry.rb +32 -0
- data/lib/servactory/stroma/exceptions/base.rb +45 -0
- data/lib/servactory/stroma/exceptions/invalid_hook_type.rb +29 -0
- data/lib/servactory/stroma/exceptions/key_already_registered.rb +32 -0
- data/lib/servactory/stroma/exceptions/registry_frozen.rb +33 -0
- data/lib/servactory/stroma/exceptions/registry_not_finalized.rb +33 -0
- data/lib/servactory/stroma/exceptions/unknown_hook_target.rb +39 -0
- data/lib/servactory/stroma/hooks/applier.rb +63 -0
- data/lib/servactory/stroma/hooks/collection.rb +103 -0
- data/lib/servactory/stroma/hooks/factory.rb +80 -0
- data/lib/servactory/stroma/hooks/hook.rb +74 -0
- data/lib/servactory/stroma/registry.rb +94 -0
- data/lib/servactory/stroma/settings/collection.rb +90 -0
- data/lib/servactory/stroma/settings/registry_settings.rb +88 -0
- data/lib/servactory/stroma/settings/setting.rb +113 -0
- data/lib/servactory/stroma/state.rb +59 -0
- data/lib/servactory/test_kit/fake_type.rb +23 -0
- data/lib/servactory/test_kit/result.rb +45 -0
- data/lib/servactory/test_kit/rspec/helpers/argument_matchers.rb +97 -0
- data/lib/servactory/test_kit/rspec/helpers/concerns/error_messages.rb +179 -0
- data/lib/servactory/test_kit/rspec/helpers/concerns/service_class_validation.rb +74 -0
- data/lib/servactory/test_kit/rspec/helpers/fluent.rb +110 -0
- data/lib/servactory/test_kit/rspec/helpers/input_validator.rb +149 -0
- data/lib/servactory/test_kit/rspec/helpers/legacy.rb +228 -0
- data/lib/servactory/test_kit/rspec/helpers/mock_executor.rb +256 -0
- data/lib/servactory/test_kit/rspec/helpers/output_validator.rb +121 -0
- data/lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb +422 -0
- data/lib/servactory/test_kit/rspec/helpers/service_mock_config.rb +129 -0
- data/lib/servactory/test_kit/rspec/helpers.rb +51 -84
- data/lib/servactory/test_kit/rspec/matchers/base/attribute_matcher.rb +324 -0
- data/lib/servactory/test_kit/rspec/matchers/base/submatcher.rb +133 -0
- data/lib/servactory/test_kit/rspec/matchers/base/submatcher_context.rb +101 -0
- data/lib/servactory/test_kit/rspec/matchers/base/submatcher_registry.rb +205 -0
- data/lib/servactory/test_kit/rspec/matchers/concerns/attribute_data_access.rb +100 -0
- data/lib/servactory/test_kit/rspec/matchers/concerns/error_message_builder.rb +106 -0
- data/lib/servactory/test_kit/rspec/matchers/concerns/value_comparison.rb +97 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +89 -219
- data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +74 -166
- data/lib/servactory/test_kit/rspec/matchers/have_service_output_matcher.rb +238 -0
- data/lib/servactory/test_kit/rspec/matchers/result/be_failure_service_matcher.rb +257 -0
- data/lib/servactory/test_kit/rspec/matchers/result/be_success_service_matcher.rb +185 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/input/default_submatcher.rb +81 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/input/optional_submatcher.rb +62 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/input/required_submatcher.rb +93 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/input/valid_with_submatcher.rb +271 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/consists_of_submatcher.rb +85 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/inclusion_submatcher.rb +120 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/message_submatcher.rb +115 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/must_submatcher.rb +82 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/schema_submatcher.rb +102 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/target_submatcher.rb +125 -0
- data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/types_submatcher.rb +77 -0
- data/lib/servactory/test_kit/rspec/matchers.rb +126 -285
- data/lib/servactory/test_kit/utils/faker.rb +62 -2
- data/lib/servactory/tool_kit/dynamic_options/consists_of.rb +166 -0
- data/lib/servactory/tool_kit/dynamic_options/format.rb +195 -8
- data/lib/servactory/tool_kit/dynamic_options/inclusion.rb +219 -17
- data/lib/servactory/tool_kit/dynamic_options/max.rb +143 -0
- data/lib/servactory/tool_kit/dynamic_options/min.rb +143 -0
- data/lib/servactory/tool_kit/dynamic_options/multiple_of.rb +157 -8
- data/lib/servactory/tool_kit/dynamic_options/must.rb +194 -0
- data/lib/servactory/tool_kit/dynamic_options/schema.rb +226 -2
- data/lib/servactory/tool_kit/dynamic_options/target.rb +252 -0
- data/lib/servactory/version.rb +4 -4
- data/lib/servactory.rb +4 -0
- metadata +73 -19
- data/lib/generators/servactory/install_generator.rb +0 -21
- data/lib/generators/servactory/rspec_generator.rb +0 -88
- data/lib/generators/servactory/service_generator.rb +0 -49
- data/lib/servactory/configuration/setup.rb +0 -97
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +0 -68
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +0 -73
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/message_matcher.rb +0 -91
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +0 -72
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/schema_matcher.rb +0 -92
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/types_matcher.rb +0 -72
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/default_matcher.rb +0 -69
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/optional_matcher.rb +0 -63
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/required_matcher.rb +0 -81
- 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
|
|
4
|
-
class Base
|
|
5
|
-
|
|
3
|
+
module <%= namespace %>
|
|
4
|
+
class Base < Servactory::Base
|
|
5
|
+
<% unless minimal? -%>
|
|
6
6
|
|
|
7
7
|
# More information: https://servactory.com/guide/extensions
|
|
8
|
-
#
|
|
9
|
-
#
|
|
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
|
|
17
|
-
internal_exception_class
|
|
18
|
-
output_exception_class
|
|
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
|
|
26
|
+
failure_class <%= namespace %>::Exceptions::Failure
|
|
21
27
|
|
|
22
|
-
result_class
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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.
|
|
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",
|