light-services 2.2.1 → 3.1.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.
- checksums.yaml +4 -4
- data/.github/config/rubocop_linter_action.yml +4 -4
- data/.github/workflows/ci.yml +12 -12
- data/.gitignore +1 -0
- data/.rubocop.yml +83 -7
- data/CHANGELOG.md +38 -0
- data/CLAUDE.md +139 -0
- data/Gemfile +16 -11
- data/Gemfile.lock +53 -27
- data/README.md +84 -21
- data/docs/arguments.md +290 -0
- data/docs/best-practices.md +153 -0
- data/docs/callbacks.md +476 -0
- data/docs/concepts.md +80 -0
- data/docs/configuration.md +204 -0
- data/docs/context.md +128 -0
- data/docs/crud.md +525 -0
- data/docs/errors.md +280 -0
- data/docs/generators.md +250 -0
- data/docs/outputs.md +158 -0
- data/docs/pundit-authorization.md +320 -0
- data/docs/quickstart.md +134 -0
- data/docs/readme.md +101 -0
- data/docs/recipes.md +14 -0
- data/docs/rubocop.md +285 -0
- data/docs/ruby-lsp.md +133 -0
- data/docs/service-rendering.md +222 -0
- data/docs/steps.md +391 -0
- data/docs/summary.md +21 -0
- data/docs/testing.md +549 -0
- data/lib/generators/light_services/install/USAGE +15 -0
- data/lib/generators/light_services/install/install_generator.rb +41 -0
- data/lib/generators/light_services/install/templates/application_service.rb.tt +8 -0
- data/lib/generators/light_services/install/templates/application_service_spec.rb.tt +7 -0
- data/lib/generators/light_services/install/templates/initializer.rb.tt +30 -0
- data/lib/generators/light_services/service/USAGE +21 -0
- data/lib/generators/light_services/service/service_generator.rb +68 -0
- data/lib/generators/light_services/service/templates/service.rb.tt +48 -0
- data/lib/generators/light_services/service/templates/service_spec.rb.tt +40 -0
- data/lib/light/services/base.rb +134 -122
- data/lib/light/services/base_with_context.rb +23 -1
- data/lib/light/services/callbacks.rb +157 -0
- data/lib/light/services/collection.rb +145 -0
- data/lib/light/services/concerns/execution.rb +79 -0
- data/lib/light/services/concerns/parent_service.rb +34 -0
- data/lib/light/services/concerns/state_management.rb +30 -0
- data/lib/light/services/config.rb +82 -16
- data/lib/light/services/constants.rb +100 -0
- data/lib/light/services/dsl/arguments_dsl.rb +85 -0
- data/lib/light/services/dsl/outputs_dsl.rb +81 -0
- data/lib/light/services/dsl/steps_dsl.rb +205 -0
- data/lib/light/services/dsl/validation.rb +162 -0
- data/lib/light/services/exceptions.rb +25 -2
- data/lib/light/services/message.rb +28 -3
- data/lib/light/services/messages.rb +92 -32
- data/lib/light/services/rspec/matchers/define_argument.rb +174 -0
- data/lib/light/services/rspec/matchers/define_output.rb +147 -0
- data/lib/light/services/rspec/matchers/define_step.rb +225 -0
- data/lib/light/services/rspec/matchers/execute_step.rb +230 -0
- data/lib/light/services/rspec/matchers/have_error_on.rb +148 -0
- data/lib/light/services/rspec/matchers/have_warning_on.rb +148 -0
- data/lib/light/services/rspec/matchers/trigger_callback.rb +138 -0
- data/lib/light/services/rspec.rb +15 -0
- data/lib/light/services/rubocop/cop/light_services/argument_type_required.rb +52 -0
- data/lib/light/services/rubocop/cop/light_services/condition_method_exists.rb +173 -0
- data/lib/light/services/rubocop/cop/light_services/deprecated_methods.rb +113 -0
- data/lib/light/services/rubocop/cop/light_services/dsl_order.rb +176 -0
- data/lib/light/services/rubocop/cop/light_services/missing_private_keyword.rb +102 -0
- data/lib/light/services/rubocop/cop/light_services/no_direct_instantiation.rb +66 -0
- data/lib/light/services/rubocop/cop/light_services/output_type_required.rb +52 -0
- data/lib/light/services/rubocop/cop/light_services/step_method_exists.rb +109 -0
- data/lib/light/services/rubocop.rb +12 -0
- data/lib/light/services/settings/field.rb +114 -0
- data/lib/light/services/settings/step.rb +53 -20
- data/lib/light/services/utils.rb +38 -0
- data/lib/light/services/version.rb +1 -1
- data/lib/light/services.rb +2 -0
- data/lib/ruby_lsp/light_services/addon.rb +36 -0
- data/lib/ruby_lsp/light_services/definition.rb +132 -0
- data/lib/ruby_lsp/light_services/indexing_enhancement.rb +263 -0
- data/light-services.gemspec +6 -8
- metadata +68 -26
- data/lib/light/services/class_based_collection/base.rb +0 -86
- data/lib/light/services/class_based_collection/mount.rb +0 -33
- data/lib/light/services/collection/arguments.rb +0 -34
- data/lib/light/services/collection/base.rb +0 -59
- data/lib/light/services/collection/outputs.rb +0 -16
- data/lib/light/services/settings/argument.rb +0 -68
- data/lib/light/services/settings/output.rb +0 -34
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLsp
|
|
4
|
+
module LightServices
|
|
5
|
+
class IndexingEnhancement < RubyIndexer::Enhancement # rubocop:disable Metrics/ClassLength
|
|
6
|
+
# DSL methods that generate getter, predicate, and setter methods
|
|
7
|
+
FIELD_DSL_METHODS = [:arg, :output].freeze
|
|
8
|
+
|
|
9
|
+
# Default type mappings for common dry-types to their underlying Ruby types
|
|
10
|
+
# These can be extended via Light::Services.config.ruby_lsp_type_mappings
|
|
11
|
+
DEFAULT_TYPE_MAPPINGS = {
|
|
12
|
+
"Types::String" => "String",
|
|
13
|
+
"Types::Strict::String" => "String",
|
|
14
|
+
"Types::Coercible::String" => "String",
|
|
15
|
+
"Types::Integer" => "Integer",
|
|
16
|
+
"Types::Strict::Integer" => "Integer",
|
|
17
|
+
"Types::Coercible::Integer" => "Integer",
|
|
18
|
+
"Types::Float" => "Float",
|
|
19
|
+
"Types::Strict::Float" => "Float",
|
|
20
|
+
"Types::Coercible::Float" => "Float",
|
|
21
|
+
"Types::Decimal" => "BigDecimal",
|
|
22
|
+
"Types::Strict::Decimal" => "BigDecimal",
|
|
23
|
+
"Types::Coercible::Decimal" => "BigDecimal",
|
|
24
|
+
"Types::Bool" => "TrueClass | FalseClass",
|
|
25
|
+
"Types::Strict::Bool" => "TrueClass | FalseClass",
|
|
26
|
+
"Types::True" => "TrueClass",
|
|
27
|
+
"Types::Strict::True" => "TrueClass",
|
|
28
|
+
"Types::False" => "FalseClass",
|
|
29
|
+
"Types::Strict::False" => "FalseClass",
|
|
30
|
+
"Types::Array" => "Array",
|
|
31
|
+
"Types::Strict::Array" => "Array",
|
|
32
|
+
"Types::Hash" => "Hash",
|
|
33
|
+
"Types::Strict::Hash" => "Hash",
|
|
34
|
+
"Types::Symbol" => "Symbol",
|
|
35
|
+
"Types::Strict::Symbol" => "Symbol",
|
|
36
|
+
"Types::Coercible::Symbol" => "Symbol",
|
|
37
|
+
"Types::Date" => "Date",
|
|
38
|
+
"Types::Strict::Date" => "Date",
|
|
39
|
+
"Types::DateTime" => "DateTime",
|
|
40
|
+
"Types::Strict::DateTime" => "DateTime",
|
|
41
|
+
"Types::Time" => "Time",
|
|
42
|
+
"Types::Strict::Time" => "Time",
|
|
43
|
+
"Types::Nil" => "NilClass",
|
|
44
|
+
"Types::Strict::Nil" => "NilClass",
|
|
45
|
+
"Types::Any" => "Object",
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
49
|
+
# Public API - Called by Ruby LSP indexer
|
|
50
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
# Called when the indexer encounters a method call node
|
|
53
|
+
# Detects `arg` and `output` DSL calls and registers the generated methods
|
|
54
|
+
def on_call_node_enter(node)
|
|
55
|
+
return unless @listener.current_owner
|
|
56
|
+
return unless FIELD_DSL_METHODS.include?(node.name)
|
|
57
|
+
|
|
58
|
+
field_name = extract_field_name(node)
|
|
59
|
+
return unless field_name
|
|
60
|
+
|
|
61
|
+
ruby_type = extract_ruby_type(node)
|
|
62
|
+
register_field_methods(field_name, node.location, ruby_type)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def on_call_node_leave(node); end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
70
|
+
# Field Extraction
|
|
71
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
# Extract the field name from the first argument (symbol)
|
|
74
|
+
# Example: `arg :user` → "user"
|
|
75
|
+
def extract_field_name(node)
|
|
76
|
+
arguments = node.arguments&.arguments
|
|
77
|
+
return unless arguments&.any?
|
|
78
|
+
|
|
79
|
+
first_arg = arguments.first
|
|
80
|
+
return unless first_arg.is_a?(Prism::SymbolNode)
|
|
81
|
+
|
|
82
|
+
first_arg.value
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
86
|
+
# Type Resolution
|
|
87
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
# Extract and resolve the Ruby type from the `type:` keyword argument
|
|
90
|
+
# Returns the mapped Ruby type string or the original type if no mapping exists
|
|
91
|
+
def extract_ruby_type(node)
|
|
92
|
+
type_node = find_type_value_node(node)
|
|
93
|
+
return unless type_node
|
|
94
|
+
|
|
95
|
+
resolve_to_ruby_type(type_node)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Find the value node for the `type:` keyword argument
|
|
99
|
+
def find_type_value_node(node)
|
|
100
|
+
arguments = node.arguments&.arguments
|
|
101
|
+
return unless arguments
|
|
102
|
+
|
|
103
|
+
keyword_hash = arguments.find { |arg| arg.is_a?(Prism::KeywordHashNode) }
|
|
104
|
+
return unless keyword_hash
|
|
105
|
+
|
|
106
|
+
# NOTE: Prism's SymbolNode#value returns a String, not a Symbol
|
|
107
|
+
type_element = keyword_hash.elements.find do |element|
|
|
108
|
+
element.is_a?(Prism::AssocNode) &&
|
|
109
|
+
element.key.is_a?(Prism::SymbolNode) &&
|
|
110
|
+
element.key.value == "type"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
type_element&.value
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Resolve a Prism type node to a Ruby type string
|
|
117
|
+
# Handles constants, constant paths, and method chains
|
|
118
|
+
def resolve_to_ruby_type(node)
|
|
119
|
+
type_string = node_to_constant_string(node)
|
|
120
|
+
return unless type_string
|
|
121
|
+
|
|
122
|
+
map_to_ruby_type(type_string) || type_string
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Convert a Prism node to its constant string representation
|
|
126
|
+
def node_to_constant_string(node)
|
|
127
|
+
case node
|
|
128
|
+
when Prism::ConstantReadNode
|
|
129
|
+
node.name.to_s
|
|
130
|
+
when Prism::ConstantPathNode
|
|
131
|
+
build_constant_path(node)
|
|
132
|
+
when Prism::CallNode
|
|
133
|
+
# Handle method chains like Types::String.constrained(...) or Types::Array.of(...)
|
|
134
|
+
extract_receiver_constant(node)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Build a full constant path string from nested ConstantPathNodes
|
|
139
|
+
# Example: Types::Strict::String
|
|
140
|
+
def build_constant_path(node)
|
|
141
|
+
parts = []
|
|
142
|
+
current = node
|
|
143
|
+
|
|
144
|
+
while current.is_a?(Prism::ConstantPathNode)
|
|
145
|
+
parts.unshift(current.name.to_s)
|
|
146
|
+
current = current.parent
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
parts.unshift(current.name.to_s) if current.is_a?(Prism::ConstantReadNode)
|
|
150
|
+
parts.join("::")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Extract the receiver constant from a method call chain
|
|
154
|
+
# Example: Types::String.constrained(format: /@/) → "Types::String"
|
|
155
|
+
def extract_receiver_constant(node)
|
|
156
|
+
receiver = node.receiver
|
|
157
|
+
return unless receiver
|
|
158
|
+
|
|
159
|
+
case receiver
|
|
160
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
|
161
|
+
node_to_constant_string(receiver)
|
|
162
|
+
when Prism::CallNode
|
|
163
|
+
extract_receiver_constant(receiver)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
168
|
+
# Type Mapping
|
|
169
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
# Map a type string to its corresponding Ruby type
|
|
172
|
+
# Uses custom mappings from config (if available) merged with defaults
|
|
173
|
+
def map_to_ruby_type(type_string)
|
|
174
|
+
mappings = effective_type_mappings
|
|
175
|
+
|
|
176
|
+
# Direct mapping lookup (custom mappings take precedence)
|
|
177
|
+
return mappings[type_string] if mappings.key?(type_string)
|
|
178
|
+
|
|
179
|
+
# Handle parameterized types: Types::Array.of(...) → Types::Array
|
|
180
|
+
base_type = type_string.split(".").first
|
|
181
|
+
mappings[base_type]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Returns the effective type mappings (defaults + custom from config)
|
|
185
|
+
# Custom mappings take precedence over defaults
|
|
186
|
+
def effective_type_mappings
|
|
187
|
+
return DEFAULT_TYPE_MAPPINGS unless defined?(Light::Services)
|
|
188
|
+
return DEFAULT_TYPE_MAPPINGS unless Light::Services.respond_to?(:config)
|
|
189
|
+
|
|
190
|
+
custom_mappings = Light::Services.config&.ruby_lsp_type_mappings
|
|
191
|
+
return DEFAULT_TYPE_MAPPINGS if custom_mappings.nil? || custom_mappings.empty?
|
|
192
|
+
|
|
193
|
+
DEFAULT_TYPE_MAPPINGS.merge(custom_mappings)
|
|
194
|
+
rescue NoMethodError
|
|
195
|
+
DEFAULT_TYPE_MAPPINGS
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
199
|
+
# Method Registration
|
|
200
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
# Register all three generated methods for a field (getter, predicate, setter)
|
|
203
|
+
def register_field_methods(field_name, location, ruby_type)
|
|
204
|
+
register_getter(field_name, location, ruby_type)
|
|
205
|
+
register_predicate(field_name, location)
|
|
206
|
+
register_setter(field_name, location, ruby_type)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def register_getter(field_name, location, ruby_type)
|
|
210
|
+
@listener.add_method(
|
|
211
|
+
field_name.to_s,
|
|
212
|
+
location,
|
|
213
|
+
no_params_signature,
|
|
214
|
+
comments: return_type_comment(ruby_type),
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def register_predicate(field_name, location)
|
|
219
|
+
@listener.add_method(
|
|
220
|
+
"#{field_name}?",
|
|
221
|
+
location,
|
|
222
|
+
no_params_signature,
|
|
223
|
+
comments: "@return [Boolean]",
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def register_setter(field_name, location, ruby_type)
|
|
228
|
+
@listener.add_method(
|
|
229
|
+
"#{field_name}=",
|
|
230
|
+
location,
|
|
231
|
+
value_param_signature,
|
|
232
|
+
comments: setter_comment(ruby_type),
|
|
233
|
+
)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
237
|
+
# Signatures & Comments
|
|
238
|
+
# ─────────────────────────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
def no_params_signature
|
|
241
|
+
[RubyIndexer::Entry::Signature.new([])]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def value_param_signature
|
|
245
|
+
[RubyIndexer::Entry::Signature.new([
|
|
246
|
+
RubyIndexer::Entry::RequiredParameter.new(name: :value),
|
|
247
|
+
])]
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def return_type_comment(ruby_type)
|
|
251
|
+
return nil unless ruby_type
|
|
252
|
+
|
|
253
|
+
"@return [#{ruby_type}]"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def setter_comment(ruby_type)
|
|
257
|
+
return "@param value the value to set" unless ruby_type
|
|
258
|
+
|
|
259
|
+
"@param value [#{ruby_type}] the value to set\n@return [#{ruby_type}]"
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
data/light-services.gemspec
CHANGED
|
@@ -8,11 +8,11 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ["Andrew Kodkod"]
|
|
9
9
|
spec.email = ["andrew@kodkod.me"]
|
|
10
10
|
|
|
11
|
-
spec.summary = "Robust service architecture for Ruby
|
|
12
|
-
spec.description = "
|
|
11
|
+
spec.summary = "Robust service architecture for Ruby/Rails applications"
|
|
12
|
+
spec.description = "Light Services is a simple yet powerful way to organize business logic in Ruby applications. Build services that are easy to test, maintain, and understand." # rubocop:disable Layout/LineLength
|
|
13
13
|
spec.homepage = "https://light-services-docs.vercel.app/"
|
|
14
14
|
spec.license = "MIT"
|
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new(">=
|
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0")
|
|
16
16
|
|
|
17
17
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
18
18
|
|
|
@@ -26,10 +26,8 @@ Gem::Specification.new do |spec|
|
|
|
26
26
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
spec.bindir
|
|
30
|
-
spec.executables
|
|
31
|
-
spec.require_paths
|
|
29
|
+
spec.bindir = "exe"
|
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
31
|
+
spec.require_paths = ["lib"]
|
|
32
32
|
spec.metadata["rubygems_mfa_required"] = "true"
|
|
33
|
-
|
|
34
|
-
spec.add_dependency "benchmark", ">= 0.5"
|
|
35
33
|
end
|
metadata
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: light-services
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kodkod
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.5'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.5'
|
|
26
|
-
description: Robust service architecture for Ruby frameworks
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Light Services is a simple yet powerful way to organize business logic
|
|
13
|
+
in Ruby applications. Build services that are easy to test, maintain, and understand.
|
|
27
14
|
email:
|
|
28
15
|
- andrew@kodkod.me
|
|
29
16
|
executables: []
|
|
@@ -38,6 +25,7 @@ files:
|
|
|
38
25
|
- ".rubocop.yml"
|
|
39
26
|
- ".ruby-version"
|
|
40
27
|
- CHANGELOG.md
|
|
28
|
+
- CLAUDE.md
|
|
41
29
|
- CODE_OF_CONDUCT.md
|
|
42
30
|
- Gemfile
|
|
43
31
|
- Gemfile.lock
|
|
@@ -46,22 +34,76 @@ files:
|
|
|
46
34
|
- Rakefile
|
|
47
35
|
- bin/console
|
|
48
36
|
- bin/setup
|
|
37
|
+
- docs/arguments.md
|
|
38
|
+
- docs/best-practices.md
|
|
39
|
+
- docs/callbacks.md
|
|
40
|
+
- docs/concepts.md
|
|
41
|
+
- docs/configuration.md
|
|
42
|
+
- docs/context.md
|
|
43
|
+
- docs/crud.md
|
|
44
|
+
- docs/errors.md
|
|
45
|
+
- docs/generators.md
|
|
46
|
+
- docs/outputs.md
|
|
47
|
+
- docs/pundit-authorization.md
|
|
48
|
+
- docs/quickstart.md
|
|
49
|
+
- docs/readme.md
|
|
50
|
+
- docs/recipes.md
|
|
51
|
+
- docs/rubocop.md
|
|
52
|
+
- docs/ruby-lsp.md
|
|
53
|
+
- docs/service-rendering.md
|
|
54
|
+
- docs/steps.md
|
|
55
|
+
- docs/summary.md
|
|
56
|
+
- docs/testing.md
|
|
57
|
+
- lib/generators/light_services/install/USAGE
|
|
58
|
+
- lib/generators/light_services/install/install_generator.rb
|
|
59
|
+
- lib/generators/light_services/install/templates/application_service.rb.tt
|
|
60
|
+
- lib/generators/light_services/install/templates/application_service_spec.rb.tt
|
|
61
|
+
- lib/generators/light_services/install/templates/initializer.rb.tt
|
|
62
|
+
- lib/generators/light_services/service/USAGE
|
|
63
|
+
- lib/generators/light_services/service/service_generator.rb
|
|
64
|
+
- lib/generators/light_services/service/templates/service.rb.tt
|
|
65
|
+
- lib/generators/light_services/service/templates/service_spec.rb.tt
|
|
49
66
|
- lib/light/services.rb
|
|
50
67
|
- lib/light/services/base.rb
|
|
51
68
|
- lib/light/services/base_with_context.rb
|
|
52
|
-
- lib/light/services/
|
|
53
|
-
- lib/light/services/
|
|
54
|
-
- lib/light/services/
|
|
55
|
-
- lib/light/services/
|
|
56
|
-
- lib/light/services/
|
|
69
|
+
- lib/light/services/callbacks.rb
|
|
70
|
+
- lib/light/services/collection.rb
|
|
71
|
+
- lib/light/services/concerns/execution.rb
|
|
72
|
+
- lib/light/services/concerns/parent_service.rb
|
|
73
|
+
- lib/light/services/concerns/state_management.rb
|
|
57
74
|
- lib/light/services/config.rb
|
|
75
|
+
- lib/light/services/constants.rb
|
|
76
|
+
- lib/light/services/dsl/arguments_dsl.rb
|
|
77
|
+
- lib/light/services/dsl/outputs_dsl.rb
|
|
78
|
+
- lib/light/services/dsl/steps_dsl.rb
|
|
79
|
+
- lib/light/services/dsl/validation.rb
|
|
58
80
|
- lib/light/services/exceptions.rb
|
|
59
81
|
- lib/light/services/message.rb
|
|
60
82
|
- lib/light/services/messages.rb
|
|
61
|
-
- lib/light/services/
|
|
62
|
-
- lib/light/services/
|
|
83
|
+
- lib/light/services/rspec.rb
|
|
84
|
+
- lib/light/services/rspec/matchers/define_argument.rb
|
|
85
|
+
- lib/light/services/rspec/matchers/define_output.rb
|
|
86
|
+
- lib/light/services/rspec/matchers/define_step.rb
|
|
87
|
+
- lib/light/services/rspec/matchers/execute_step.rb
|
|
88
|
+
- lib/light/services/rspec/matchers/have_error_on.rb
|
|
89
|
+
- lib/light/services/rspec/matchers/have_warning_on.rb
|
|
90
|
+
- lib/light/services/rspec/matchers/trigger_callback.rb
|
|
91
|
+
- lib/light/services/rubocop.rb
|
|
92
|
+
- lib/light/services/rubocop/cop/light_services/argument_type_required.rb
|
|
93
|
+
- lib/light/services/rubocop/cop/light_services/condition_method_exists.rb
|
|
94
|
+
- lib/light/services/rubocop/cop/light_services/deprecated_methods.rb
|
|
95
|
+
- lib/light/services/rubocop/cop/light_services/dsl_order.rb
|
|
96
|
+
- lib/light/services/rubocop/cop/light_services/missing_private_keyword.rb
|
|
97
|
+
- lib/light/services/rubocop/cop/light_services/no_direct_instantiation.rb
|
|
98
|
+
- lib/light/services/rubocop/cop/light_services/output_type_required.rb
|
|
99
|
+
- lib/light/services/rubocop/cop/light_services/step_method_exists.rb
|
|
100
|
+
- lib/light/services/settings/field.rb
|
|
63
101
|
- lib/light/services/settings/step.rb
|
|
102
|
+
- lib/light/services/utils.rb
|
|
64
103
|
- lib/light/services/version.rb
|
|
104
|
+
- lib/ruby_lsp/light_services/addon.rb
|
|
105
|
+
- lib/ruby_lsp/light_services/definition.rb
|
|
106
|
+
- lib/ruby_lsp/light_services/indexing_enhancement.rb
|
|
65
107
|
- light-services.gemspec
|
|
66
108
|
homepage: https://light-services-docs.vercel.app/
|
|
67
109
|
licenses:
|
|
@@ -79,7 +121,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
79
121
|
requirements:
|
|
80
122
|
- - ">="
|
|
81
123
|
- !ruby/object:Gem::Version
|
|
82
|
-
version:
|
|
124
|
+
version: 3.0.0
|
|
83
125
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
126
|
requirements:
|
|
85
127
|
- - ">="
|
|
@@ -88,5 +130,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
88
130
|
requirements: []
|
|
89
131
|
rubygems_version: 3.6.9
|
|
90
132
|
specification_version: 4
|
|
91
|
-
summary: Robust service architecture for Ruby
|
|
133
|
+
summary: Robust service architecture for Ruby/Rails applications
|
|
92
134
|
test_files: []
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Create class based collections for storing arguments settings, steps settings and outputs settings
|
|
4
|
-
#
|
|
5
|
-
# General functionality:
|
|
6
|
-
# 1. Collection automatically loads data from parent classes
|
|
7
|
-
# 2. It's possible to redefine items if needed (e.g. arguments)
|
|
8
|
-
# 3. We can add items into collection after or before another items
|
|
9
|
-
#
|
|
10
|
-
module Light
|
|
11
|
-
module Services
|
|
12
|
-
module ClassBasedCollection
|
|
13
|
-
class Base
|
|
14
|
-
# TODO: Add `prepend: true`
|
|
15
|
-
def initialize(item_class, allow_redefine)
|
|
16
|
-
@item_class = item_class
|
|
17
|
-
@allow_redefine = allow_redefine
|
|
18
|
-
@collection = {}
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def add(klass, name, opts = {})
|
|
22
|
-
@collection[klass] ||= all_from_superclass(klass)
|
|
23
|
-
|
|
24
|
-
validate_name!(klass, name)
|
|
25
|
-
validate_opts!(klass, name, opts)
|
|
26
|
-
|
|
27
|
-
item = @item_class.new(name, klass, opts)
|
|
28
|
-
|
|
29
|
-
if opts[:before] || opts[:after]
|
|
30
|
-
insert_item(klass, name, opts, item)
|
|
31
|
-
else
|
|
32
|
-
@collection[klass][name] = item
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def find_index(klass, name)
|
|
37
|
-
index = @collection[klass].keys.index(name)
|
|
38
|
-
|
|
39
|
-
return index if index
|
|
40
|
-
|
|
41
|
-
# TODO: Update `NoStepError` because it maybe not only step
|
|
42
|
-
raise Light::Services::NoStepError, "Cannot find #{@item_class} `#{name}` in service #{klass}"
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def remove(klass, name)
|
|
46
|
-
@collection[klass] ||= all_from_superclass(klass)
|
|
47
|
-
@collection[klass].delete(name)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def all(klass)
|
|
51
|
-
@collection[klass] || all_from_superclass(klass)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
private
|
|
55
|
-
|
|
56
|
-
def all_from_superclass(klass)
|
|
57
|
-
if klass.superclass <= Light::Services::Base
|
|
58
|
-
all(klass.superclass).dup
|
|
59
|
-
else
|
|
60
|
-
{}
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def validate_name!(klass, name)
|
|
65
|
-
if !@allow_redefine && all(klass).key?(name)
|
|
66
|
-
raise Light::Services::Error, "#{@item_class} with name `#{name}` already exists in service #{klass}"
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def validate_opts!(klass, name, opts)
|
|
71
|
-
if opts[:before] && opts[:after]
|
|
72
|
-
raise Light::Services::Error, "You cannot specify `before` and `after` " \
|
|
73
|
-
"for #{@item_class} `#{name}` in service #{klass} at the same time"
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def insert_item(klass, name, opts, item)
|
|
78
|
-
index = find_index(klass, opts[:before] || opts[:after])
|
|
79
|
-
index += 1 unless opts[:before]
|
|
80
|
-
|
|
81
|
-
@collection[klass] = @collection[klass].to_a.insert(index, [name, item]).to_h
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# This class allows to mount class based collections to service objects
|
|
4
|
-
#
|
|
5
|
-
# Usage:
|
|
6
|
-
#
|
|
7
|
-
# mount_class_based_collection :steps, klass: Settings::Step, shortcut: :step
|
|
8
|
-
# mount_class_based_collection :outputs, klass: Settings::Output, shortcut: :output
|
|
9
|
-
# mount_class_based_collection :arguments, klass: Settings::Argument, shortcut: :arg, allow_redefine: true
|
|
10
|
-
#
|
|
11
|
-
module Light
|
|
12
|
-
module Services
|
|
13
|
-
module ClassBasedCollection
|
|
14
|
-
module Mount
|
|
15
|
-
def mount_class_based_collection(collection_name, item_class:, shortcut:, allow_redefine: false)
|
|
16
|
-
class_variable_set(:"@@#{collection_name}", ClassBasedCollection::Base.new(item_class, allow_redefine))
|
|
17
|
-
|
|
18
|
-
define_singleton_method shortcut do |item_name, opts = {}|
|
|
19
|
-
class_variable_get(:"@@#{collection_name}").add(self, item_name, opts)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
define_singleton_method :"remove_#{shortcut}" do |item_name|
|
|
23
|
-
class_variable_get(:"@@#{collection_name}").remove(self, item_name)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
define_singleton_method collection_name do
|
|
27
|
-
class_variable_get(:"@@#{collection_name}").all(self)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Collection to store, merge and validate arguments
|
|
4
|
-
module Light
|
|
5
|
-
module Services
|
|
6
|
-
module Collection
|
|
7
|
-
class Arguments < Base
|
|
8
|
-
def extend_with_context(args)
|
|
9
|
-
settings_collection.each do |name, settings|
|
|
10
|
-
next if !settings.context || args.key?(name) || !key?(name)
|
|
11
|
-
|
|
12
|
-
args[settings.name] = get(name)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
args
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def validate!
|
|
19
|
-
settings_collection.each do |name, settings|
|
|
20
|
-
next if settings.optional && (!key?(name) || get(name).nil?)
|
|
21
|
-
|
|
22
|
-
settings.validate_type!(get(name))
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
|
|
28
|
-
def settings_collection
|
|
29
|
-
@instance.class.arguments
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Collection to store arguments and outputs values
|
|
4
|
-
module Light
|
|
5
|
-
module Services
|
|
6
|
-
module Collection
|
|
7
|
-
class Base
|
|
8
|
-
# Includes
|
|
9
|
-
extend Forwardable
|
|
10
|
-
|
|
11
|
-
# Settings
|
|
12
|
-
def_delegators :@storage, :key?, :to_h
|
|
13
|
-
|
|
14
|
-
def initialize(instance, storage = {})
|
|
15
|
-
@instance = instance
|
|
16
|
-
@storage = storage
|
|
17
|
-
|
|
18
|
-
return if storage.is_a?(Hash)
|
|
19
|
-
|
|
20
|
-
raise Light::Services::ArgTypeError, "#{instance.class} - arguments must be a Hash"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def set(key, value)
|
|
24
|
-
@storage[key] = value
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def get(key)
|
|
28
|
-
@storage[key]
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def [](key)
|
|
32
|
-
get(key)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def []=(key, value)
|
|
36
|
-
set(key, value)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def load_defaults
|
|
40
|
-
settings_collection.each do |name, settings|
|
|
41
|
-
next if !settings.default_exists || key?(name)
|
|
42
|
-
|
|
43
|
-
if settings.default.is_a?(Proc)
|
|
44
|
-
set(name, @instance.instance_exec(&settings.default))
|
|
45
|
-
else
|
|
46
|
-
set(name, deep_dup(settings.default))
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
|
|
53
|
-
def deep_dup(object)
|
|
54
|
-
Marshal.load(Marshal.dump(object))
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|