light-services 2.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/config/rubocop_linter_action.yml +4 -4
- data/.github/workflows/ci.yml +12 -12
- data/.gitignore +5 -0
- data/.rubocop.yml +77 -7
- data/CHANGELOG.md +23 -0
- data/CLAUDE.md +139 -0
- data/Gemfile +16 -11
- data/Gemfile.lock +53 -27
- data/README.md +76 -13
- data/docs/arguments.md +267 -0
- data/docs/best-practices.md +153 -0
- data/docs/callbacks.md +476 -0
- data/docs/concepts.md +80 -0
- data/docs/configuration.md +168 -0
- data/docs/context.md +128 -0
- data/docs/crud.md +525 -0
- data/docs/errors.md +250 -0
- data/docs/generators.md +250 -0
- data/docs/outputs.md +135 -0
- data/docs/pundit-authorization.md +320 -0
- data/docs/quickstart.md +134 -0
- data/docs/readme.md +100 -0
- data/docs/recipes.md +14 -0
- data/docs/service-rendering.md +222 -0
- data/docs/steps.md +337 -0
- data/docs/summary.md +19 -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 +24 -114
- data/lib/light/services/base_with_context.rb +2 -3
- data/lib/light/services/callbacks.rb +103 -0
- data/lib/light/services/collection.rb +97 -0
- data/lib/light/services/concerns/execution.rb +76 -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 +4 -18
- data/lib/light/services/constants.rb +97 -0
- data/lib/light/services/dsl/arguments_dsl.rb +84 -0
- data/lib/light/services/dsl/outputs_dsl.rb +80 -0
- data/lib/light/services/dsl/steps_dsl.rb +205 -0
- data/lib/light/services/dsl/validation.rb +132 -0
- data/lib/light/services/exceptions.rb +7 -2
- data/lib/light/services/messages.rb +19 -31
- 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/settings/field.rb +86 -0
- data/lib/light/services/settings/step.rb +31 -16
- data/lib/light/services/utils.rb +38 -0
- data/lib/light/services/version.rb +1 -1
- data/lib/light/services.rb +2 -0
- data/light-services.gemspec +6 -8
- metadata +54 -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,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Unified settings class for arguments and outputs
|
|
4
|
+
module Light
|
|
5
|
+
module Services
|
|
6
|
+
module Settings
|
|
7
|
+
class Field
|
|
8
|
+
attr_reader :name, :default_exists, :default, :context, :optional
|
|
9
|
+
|
|
10
|
+
def initialize(name, service_class, opts = {})
|
|
11
|
+
@name = name
|
|
12
|
+
@service_class = service_class
|
|
13
|
+
@field_type = opts.delete(:field_type) || :argument
|
|
14
|
+
|
|
15
|
+
@type = opts.delete(:type)
|
|
16
|
+
@context = opts.delete(:context)
|
|
17
|
+
@default_exists = opts.key?(:default)
|
|
18
|
+
@default = opts.delete(:default)
|
|
19
|
+
@optional = opts.delete(:optional)
|
|
20
|
+
|
|
21
|
+
define_methods
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Validate and optionally coerce the value
|
|
25
|
+
# Returns the (possibly coerced) value
|
|
26
|
+
def validate_type!(value)
|
|
27
|
+
return value unless @type
|
|
28
|
+
|
|
29
|
+
if dry_type?(@type)
|
|
30
|
+
coerce_and_validate_dry_type!(value)
|
|
31
|
+
else
|
|
32
|
+
validate_ruby_type!(value)
|
|
33
|
+
value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# Check if the type is a dry-types type
|
|
40
|
+
def dry_type?(type)
|
|
41
|
+
return false unless defined?(Dry::Types::Type)
|
|
42
|
+
|
|
43
|
+
type.is_a?(Dry::Types::Type)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Validate and coerce value against dry-types
|
|
47
|
+
# Returns the coerced value
|
|
48
|
+
def coerce_and_validate_dry_type!(value)
|
|
49
|
+
@type[value]
|
|
50
|
+
rescue Dry::Types::ConstraintError, Dry::Types::CoercionError => e
|
|
51
|
+
raise Light::Services::ArgTypeError,
|
|
52
|
+
"#{@service_class} #{@field_type} `#{@name}` #{e.message}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Validate value against Ruby class types
|
|
56
|
+
def validate_ruby_type!(value)
|
|
57
|
+
return if [*@type].any? { |type| value.is_a?(type) }
|
|
58
|
+
|
|
59
|
+
raise Light::Services::ArgTypeError, type_error_message(value)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def type_error_message(value)
|
|
63
|
+
expected_types = [*@type].map(&:to_s).join(" or ")
|
|
64
|
+
"#{@service_class} #{@field_type} `#{@name}` must be #{expected_types}, \" \\
|
|
65
|
+
\"but got #{value.class} with value: #{value.inspect}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def define_methods
|
|
69
|
+
name = @name
|
|
70
|
+
collection_instance_var = :"@#{@field_type}s"
|
|
71
|
+
|
|
72
|
+
@service_class.define_method(@name) { instance_variable_get(collection_instance_var).get(name) }
|
|
73
|
+
@service_class.define_method(:"#{@name}?") { !!instance_variable_get(collection_instance_var).get(name) }
|
|
74
|
+
@service_class.define_method(:"#{@name}=") do |value|
|
|
75
|
+
instance_variable_get(collection_instance_var).set(name, value)
|
|
76
|
+
end
|
|
77
|
+
@service_class.send(:private, "#{@name}=")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Aliases for backwards compatibility
|
|
82
|
+
Argument = Field
|
|
83
|
+
Output = Field
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -17,33 +17,48 @@ module Light
|
|
|
17
17
|
@always = opts[:always]
|
|
18
18
|
|
|
19
19
|
if @if && @unless
|
|
20
|
-
raise Light::Services::
|
|
21
|
-
|
|
20
|
+
raise Light::Services::Error, "#{service_class} `if` and `unless` cannot be specified " \
|
|
21
|
+
"for the step `#{name}` at the same time"
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def run(instance
|
|
25
|
+
def run(instance) # rubocop:disable Naming/PredicateMethod
|
|
26
26
|
return false unless run?(instance)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
unless instance.respond_to?(name, true)
|
|
29
|
+
available_steps = @service_class.steps.keys.join(", ")
|
|
30
|
+
raise Light::Services::Error,
|
|
31
|
+
"Step method `#{name}` is not defined in #{@service_class}. " \
|
|
32
|
+
"Defined steps: [#{available_steps}]"
|
|
33
|
+
end
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
end
|
|
35
|
+
execute_with_callbacks(instance)
|
|
36
|
+
true
|
|
37
|
+
end
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def execute_with_callbacks(instance)
|
|
42
|
+
errors_count_before = instance.errors.count
|
|
43
|
+
|
|
44
|
+
instance.run_callbacks(:before_step_run, instance, name)
|
|
45
|
+
|
|
46
|
+
instance.run_callbacks(:around_step_run, instance, name) do
|
|
47
|
+
instance.send(name)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
instance.run_callbacks(:after_step_run, instance, name)
|
|
51
|
+
|
|
52
|
+
if instance.errors.count > errors_count_before
|
|
53
|
+
instance.run_callbacks(:on_step_failure, instance, name)
|
|
40
54
|
else
|
|
41
|
-
|
|
55
|
+
instance.run_callbacks(:on_step_success, instance, name)
|
|
42
56
|
end
|
|
57
|
+
rescue StandardError => e
|
|
58
|
+
instance.run_callbacks(:on_step_crash, instance, name, e)
|
|
59
|
+
raise e
|
|
43
60
|
end
|
|
44
61
|
|
|
45
|
-
private
|
|
46
|
-
|
|
47
62
|
def run?(instance)
|
|
48
63
|
return false if instance.done?
|
|
49
64
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Light
|
|
4
|
+
module Services
|
|
5
|
+
# Utility module providing helper methods for the Light Services library
|
|
6
|
+
module Utils
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# Creates a deep copy of an object to prevent mutation of shared references.
|
|
10
|
+
#
|
|
11
|
+
# @param object [Object] the object to duplicate
|
|
12
|
+
# @return [Object] a deep copy of the object
|
|
13
|
+
#
|
|
14
|
+
# @example Deep duping a hash
|
|
15
|
+
# original = { a: { b: 1 } }
|
|
16
|
+
# copy = Utils.deep_dup(original)
|
|
17
|
+
# copy[:a][:b] = 2
|
|
18
|
+
# original[:a][:b] # => 1
|
|
19
|
+
#
|
|
20
|
+
# @example Deep duping an array
|
|
21
|
+
# original = [[1, 2], [3, 4]]
|
|
22
|
+
# copy = Utils.deep_dup(original)
|
|
23
|
+
# copy[0] << 5
|
|
24
|
+
# original[0] # => [1, 2]
|
|
25
|
+
#
|
|
26
|
+
def deep_dup(object)
|
|
27
|
+
# Use ActiveSupport's deep_dup if available (preferred for Rails apps)
|
|
28
|
+
return object.deep_dup if object.respond_to?(:deep_dup)
|
|
29
|
+
|
|
30
|
+
# Fallback to Marshal for objects that support serialization
|
|
31
|
+
Marshal.load(Marshal.dump(object))
|
|
32
|
+
rescue TypeError
|
|
33
|
+
# Last resort: use dup if available, otherwise return original
|
|
34
|
+
object.respond_to?(:dup) ? object.dup : object
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/light/services.rb
CHANGED
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.0.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,21 +34,61 @@ 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/service-rendering.md
|
|
52
|
+
- docs/steps.md
|
|
53
|
+
- docs/summary.md
|
|
54
|
+
- docs/testing.md
|
|
55
|
+
- lib/generators/light_services/install/USAGE
|
|
56
|
+
- lib/generators/light_services/install/install_generator.rb
|
|
57
|
+
- lib/generators/light_services/install/templates/application_service.rb.tt
|
|
58
|
+
- lib/generators/light_services/install/templates/application_service_spec.rb.tt
|
|
59
|
+
- lib/generators/light_services/install/templates/initializer.rb.tt
|
|
60
|
+
- lib/generators/light_services/service/USAGE
|
|
61
|
+
- lib/generators/light_services/service/service_generator.rb
|
|
62
|
+
- lib/generators/light_services/service/templates/service.rb.tt
|
|
63
|
+
- lib/generators/light_services/service/templates/service_spec.rb.tt
|
|
49
64
|
- lib/light/services.rb
|
|
50
65
|
- lib/light/services/base.rb
|
|
51
66
|
- 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/
|
|
67
|
+
- lib/light/services/callbacks.rb
|
|
68
|
+
- lib/light/services/collection.rb
|
|
69
|
+
- lib/light/services/concerns/execution.rb
|
|
70
|
+
- lib/light/services/concerns/parent_service.rb
|
|
71
|
+
- lib/light/services/concerns/state_management.rb
|
|
57
72
|
- lib/light/services/config.rb
|
|
73
|
+
- lib/light/services/constants.rb
|
|
74
|
+
- lib/light/services/dsl/arguments_dsl.rb
|
|
75
|
+
- lib/light/services/dsl/outputs_dsl.rb
|
|
76
|
+
- lib/light/services/dsl/steps_dsl.rb
|
|
77
|
+
- lib/light/services/dsl/validation.rb
|
|
58
78
|
- lib/light/services/exceptions.rb
|
|
59
79
|
- lib/light/services/message.rb
|
|
60
80
|
- lib/light/services/messages.rb
|
|
61
|
-
- lib/light/services/
|
|
62
|
-
- lib/light/services/
|
|
81
|
+
- lib/light/services/rspec.rb
|
|
82
|
+
- lib/light/services/rspec/matchers/define_argument.rb
|
|
83
|
+
- lib/light/services/rspec/matchers/define_output.rb
|
|
84
|
+
- lib/light/services/rspec/matchers/define_step.rb
|
|
85
|
+
- lib/light/services/rspec/matchers/execute_step.rb
|
|
86
|
+
- lib/light/services/rspec/matchers/have_error_on.rb
|
|
87
|
+
- lib/light/services/rspec/matchers/have_warning_on.rb
|
|
88
|
+
- lib/light/services/rspec/matchers/trigger_callback.rb
|
|
89
|
+
- lib/light/services/settings/field.rb
|
|
63
90
|
- lib/light/services/settings/step.rb
|
|
91
|
+
- lib/light/services/utils.rb
|
|
64
92
|
- lib/light/services/version.rb
|
|
65
93
|
- light-services.gemspec
|
|
66
94
|
homepage: https://light-services-docs.vercel.app/
|
|
@@ -79,7 +107,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
79
107
|
requirements:
|
|
80
108
|
- - ">="
|
|
81
109
|
- !ruby/object:Gem::Version
|
|
82
|
-
version:
|
|
110
|
+
version: 3.0.0
|
|
83
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
112
|
requirements:
|
|
85
113
|
- - ">="
|
|
@@ -88,5 +116,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
88
116
|
requirements: []
|
|
89
117
|
rubygems_version: 3.6.9
|
|
90
118
|
specification_version: 4
|
|
91
|
-
summary: Robust service architecture for Ruby
|
|
119
|
+
summary: Robust service architecture for Ruby/Rails applications
|
|
92
120
|
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
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# This class defines settings for argument
|
|
4
|
-
module Light
|
|
5
|
-
module Services
|
|
6
|
-
module Settings
|
|
7
|
-
class Argument
|
|
8
|
-
# Getters
|
|
9
|
-
attr_reader :name, :default_exists, :default, :context, :optional, :arg_types_cache
|
|
10
|
-
|
|
11
|
-
def initialize(name, service_class, opts = {})
|
|
12
|
-
@name = name
|
|
13
|
-
@service_class = service_class
|
|
14
|
-
|
|
15
|
-
@type = opts.delete(:type)
|
|
16
|
-
@context = opts.delete(:context)
|
|
17
|
-
@default_exists = opts.key?(:default)
|
|
18
|
-
@default = opts.delete(:default)
|
|
19
|
-
@optional = opts.delete(:optional)
|
|
20
|
-
|
|
21
|
-
@arg_types_cache = {}
|
|
22
|
-
|
|
23
|
-
define_methods
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def validate_type!(value)
|
|
27
|
-
return if !@type || [*@type].any? do |type|
|
|
28
|
-
case type
|
|
29
|
-
when :boolean
|
|
30
|
-
value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
31
|
-
when Symbol
|
|
32
|
-
arg_type(value) == type
|
|
33
|
-
else
|
|
34
|
-
value.is_a?(type)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
raise Light::Services::ArgTypeError, "#{@service_class} argument `#{name}` must be " \
|
|
39
|
-
"a #{[*@type].join(', ')} (currently: #{value.class})"
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
def arg_type(value)
|
|
45
|
-
klass = value.class
|
|
46
|
-
|
|
47
|
-
@arg_types_cache[klass] ||= klass
|
|
48
|
-
.name
|
|
49
|
-
.gsub("::", "/")
|
|
50
|
-
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
51
|
-
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
52
|
-
.tr("-", "_")
|
|
53
|
-
.downcase
|
|
54
|
-
.to_sym
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def define_methods
|
|
58
|
-
name = @name
|
|
59
|
-
|
|
60
|
-
@service_class.define_method(@name) { @arguments.get(name) }
|
|
61
|
-
@service_class.define_method(:"#{@name}?") { !!@arguments.get(name) }
|
|
62
|
-
@service_class.define_method(:"#{@name}=") { |value| @arguments.set(name, value) }
|
|
63
|
-
@service_class.send(:private, "#{@name}=")
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|