light-services 0.5.4 → 2.0.0.beta1
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 +5 -5
- data/.github/config/rubocop_linter_action.yml +4 -0
- data/.github/workflows/ci.yml +63 -0
- data/.gitignore +3 -4
- data/.rspec +1 -0
- data/.rubocop.yml +22 -8
- data/CHANGELOG.md +1 -0
- data/CODE_OF_CONDUCT.md +55 -30
- data/Gemfile +16 -2
- data/Gemfile.lock +101 -0
- data/LICENSE.txt +1 -1
- data/README.md +3 -149
- data/Rakefile +2 -2
- data/bin/console +4 -4
- data/lib/light/services.rb +4 -7
- data/lib/light/services/base.rb +127 -27
- data/lib/light/services/base_with_context.rb +32 -0
- data/lib/light/services/class_based_collection/base.rb +88 -0
- data/lib/light/services/class_based_collection/mount.rb +33 -0
- data/lib/light/services/collection/arguments.rb +34 -0
- data/lib/light/services/collection/base.rb +47 -0
- data/lib/light/services/collection/outputs.rb +16 -0
- data/lib/light/services/config.rb +63 -0
- data/lib/light/services/exceptions.rb +3 -2
- data/lib/light/services/messages.rb +77 -34
- data/lib/light/services/settings/argument.rb +50 -0
- data/lib/light/services/settings/output.rb +34 -0
- data/lib/light/services/settings/step.rb +62 -0
- data/lib/light/services/version.rb +1 -1
- data/light-services.gemspec +21 -24
- metadata +34 -127
- data/.codeclimate.yml +0 -16
- data/.ruby-gemset +0 -1
- data/.travis.yml +0 -29
- data/Appraisals +0 -21
- data/gemfiles/rails_4_0.gemfile +0 -7
- data/gemfiles/rails_4_0.gemfile.lock +0 -115
- data/gemfiles/rails_4_1.gemfile +0 -7
- data/gemfiles/rails_4_1.gemfile.lock +0 -119
- data/gemfiles/rails_4_2.gemfile +0 -7
- data/gemfiles/rails_4_2.gemfile.lock +0 -146
- data/gemfiles/rails_5_0.gemfile +0 -7
- data/gemfiles/rails_5_0.gemfile.lock +0 -150
- data/gemfiles/rails_5_1.gemfile +0 -7
- data/gemfiles/rails_5_1.gemfile.lock +0 -150
- data/lib/light/services/callbacks.rb +0 -54
- data/lib/light/services/outputs.rb +0 -70
- data/lib/light/services/parameters.rb +0 -96
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "bundler/setup"
|
5
|
+
require "light/services"
|
6
6
|
|
7
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
8
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -11,5 +11,5 @@ require 'light/services'
|
|
11
11
|
# require "pry"
|
12
12
|
# Pry.start
|
13
13
|
|
14
|
-
require
|
15
|
-
IRB.start
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/lib/light/services.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require 'light/services/outputs'
|
8
|
-
require 'light/services/callbacks'
|
9
|
-
require 'light/services/base'
|
3
|
+
require "light/services/config"
|
4
|
+
require "light/services/version"
|
5
|
+
require "light/services/exceptions"
|
6
|
+
require "light/services/base"
|
10
7
|
|
11
8
|
module Light
|
12
9
|
module Services
|
data/lib/light/services/base.rb
CHANGED
@@ -1,57 +1,157 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "light/services/messages"
|
4
|
+
require "light/services/base_with_context"
|
5
|
+
|
6
|
+
require "light/services/settings/step"
|
7
|
+
require "light/services/settings/output"
|
8
|
+
require "light/services/settings/argument"
|
9
|
+
|
10
|
+
require "light/services/collection/base"
|
11
|
+
require "light/services/collection/outputs"
|
12
|
+
require "light/services/collection/arguments"
|
13
|
+
|
14
|
+
require "light/services/class_based_collection/base"
|
15
|
+
require "light/services/class_based_collection/mount"
|
16
|
+
|
17
|
+
# Base class for all service objects
|
3
18
|
module Light
|
4
19
|
module Services
|
5
20
|
class Base
|
6
21
|
# Includes
|
7
|
-
|
8
|
-
|
9
|
-
|
22
|
+
extend ClassBasedCollection::Mount
|
23
|
+
|
24
|
+
# Settings
|
25
|
+
mount_class_based_collection :steps, item_class: Settings::Step, shortcut: :step
|
26
|
+
mount_class_based_collection :outputs, item_class: Settings::Output, shortcut: :output
|
27
|
+
mount_class_based_collection :arguments, item_class: Settings::Argument, shortcut: :arg, allow_redefine: true
|
28
|
+
|
29
|
+
# Steps
|
30
|
+
step :load_defaults_and_validate
|
10
31
|
|
11
32
|
# Getters
|
12
|
-
attr_reader :errors, :warnings
|
33
|
+
attr_reader :outputs, :arguments, :errors, :warnings
|
13
34
|
|
14
|
-
def initialize(args = {})
|
15
|
-
@
|
35
|
+
def initialize(args = {}, config = {}, parent_service = nil)
|
36
|
+
@config = Light::Services.config.merge(config)
|
37
|
+
@parent_service = parent_service
|
16
38
|
|
17
|
-
|
18
|
-
|
39
|
+
@outputs = Collection::Outputs.new(self)
|
40
|
+
@arguments = Collection::Arguments.new(self, args)
|
19
41
|
|
20
|
-
@
|
21
|
-
@warnings = Light::Services::Messages.new
|
22
|
-
end
|
42
|
+
@launched_steps = []
|
23
43
|
|
24
|
-
|
25
|
-
|
44
|
+
initialize_errors
|
45
|
+
initialize_warnings
|
26
46
|
end
|
27
47
|
|
28
48
|
def success?
|
29
|
-
errors
|
49
|
+
!errors?
|
50
|
+
end
|
51
|
+
|
52
|
+
def failed?
|
53
|
+
errors?
|
30
54
|
end
|
31
55
|
|
32
|
-
def
|
33
|
-
|
56
|
+
def errors?
|
57
|
+
@errors.any?
|
58
|
+
end
|
59
|
+
|
60
|
+
def warnings?
|
61
|
+
@warnings.any?
|
62
|
+
end
|
63
|
+
|
64
|
+
def call
|
65
|
+
run_steps
|
66
|
+
run_steps_with_always
|
67
|
+
|
68
|
+
copy_warnings_to_parent_service
|
69
|
+
copy_errors_to_parent_service
|
34
70
|
end
|
35
71
|
|
36
72
|
class << self
|
37
|
-
|
73
|
+
# TODO: Create `run!`
|
74
|
+
def run(args = {})
|
38
75
|
new(args).tap(&:call)
|
39
76
|
end
|
40
77
|
|
41
|
-
|
78
|
+
def with(service_or_config = {}, config = {})
|
79
|
+
service = service_or_config.is_a?(Hash) ? nil : service_or_config
|
80
|
+
config = service ? config : service_or_config
|
81
|
+
|
82
|
+
BaseWithContext.new(self, service, config)
|
83
|
+
end
|
42
84
|
end
|
43
85
|
|
44
86
|
private
|
45
87
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
88
|
+
def initialize_errors
|
89
|
+
@errors = Messages.new(
|
90
|
+
break_on_add: @config[:break_on_error],
|
91
|
+
raise_on_add: @config[:raise_on_error],
|
92
|
+
rollback_on_add: @config[:use_transactions] && @config[:rollback_on_error]
|
93
|
+
)
|
94
|
+
end
|
95
|
+
|
96
|
+
def initialize_warnings
|
97
|
+
@warnings = Messages.new(
|
98
|
+
break_on_add: @config[:break_on_warning],
|
99
|
+
raise_on_add: @config[:raise_on_warning],
|
100
|
+
rollback_on_add: @config[:use_transactions] && @config[:rollback_on_warning]
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def run_steps
|
105
|
+
within_transaction do
|
106
|
+
self.class.steps.each do |name, step|
|
107
|
+
@launched_steps << name if step.run(self)
|
108
|
+
|
109
|
+
break if @errors.break? || @warnings.break?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Run steps with parameter `always` if they weren't launched because of errors/warnings
|
115
|
+
def run_steps_with_always
|
116
|
+
self.class.steps.each do |name, step|
|
117
|
+
next if !step.always || @launched_steps.include?(name)
|
118
|
+
|
119
|
+
@launched_steps << name if step.run(self)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def copy_warnings_to_parent_service
|
124
|
+
return if !@parent_service || !@config[:load_warnings]
|
125
|
+
|
126
|
+
@parent_service.warnings.copy_from(
|
127
|
+
@warnings,
|
128
|
+
break: @config[:self_break_on_warning],
|
129
|
+
rollback: @config[:self_rollback_on_warning]
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
def copy_errors_to_parent_service
|
134
|
+
return if !@parent_service || !@config[:load_errors]
|
135
|
+
|
136
|
+
@parent_service.errors.copy_from(
|
137
|
+
@errors,
|
138
|
+
break: @config[:self_break_on_error],
|
139
|
+
rollback: @config[:self_rollback_on_error]
|
140
|
+
)
|
141
|
+
end
|
142
|
+
|
143
|
+
def load_defaults_and_validate
|
144
|
+
@outputs.load_defaults
|
145
|
+
@arguments.load_defaults
|
146
|
+
@arguments.validate!
|
147
|
+
end
|
148
|
+
|
149
|
+
def within_transaction
|
150
|
+
if @config[:use_transactions] && defined?(ActiveRecord::Base)
|
151
|
+
ActiveRecord::Base.transaction(requires_new: true) { yield }
|
152
|
+
else
|
153
|
+
yield
|
154
|
+
end
|
55
155
|
end
|
56
156
|
end
|
57
157
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This class allows to run service object with context (parent class and custom config)
|
4
|
+
module Light
|
5
|
+
module Services
|
6
|
+
class BaseWithContext
|
7
|
+
def initialize(service_class, parent_service, config)
|
8
|
+
@service_class = service_class
|
9
|
+
@config = config
|
10
|
+
@parent_service = parent_service
|
11
|
+
|
12
|
+
return if parent_service.nil? || parent_service.is_a?(Light::Services::Base)
|
13
|
+
|
14
|
+
raise Light::Services::ArgTypeError, "#{parent_service.class} - must be a subclass of Light::Services::Base"
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO: Create `run!`
|
18
|
+
def run(args = {})
|
19
|
+
@service_class.new(extend_arguments(args), @config, @parent_service).tap(&:call)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def extend_arguments(args)
|
25
|
+
return args unless @parent_service
|
26
|
+
|
27
|
+
# TODO: Do we need `.dup` here?
|
28
|
+
@parent_service.arguments.extend_with_context(args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,88 @@
|
|
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
|
+
# TODO: Update error class
|
67
|
+
raise Light::Services::Error, "#{@item_class} with name `#{name}` already exists in service #{klass}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_opts!(klass, name, opts)
|
72
|
+
if opts[:before] && opts[:after]
|
73
|
+
# TODO: Update error class
|
74
|
+
raise Light::Services::Error, "You cannot specify `before` and `after` " \
|
75
|
+
"for #{@item_class} `#{name}` in service #{klass} at the same time"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def insert_item(klass, name, opts, item)
|
80
|
+
index = find_index(klass, opts[:before] || opts[:after])
|
81
|
+
index = opts[:before] ? index : index + 1
|
82
|
+
|
83
|
+
@collection[klass] = @collection[klass].to_a.insert(index, [name, item]).to_h
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,33 @@
|
|
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
|
@@ -0,0 +1,34 @@
|
|
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.valid_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
|
@@ -0,0 +1,47 @@
|
|
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 load_defaults
|
32
|
+
settings_collection.each do |name, settings|
|
33
|
+
next if !settings.default_exists || key?(name)
|
34
|
+
|
35
|
+
set(name, deep_dup(settings.default))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def deep_dup(object)
|
42
|
+
Marshal.load(Marshal.dump(object))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|