servactory 3.1.0.rc2 → 3.1.0.rc4

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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/README.md +7 -2
  4. data/config/locales/ja.yml +134 -0
  5. data/lib/generators/README.md +2 -2
  6. data/lib/generators/servactory/extension/USAGE +1 -1
  7. data/lib/generators/servactory/install/USAGE +2 -2
  8. data/lib/generators/servactory/rspec/USAGE +4 -4
  9. data/lib/generators/servactory/rspec/rspec_generator.rb +2 -2
  10. data/lib/generators/servactory/service/USAGE +5 -5
  11. data/lib/servactory/actions/collection.rb +21 -5
  12. data/lib/servactory/actions/stages/collection.rb +19 -1
  13. data/lib/servactory/actions/tools/rules.rb +1 -1
  14. data/lib/servactory/actions/tools/runner.rb +1 -1
  15. data/lib/servactory/configuration/collection_mode/class_names_collection.rb +1 -1
  16. data/lib/servactory/configuration/config.rb +16 -0
  17. data/lib/servactory/configuration/configurable.rb +7 -4
  18. data/lib/servactory/configuration/factory.rb +30 -3
  19. data/lib/servactory/configuration/hash_mode/class_names_collection.rb +5 -1
  20. data/lib/servactory/configuration/option_helpers/option_helpers_collection.rb +156 -29
  21. data/lib/servactory/context/warehouse/inputs.rb +1 -1
  22. data/lib/servactory/context/workspace/inputs.rb +2 -2
  23. data/lib/servactory/context/workspace/internals.rb +2 -2
  24. data/lib/servactory/context/workspace/outputs.rb +2 -2
  25. data/lib/servactory/context/workspace.rb +7 -7
  26. data/lib/servactory/info/builder.rb +4 -3
  27. data/lib/servactory/inputs/dsl.rb +5 -1
  28. data/lib/servactory/inputs/input.rb +1 -2
  29. data/lib/servactory/inputs/tools/validation.rb +10 -18
  30. data/lib/servactory/internals/dsl.rb +7 -1
  31. data/lib/servactory/maintenance/attributes/base.rb +4 -1
  32. data/lib/servactory/maintenance/attributes/collection.rb +61 -6
  33. data/lib/servactory/maintenance/options/collection.rb +71 -30
  34. data/lib/servactory/maintenance/options/option.rb +3 -12
  35. data/lib/servactory/maintenance/options/registrar.rb +3 -13
  36. data/lib/servactory/maintenance/validations/checkers/type.rb +23 -5
  37. data/lib/servactory/maintenance/validations/performer.rb +12 -23
  38. data/lib/servactory/outputs/dsl.rb +7 -1
  39. data/lib/servactory/result.rb +21 -8
  40. data/lib/servactory/test_kit/rspec/helpers/exception_validator.rb +112 -0
  41. data/lib/servactory/test_kit/rspec/helpers/input_validator.rb +3 -1
  42. data/lib/servactory/test_kit/rspec/helpers/mock_executor.rb +125 -125
  43. data/lib/servactory/test_kit/rspec/helpers/service_inputs_guard.rb +98 -0
  44. data/lib/servactory/test_kit/rspec/helpers/service_inputs_matcher.rb +75 -0
  45. data/lib/servactory/test_kit/rspec/helpers/service_mock_builder.rb +185 -50
  46. data/lib/servactory/test_kit/rspec/helpers/service_mock_config.rb +45 -10
  47. data/lib/servactory/test_kit/rspec/helpers/wrap_block_adapter.rb +74 -0
  48. data/lib/servactory/test_kit/rspec/helpers.rb +4 -0
  49. data/lib/servactory/test_kit/rspec/matchers/base/attribute_matcher.rb +72 -16
  50. data/lib/servactory/test_kit/rspec/matchers/base/message_expectation.rb +194 -0
  51. data/lib/servactory/test_kit/rspec/matchers/base/submatcher.rb +8 -0
  52. data/lib/servactory/test_kit/rspec/matchers/concerns/option_helper_rules.rb +148 -0
  53. data/lib/servactory/test_kit/rspec/matchers/concerns/proc_message.rb +117 -0
  54. data/lib/servactory/test_kit/rspec/matchers/concerns/required_message.rb +92 -0
  55. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +27 -4
  56. data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +10 -3
  57. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/required_submatcher.rb +58 -23
  58. data/lib/servactory/test_kit/rspec/matchers/submatchers/input/valid_with_submatcher.rb +124 -59
  59. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/message_submatcher.rb +133 -39
  60. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/must_submatcher.rb +192 -17
  61. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/target_submatcher.rb +4 -2
  62. data/lib/servactory/test_kit/rspec/matchers/submatchers/shared/types_submatcher.rb +6 -0
  63. data/lib/servactory/test_kit/rspec/matchers.rb +15 -0
  64. data/lib/servactory/tool_kit/dynamic_options/consists_of.rb +34 -11
  65. data/lib/servactory/tool_kit/dynamic_options/format.rb +3 -4
  66. data/lib/servactory/tool_kit/dynamic_options/inclusion.rb +3 -3
  67. data/lib/servactory/tool_kit/dynamic_options/max.rb +3 -3
  68. data/lib/servactory/tool_kit/dynamic_options/min.rb +3 -3
  69. data/lib/servactory/tool_kit/dynamic_options/multiple_of.rb +35 -7
  70. data/lib/servactory/tool_kit/dynamic_options/must.rb +2 -3
  71. data/lib/servactory/tool_kit/dynamic_options/schema.rb +40 -25
  72. data/lib/servactory/tool_kit/dynamic_options/target.rb +4 -4
  73. data/lib/servactory/utils.rb +13 -3
  74. data/lib/servactory/version.rb +1 -1
  75. data/lib/servactory.rb +3 -4
  76. metadata +20 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a7c9b4d9ad2e7d15265ba278d740eb34e6df33590bf517a0b42d914dad46e22
4
- data.tar.gz: 21ad5c1814f6bd2c62221057547f5388cf928fd1f937f37131dab041d6325983
3
+ metadata.gz: f404944fef3e958464a2287b2aa1ca008cd5c6a04b0de7dbf3099f871878eee2
4
+ data.tar.gz: fd55568c5b5a48003ce84f1cb9aa64adbae0140b707517f99186ab2064127d94
5
5
  SHA512:
6
- metadata.gz: 9f231bf3c0662ec8285125cacd138d765622eb1ebad7e9eae5d95ae2dd84ad48f80a65783e208b3b9752e40e9bc755ca245bcf2f3b1c68b3724ca13a68d1f6a5
7
- data.tar.gz: 8fb3f9f3bcf2a49f26d4cff76885537559ba4173c78585c2432fc7bf814a8e587303dc941c36a7992800bff1c561661cc04ae20beffbc17dac4d5a0af64de387
6
+ metadata.gz: a2a7b707d93c5e210c9c0eefb54bbfd3374ac48fda5afbcce8669ec4eda8f1358c063a9a7c8121b3ea6c651fc4c8748e380f72c8a33ca13ab586d39b89fbe505
7
+ data.tar.gz: 36f8a525ca8a622771136ed8955a74091fd2a17a3aa444871c711048c45f60c6e612eaec28cd7220f1663762095fac203d9cb7996b7630448cc03d2964763cd7
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Anton Sokolov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -52,7 +52,7 @@ gem "servactory"
52
52
  ### Define service
53
53
 
54
54
  ```ruby
55
- class UserService::Authenticate < Servactory::Base
55
+ class Users::Authenticate < Servactory::Base
56
56
  input :email, type: String
57
57
  input :password, type: String
58
58
 
@@ -77,7 +77,7 @@ end
77
77
  ```ruby
78
78
  class SessionsController < ApplicationController
79
79
  def create
80
- service = UserService::Authenticate.call(**session_params)
80
+ service = Users::Authenticate.call(**session_params)
81
81
 
82
82
  if service.success?
83
83
  session[:current_user_id] = service.user.id
@@ -96,6 +96,11 @@ class SessionsController < ApplicationController
96
96
  end
97
97
  ```
98
98
 
99
+ ## 🧩 Ecosystem
100
+
101
+ - [**Stroma**](https://github.com/servactory/stroma) — A foundation for building modular, extensible DSLs in Ruby
102
+ - [**OpenTelemetry Instrumentation**](https://github.com/servactory/opentelemetry-instrumentation-servactory) — Automatic tracing of service calls and actions with span attributes and failure detection
103
+
99
104
  ## 🤝 Contributing
100
105
 
101
106
  We love contributions! Check out our [Contributing Guide](https://servactory.com/CONTRIBUTING) to get started.
@@ -0,0 +1,134 @@
1
+ ja:
2
+ servactory:
3
+ common:
4
+ undefined_method:
5
+ missing_name: "[%{service_class_name}] %{error_text}"
6
+ methods:
7
+ call:
8
+ not_used: "[%{service_class_name}] 実行する処理がありません。`make`を使用するか、`call`メソッドを作成してください。"
9
+ cannot_be_overwritten: "[%{service_class_name}] 次のメソッドは上書きできません: %{list_of_methods}"
10
+ inputs:
11
+ undefined:
12
+ for_fetch: "[%{service_class_name}] 未定義の入力`%{input_name}`"
13
+ for_assign: "[%{service_class_name}] 未定義の入力`%{input_name}`"
14
+ validations:
15
+ must:
16
+ default_error: "[%{service_class_name}] 入力`%{input_name}`は「%{code}」を満たす必要があります"
17
+ syntax_error: "[%{service_class_name}] 入力`%{input_name}`の`%{code}`内の構文エラー: %{exception_message}"
18
+ dynamic_options:
19
+ consists_of:
20
+ required: "[%{service_class_name}] 入力コレクション`%{input_name}`の必須要素が不足しています"
21
+ wrong_type: "[%{service_class_name}] 入力コレクション`%{input_name}`の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
22
+ wrong_element_type: "[%{service_class_name}] 入力コレクション`%{input_name}`の要素の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
23
+ format:
24
+ default: "[%{service_class_name}] 入力`%{input_name}`は`%{format_name}`フォーマットに一致しません"
25
+ wrong_pattern: "[%{service_class_name}] 入力`%{input_name}`は`%{format_name}`フォーマットに一致しません"
26
+ wrong_type: "[%{service_class_name}] 入力`%{input_name}`は`%{format_name}`フォーマット検証のためにStringである必要があります"
27
+ unknown: "[%{service_class_name}] 入力`%{input_name}`に指定された`%{format_name}`フォーマットは不明です"
28
+ inclusion:
29
+ default: "[%{service_class_name}] `%{input_name}`の値が不正です。`%{input_inclusion}`のいずれかである必要がありますが、`%{value}`が渡されました"
30
+ invalid_option: "[%{service_class_name}] 入力`%{input_name}`の`%{option_name}`オプションに値がありません"
31
+ min:
32
+ default: "[%{service_class_name}] 入力`%{input_name}`は値`%{value}`を受け取りましたが、`%{option_value}`より小さいです"
33
+ max:
34
+ default: "[%{service_class_name}] 入力`%{input_name}`は値`%{value}`を受け取りましたが、`%{option_value}`より大きいです"
35
+ multiple_of:
36
+ default: "[%{service_class_name}] 入力`%{input_name}`の値`%{value}`は`%{option_value}`の倍数ではありません"
37
+ blank: "[%{service_class_name}] 入力`%{input_name}`の`%{option_name}`オプションに無効な値`%{option_value}`があります"
38
+ divided_by_0: "[%{service_class_name}] 入力`%{input_name}`の`%{option_name}`オプションに無効な値`%{option_value}`があります"
39
+ schema:
40
+ wrong_type: "[%{service_class_name}] 入力`%{input_name}`の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
41
+ wrong_element_type: "[%{service_class_name}] 入力ハッシュ`%{input_name}`の型が不正です。`%{key_name}`には`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
42
+ wrong_element_value: "[%{service_class_name}] 入力ハッシュ`%{input_name}`の値が不正です。`%{key_name}`には`%{expected_type}`型の値が期待されましたが、`%{given_type}`が渡されました"
43
+ target:
44
+ default: "[%{service_class_name}] 入力`%{input_name}`のターゲットが不正です。`%{expected_target}`である必要がありますが、`%{value}`が渡されました"
45
+ invalid_option: "[%{service_class_name}] 入力`%{input_name}`の`%{option_name}`オプションに値がありません"
46
+ required:
47
+ default_error:
48
+ default: "[%{service_class_name}] 必須の入力`%{input_name}`が不足しています"
49
+ type:
50
+ default_error:
51
+ default: "[%{service_class_name}] 入力`%{input_name}`の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
52
+ tools:
53
+ find_unnecessary:
54
+ error: "[%{service_class_name}] 予期しない属性: `%{unnecessary_attributes}`"
55
+ rules:
56
+ error: "[%{service_class_name}] 入力`%{input_name}`のオプションに競合があります: `%{conflict_code}`"
57
+ internals:
58
+ undefined:
59
+ for_fetch: "[%{service_class_name}] 未定義の内部属性`%{internal_name}`"
60
+ for_assign: "[%{service_class_name}] 未定義の内部属性`%{internal_name}`"
61
+ validations:
62
+ must:
63
+ default_error: "[%{service_class_name}] 内部属性`%{internal_name}`は「%{code}」を満たす必要があります"
64
+ syntax_error: "[%{service_class_name}] 内部属性`%{internal_name}`の`%{code}`内の構文エラー: %{exception_message}"
65
+ dynamic_options:
66
+ consists_of:
67
+ required: "[%{service_class_name}] 内部属性コレクション`%{internal_name}`の必須要素が不足しています"
68
+ wrong_type: "[%{service_class_name}] 内部属性コレクション`%{internal_name}`の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
69
+ wrong_element_type: "[%{service_class_name}] 内部属性コレクション`%{internal_name}`の要素の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
70
+ format:
71
+ default: "[%{service_class_name}] 内部属性`%{internal_name}`は`%{format_name}`フォーマットに一致しません"
72
+ wrong_pattern: "[%{service_class_name}] 内部属性`%{internal_name}`は`%{format_name}`フォーマットに一致しません"
73
+ wrong_type: "[%{service_class_name}] 内部属性`%{internal_name}`は`%{format_name}`フォーマット検証のためにStringである必要があります"
74
+ unknown: "[%{service_class_name}] 内部属性`%{internal_name}`に指定された`%{format_name}`フォーマットは不明です"
75
+ inclusion:
76
+ default: "[%{service_class_name}] `%{internal_name}`の値が不正です。`%{internal_inclusion}`のいずれかである必要がありますが、`%{value}`が渡されました"
77
+ invalid_option: "[%{service_class_name}] 内部属性`%{internal_name}`の`%{option_name}`オプションに値がありません"
78
+ min:
79
+ default: "[%{service_class_name}] 内部属性`%{internal_name}`は値`%{value}`を受け取りましたが、`%{option_value}`より小さいです"
80
+ max:
81
+ default: "[%{service_class_name}] 内部属性`%{internal_name}`は値`%{value}`を受け取りましたが、`%{option_value}`より大きいです"
82
+ multiple_of:
83
+ default: "[%{service_class_name}] 内部属性`%{internal_name}`の値`%{value}`は`%{option_value}`の倍数ではありません"
84
+ blank: "[%{service_class_name}] 内部属性`%{internal_name}`の`%{option_name}`オプションに無効な値`%{option_value}`があります"
85
+ divided_by_0: "[%{service_class_name}] 内部属性`%{internal_name}`の`%{option_name}`オプションに無効な値`%{option_value}`があります"
86
+ schema:
87
+ wrong_type: "[%{service_class_name}] 内部属性`%{internal_name}`の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
88
+ wrong_element_type: "[%{service_class_name}] 内部属性ハッシュ`%{internal_name}`の型が不正です。`%{key_name}`には`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
89
+ wrong_element_value: "[%{service_class_name}] 内部属性ハッシュ`%{internal_name}`の値が不正です。`%{key_name}`には`%{expected_type}`型の値が期待されましたが、`%{given_type}`が渡されました"
90
+ target:
91
+ default: "[%{service_class_name}] 内部属性`%{internal_name}`のターゲットが不正です。`%{expected_target}`である必要がありますが、`%{value}`が渡されました"
92
+ invalid_option: "[%{service_class_name}] 内部属性`%{internal_name}`の`%{option_name}`オプションに値がありません"
93
+ type:
94
+ default_error:
95
+ default: "[%{service_class_name}] 内部属性`%{internal_name}`の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
96
+ outputs:
97
+ undefined:
98
+ for_fetch: "[%{service_class_name}] 未定義の出力属性`%{output_name}`"
99
+ for_assign: "[%{service_class_name}] 未定義の出力属性`%{output_name}`"
100
+ validations:
101
+ must:
102
+ default_error: "[%{service_class_name}] 出力属性`%{output_name}`は「%{code}」を満たす必要があります"
103
+ syntax_error: "[%{service_class_name}] 出力属性`%{output_name}`の`%{code}`内の構文エラー: %{exception_message}"
104
+ dynamic_options:
105
+ consists_of:
106
+ required: "[%{service_class_name}] 出力属性コレクション`%{output_name}`の必須要素が不足しています"
107
+ wrong_type: "[%{service_class_name}] 出力属性コレクション`%{output_name}`の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
108
+ wrong_element_type: "[%{service_class_name}] 出力属性コレクション`%{output_name}`の要素の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
109
+ format:
110
+ default: "[%{service_class_name}] 出力属性`%{output_name}`は`%{format_name}`フォーマットに一致しません"
111
+ wrong_pattern: "[%{service_class_name}] 出力属性`%{output_name}`は`%{format_name}`フォーマットに一致しません"
112
+ wrong_type: "[%{service_class_name}] 出力属性`%{output_name}`は`%{format_name}`フォーマット検証のためにStringである必要があります"
113
+ unknown: "[%{service_class_name}] 出力属性`%{output_name}`に指定された`%{format_name}`フォーマットは不明です"
114
+ inclusion:
115
+ default: "[%{service_class_name}] `%{output_name}`の値が不正です。`%{output_inclusion}`のいずれかである必要がありますが、`%{value}`が渡されました"
116
+ invalid_option: "[%{service_class_name}] 出力属性`%{output_name}`の`%{option_name}`オプションに値がありません"
117
+ min:
118
+ default: "[%{service_class_name}] 出力属性`%{output_name}`は値`%{value}`を受け取りましたが、`%{option_value}`より小さいです"
119
+ max:
120
+ default: "[%{service_class_name}] 出力属性`%{output_name}`は値`%{value}`を受け取りましたが、`%{option_value}`より大きいです"
121
+ multiple_of:
122
+ default: "[%{service_class_name}] 出力属性`%{output_name}`の値`%{value}`は`%{option_value}`の倍数ではありません"
123
+ blank: "[%{service_class_name}] 出力属性`%{output_name}`の`%{option_name}`オプションに無効な値`%{option_value}`があります"
124
+ divided_by_0: "[%{service_class_name}] 出力属性`%{output_name}`の`%{option_name}`オプションに無効な値`%{option_value}`があります"
125
+ schema:
126
+ wrong_type: "[%{service_class_name}] 出力属性`%{output_name}`の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
127
+ wrong_element_type: "[%{service_class_name}] 出力属性ハッシュ`%{output_name}`の型が不正です。`%{key_name}`には`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
128
+ wrong_element_value: "[%{service_class_name}] 出力属性ハッシュ`%{output_name}`の値が不正です。`%{key_name}`には`%{expected_type}`型の値が期待されましたが、`%{given_type}`が渡されました"
129
+ target:
130
+ default: "[%{service_class_name}] 出力属性`%{output_name}`のターゲットが不正です。`%{expected_target}`である必要がありますが、`%{value}`が渡されました"
131
+ invalid_option: "[%{service_class_name}] 出力属性`%{output_name}`の`%{option_name}`オプションに値がありません"
132
+ type:
133
+ default_error:
134
+ default: "[%{service_class_name}] 出力属性`%{output_name}`の型が不正です。`%{expected_type}`が期待されましたが、`%{given_type}`が渡されました"
@@ -9,7 +9,7 @@ rails generate servactory:install [options]
9
9
  **Options:**
10
10
  - `--namespace` — Base namespace (default: `ApplicationService`)
11
11
  - `--path` — Path to install service files (default: `app/services`)
12
- - `--locales` — Locales to install (available: `en`, `ru`, `de`, `fr`, `es`, `it`)
12
+ - `--locales` — Locales to install (available: `en`, `ru`, `de`, `fr`, `es`, `it`, `ja`)
13
13
  - `--minimal` — Generate minimal setup without configuration examples
14
14
 
15
15
  ## Service
@@ -19,7 +19,7 @@ rails generate servactory:service NAME [inputs] [options]
19
19
  ```
20
20
 
21
21
  **Arguments:**
22
- - `NAME` — Service name (e.g., `Users::Create`, `ProcessOrder`)
22
+ - `NAME` — Service name (e.g., `Users::Create`, `Orders::Process`)
23
23
  - `inputs` — Input attributes with optional types (e.g., `email:string name:String user:User`)
24
24
 
25
25
  **Options:**
@@ -47,7 +47,7 @@ Usage in Service:
47
47
 
48
48
  Then use the DSL method in your services:
49
49
 
50
- class MyService < ApplicationService::Base
50
+ class Users::Create < ApplicationService::Base
51
51
  my_extension!(my_setting: "value")
52
52
 
53
53
  # ...
@@ -6,7 +6,7 @@ Usage:
6
6
 
7
7
  Options:
8
8
  --namespace=NAMESPACE Base namespace for services (default: ApplicationService)
9
- --locales=LOCALES Locales to install (available: en, ru, de, fr, es, it)
9
+ --locales=LOCALES Locales to install (available: en, ru, de, fr, es, it, ja)
10
10
  --minimal Generate minimal setup without configuration examples
11
11
  --path=PATH Path to install service files (default: app/services)
12
12
 
@@ -21,7 +21,7 @@ Examples:
21
21
  Creates app/services/my_app/services/ with all files
22
22
 
23
23
  rails generate servactory:install --locales=en,de,fr
24
- Also copies locale files to config/locales/ (available: en, ru, de, fr, es, it)
24
+ Also copies locale files to config/locales/ (available: en, ru, de, fr, es, it, ja)
25
25
 
26
26
  rails generate servactory:install --minimal
27
27
  Creates minimal setup without configuration examples
@@ -5,7 +5,7 @@ Usage:
5
5
  rails generate servactory:rspec NAME [inputs] [options]
6
6
 
7
7
  Arguments:
8
- NAME The name of the service (e.g., Users::Create, ProcessOrder)
8
+ NAME The name of the service (e.g., Users::Create, Orders::Process)
9
9
  inputs Input attributes with optional types (e.g., email:string, user:User)
10
10
 
11
11
  Options:
@@ -33,14 +33,14 @@ Examples:
33
33
  rails generate servactory:rspec Users::Create email:String name:String
34
34
  Creates spec/services/users/create_spec.rb with Ruby class types
35
35
 
36
- rails generate servactory:rspec ProcessOrder user:User event:Event
36
+ rails generate servactory:rspec Orders::Process user:User event:Event
37
37
  Creates spec with custom model/class type inputs
38
38
 
39
39
  rails generate servactory:rspec Users::Create --path=lib/my_gem/services
40
40
  Creates spec/my_gem/services/users/create_spec.rb for gem/library use
41
41
 
42
- rails generate servactory:rspec ProcessOrder --call-method=call
42
+ rails generate servactory:rspec Orders::Process --call-method=call
43
43
  Creates spec using .call instead of .call!
44
44
 
45
- rails generate servactory:rspec CacheData --skip-pending
45
+ rails generate servactory:rspec Caches::Write --skip-pending
46
46
  Creates spec without pending placeholder
@@ -78,8 +78,8 @@ module Servactory
78
78
  when "Float" then "1.0"
79
79
  when "TrueClass", "[TrueClass, FalseClass]" then "true"
80
80
  when "FalseClass" then "false"
81
- when "Array" then "[]"
82
- when "Hash" then "{}"
81
+ when "Array" then '["example"]'
82
+ when "Hash" then '{ key: "value" }'
83
83
  when "Symbol" then ":example"
84
84
  when "Date" then "Date.current"
85
85
  when "DateTime" then "DateTime.current"
@@ -5,7 +5,7 @@ Usage:
5
5
  rails generate servactory:service NAME [inputs] [options]
6
6
 
7
7
  Arguments:
8
- NAME The name of the service (e.g., Users::Create, ProcessOrder)
8
+ NAME The name of the service (e.g., Users::Create, Orders::Process)
9
9
  inputs Input attributes with optional types (e.g., email:string, user:User)
10
10
 
11
11
  Options:
@@ -38,14 +38,14 @@ Examples:
38
38
  rails generate servactory:service Users::Create email:String name:String
39
39
  Creates service with typed inputs (capitalized Ruby class names)
40
40
 
41
- rails generate servactory:service ProcessOrder user:User event:Event
41
+ rails generate servactory:service Orders::Process user:User event:Event
42
42
  Creates service with custom model/class type inputs
43
43
 
44
- rails generate servactory:service ProcessOrder role:Admin::Users::Role
44
+ rails generate servactory:service Orders::Process role:Admin::Users::Role
45
45
  Creates service with nested namespace type
46
46
 
47
- rails generate servactory:service SendEmail recipient
47
+ rails generate servactory:service Emails::Send recipient
48
48
  Creates service with untyped input (defaults to String)
49
49
 
50
- rails generate servactory:service CacheData key:String --skip-output
50
+ rails generate servactory:service Caches::Write key:String --skip-output
51
51
  Creates service without default output declaration
@@ -30,10 +30,6 @@ module Servactory
30
30
  class Collection
31
31
  extend Forwardable
32
32
 
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
33
  # @!method each
38
34
  # Iterates over all actions in the collection.
39
35
  # @yield [Action] Each action in the collection
@@ -50,7 +46,7 @@ module Servactory
50
46
  # @!method empty?
51
47
  # Checks if the collection is empty.
52
48
  # @return [Boolean] true if no actions registered
53
- def_delegators :@collection, :<<, :each, :to_h, :sort_by, :size, :empty?
49
+ def_delegators :@collection, :each, :to_h, :sort_by, :size, :empty?
54
50
 
55
51
  # Creates a new actions collection.
56
52
  #
@@ -59,6 +55,26 @@ module Servactory
59
55
  @collection = collection
60
56
  end
61
57
 
58
+ # Duplicates the collection, resetting memoized caches.
59
+ #
60
+ # @param original [Collection] The collection being duplicated
61
+ # @return [void]
62
+ def initialize_dup(original)
63
+ super
64
+ @collection = original.instance_variable_get(:@collection).dup
65
+ @sorted_by_position = nil
66
+ end
67
+
68
+ # Adds an action to the collection, invalidating the sorted cache.
69
+ #
70
+ # @param action [Action] The action to add
71
+ # @return [Collection] self
72
+ def <<(action)
73
+ @collection << action
74
+ @sorted_by_position = nil
75
+ self
76
+ end
77
+
62
78
  # Returns a new collection with actions sorted by position.
63
79
  #
64
80
  # @return [Collection] New collection with actions in position order
@@ -6,12 +6,30 @@ module Servactory
6
6
  class Collection
7
7
  extend Forwardable
8
8
 
9
- def_delegators :@collection, :<<, :each, :merge, :to_h, :sort_by, :size, :empty?
9
+ def_delegators :@collection, :each, :to_h, :sort_by, :size, :empty?
10
10
 
11
11
  def initialize(collection = Set.new)
12
12
  @collection = collection
13
13
  end
14
14
 
15
+ def initialize_dup(original)
16
+ super
17
+ @collection = original.instance_variable_get(:@collection).dup
18
+ @sorted_by_position = nil
19
+ end
20
+
21
+ def <<(stage)
22
+ @collection << stage
23
+ @sorted_by_position = nil
24
+ self
25
+ end
26
+
27
+ def merge(other)
28
+ @collection.merge(other)
29
+ @sorted_by_position = nil
30
+ self
31
+ end
32
+
15
33
  def sorted_by_position
16
34
  @sorted_by_position ||= Collection.new(sort_by(&:position))
17
35
  end
@@ -56,7 +56,7 @@ module Servactory
56
56
  end
57
57
 
58
58
  def raise_message_with!(formatted_text)
59
- raise @context.config.failure_class.new(
59
+ raise @context.class.config.failure_class.new(
60
60
  type: :base,
61
61
  message: @context.send(:servactory_service_info).translate(
62
62
  "methods.cannot_be_overwritten",
@@ -179,7 +179,7 @@ module Servactory
179
179
  # @param exception [Exception] The exception to handle
180
180
  # @return [Exception, nil] The exception if handled, nil otherwise
181
181
  def rescue_with_handler(exception) # rubocop:disable Metrics/MethodLength
182
- _, handler = @context.config.action_rescue_handlers.reverse_each.detect do |class_or_name, _|
182
+ _, handler = @context.class.config.action_rescue_handlers.reverse_each.detect do |class_or_name, _|
183
183
  if (detected_exception = Servactory::Utils.constantize_class(class_or_name))
184
184
  detected_exception === exception # rubocop:disable Style/CaseEquality
185
185
  end
@@ -6,7 +6,7 @@ module Servactory
6
6
  class ClassNamesCollection
7
7
  extend Forwardable
8
8
 
9
- def_delegators :@collection, :merge, :intersect?
9
+ def_delegators :@collection, :merge, :intersect?, :to_a
10
10
 
11
11
  def initialize(collection)
12
12
  @collection = collection
@@ -30,6 +30,22 @@ module Servactory
30
30
  @collection_mode_class_names = original.collection_mode_class_names.dup
31
31
  @hash_mode_class_names = original.hash_mode_class_names.dup
32
32
  @action_rescue_handlers = original.action_rescue_handlers.dup
33
+
34
+ rebind_dynamic_option_helpers!
35
+ end
36
+
37
+ private
38
+
39
+ def rebind_dynamic_option_helpers!
40
+ new_consists_of = Servactory::ToolKit::DynamicOptions::ConsistsOf
41
+ .use(collection_mode_class_names: @collection_mode_class_names)
42
+ new_schema = Servactory::ToolKit::DynamicOptions::Schema
43
+ .use(default_hash_mode_class_names: @hash_mode_class_names)
44
+
45
+ [@input_option_helpers, @internal_option_helpers, @output_option_helpers].each do |helpers|
46
+ helpers.replace(name: :consists_of, with: new_consists_of)
47
+ helpers.replace(name: :schema, with: new_schema)
48
+ end
33
49
  end
34
50
  end
35
51
  end
@@ -63,11 +63,12 @@ module Servactory
63
63
 
64
64
  def default_input_option_helpers(config)
65
65
  Set[
66
- Servactory::Maintenance::Attributes::OptionHelper
66
+ Servactory::Maintenance::Options::Helper
67
67
  .new(name: :optional, equivalent: { required: false }),
68
68
  Servactory::ToolKit::DynamicOptions::ConsistsOf
69
69
  .use(collection_mode_class_names: config.collection_mode_class_names),
70
- Servactory::ToolKit::DynamicOptions::Schema.use(default_hash_mode_class_names:),
70
+ Servactory::ToolKit::DynamicOptions::Schema
71
+ .use(default_hash_mode_class_names: config.hash_mode_class_names),
71
72
  Servactory::ToolKit::DynamicOptions::Inclusion.use
72
73
  ]
73
74
  end
@@ -76,7 +77,8 @@ module Servactory
76
77
  Set[
77
78
  Servactory::ToolKit::DynamicOptions::ConsistsOf
78
79
  .use(collection_mode_class_names: config.collection_mode_class_names),
79
- Servactory::ToolKit::DynamicOptions::Schema.use(default_hash_mode_class_names:),
80
+ Servactory::ToolKit::DynamicOptions::Schema
81
+ .use(default_hash_mode_class_names: config.hash_mode_class_names),
80
82
  Servactory::ToolKit::DynamicOptions::Inclusion.use
81
83
  ]
82
84
  end
@@ -85,7 +87,8 @@ module Servactory
85
87
  Set[
86
88
  Servactory::ToolKit::DynamicOptions::ConsistsOf
87
89
  .use(collection_mode_class_names: config.collection_mode_class_names),
88
- Servactory::ToolKit::DynamicOptions::Schema.use(default_hash_mode_class_names:),
90
+ Servactory::ToolKit::DynamicOptions::Schema
91
+ .use(default_hash_mode_class_names: config.hash_mode_class_names),
89
92
  Servactory::ToolKit::DynamicOptions::Inclusion.use
90
93
  ]
91
94
  end
@@ -60,15 +60,15 @@ module Servactory
60
60
  end
61
61
 
62
62
  def input_option_helpers(input_option_helpers)
63
- @config.input_option_helpers.merge(input_option_helpers)
63
+ register_option_helpers_with(:input_option_helpers, @config.input_option_helpers, input_option_helpers)
64
64
  end
65
65
 
66
66
  def internal_option_helpers(internal_option_helpers)
67
- @config.internal_option_helpers.merge(internal_option_helpers)
67
+ register_option_helpers_with(:internal_option_helpers, @config.internal_option_helpers, internal_option_helpers)
68
68
  end
69
69
 
70
70
  def output_option_helpers(output_option_helpers)
71
- @config.output_option_helpers.merge(output_option_helpers)
71
+ register_option_helpers_with(:output_option_helpers, @config.output_option_helpers, output_option_helpers)
72
72
  end
73
73
 
74
74
  def collection_mode_class_names(collection_mode_class_names)
@@ -113,6 +113,19 @@ module Servactory
113
113
  value.is_a?(TrueClass) || value.is_a?(FalseClass)
114
114
  end
115
115
 
116
+ def register_option_helpers_with(config_name, collection, option_helpers)
117
+ option_helpers.each do |option_helper|
118
+ case collection.register(option_helper)
119
+ when :reserved
120
+ raise_error_about_reserved_option_helper_with(config_name, option_helper.name)
121
+ when :duplicated
122
+ raise_error_about_duplicated_option_helper_with(config_name, option_helper.name)
123
+ end
124
+ end
125
+
126
+ collection
127
+ end
128
+
116
129
  ##########################################################################
117
130
 
118
131
  def raise_error_about_wrong_exception_class_with(config_name, value)
@@ -142,6 +155,20 @@ module Servactory
142
155
  "The `#{value.inspect}` value must be `TrueClass` or `FalseClass`. " \
143
156
  "See configuration example here: https://servactory.com/guide/configuration"
144
157
  end
158
+
159
+ def raise_error_about_reserved_option_helper_with(config_name, option_helper_name)
160
+ raise ArgumentError,
161
+ "Error in `#{config_name}` configuration. " \
162
+ "The `#{option_helper_name}` option helper name is reserved by a built-in option helper. " \
163
+ "See configuration example here: https://servactory.com/guide/configuration"
164
+ end
165
+
166
+ def raise_error_about_duplicated_option_helper_with(config_name, option_helper_name)
167
+ raise ArgumentError,
168
+ "Error in `#{config_name}` configuration. " \
169
+ "The `#{option_helper_name}` option helper is already defined in this configuration. " \
170
+ "See configuration example here: https://servactory.com/guide/configuration"
171
+ end
145
172
  end
146
173
  end
147
174
  end
@@ -6,7 +6,11 @@ module Servactory
6
6
  class ClassNamesCollection
7
7
  extend Forwardable
8
8
 
9
- def_delegators :@collection, :include?, :intersect?
9
+ def_delegators :@collection,
10
+ :merge,
11
+ :include?,
12
+ :intersect?,
13
+ :to_a
10
14
 
11
15
  def initialize(collection)
12
16
  @collection = collection