light-services 0.6.2 → 2.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/config/rubocop_linter_action.yml +4 -0
- data/.github/workflows/ci.yml +63 -0
- data/.gitignore +11 -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 +142 -30
- data/lib/light/services/base_with_context.rb +33 -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 +68 -44
- 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 +71 -0
- data/lib/light/services/version.rb +1 -1
- data/light-services.gemspec +21 -24
- metadata +28 -129
- data/.codeclimate.yml +0 -18
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -36
- data/Appraisals +0 -25
- 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 -118
- data/gemfiles/rails_4_2.gemfile +0 -7
- data/gemfiles/rails_4_2.gemfile.lock +0 -144
- data/gemfiles/rails_5_0.gemfile +0 -7
- data/gemfiles/rails_5_0.gemfile.lock +0 -151
- data/gemfiles/rails_5_1.gemfile +0 -7
- data/gemfiles/rails_5_1.gemfile.lock +0 -151
- data/gemfiles/rails_5_2.gemfile +0 -7
- data/gemfiles/rails_5_2.gemfile.lock +0 -159
- 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,64 +1,176 @@
|
|
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
|
+
# Arguments
|
30
|
+
# TODO: Rename internal arguments
|
31
|
+
arg :benchmark, default: false
|
32
|
+
arg :deepness, default: 0, context: true
|
33
|
+
|
34
|
+
# Steps
|
35
|
+
step :load_defaults_and_validate
|
36
|
+
step :log_header, if: :benchmark?
|
10
37
|
|
11
38
|
# Getters
|
12
|
-
attr_reader :errors, :warnings
|
39
|
+
attr_reader :outputs, :arguments, :errors, :warnings
|
13
40
|
|
14
|
-
def initialize(args = {})
|
15
|
-
@
|
41
|
+
def initialize(args = {}, config = {}, parent_service = nil)
|
42
|
+
@config = Light::Services.config.merge(config)
|
43
|
+
@parent_service = parent_service
|
16
44
|
|
17
|
-
|
18
|
-
|
45
|
+
@outputs = Collection::Outputs.new(self)
|
46
|
+
@arguments = Collection::Arguments.new(self, args)
|
19
47
|
|
20
|
-
@
|
21
|
-
@warnings = Light::Services::Messages.new
|
22
|
-
end
|
48
|
+
@launched_steps = []
|
23
49
|
|
24
|
-
|
25
|
-
|
50
|
+
initialize_errors
|
51
|
+
initialize_warnings
|
26
52
|
end
|
27
53
|
|
28
54
|
def success?
|
29
|
-
errors
|
55
|
+
!errors?
|
30
56
|
end
|
31
57
|
|
32
|
-
def
|
33
|
-
|
58
|
+
def failed?
|
59
|
+
errors?
|
60
|
+
end
|
61
|
+
|
62
|
+
def errors?
|
63
|
+
@errors.any?
|
64
|
+
end
|
65
|
+
|
66
|
+
def warnings?
|
67
|
+
@warnings.any?
|
68
|
+
end
|
69
|
+
|
70
|
+
def call
|
71
|
+
time = Benchmark.ms do
|
72
|
+
run_steps
|
73
|
+
run_steps_with_always
|
74
|
+
|
75
|
+
copy_warnings_to_parent_service
|
76
|
+
copy_errors_to_parent_service
|
77
|
+
end
|
78
|
+
|
79
|
+
return unless benchmark
|
80
|
+
|
81
|
+
log "🟢 Finished #{self.class} in #{time}ms"
|
82
|
+
puts
|
34
83
|
end
|
35
84
|
|
36
85
|
class << self
|
37
|
-
|
86
|
+
# TODO: Create `run!`
|
87
|
+
def run(args = {})
|
38
88
|
new(args).tap(&:call)
|
39
89
|
end
|
40
90
|
|
41
|
-
|
91
|
+
def with(service_or_config = {}, config = {})
|
92
|
+
service = service_or_config.is_a?(Hash) ? nil : service_or_config
|
93
|
+
config = service ? config : service_or_config
|
94
|
+
|
95
|
+
BaseWithContext.new(self, service, config)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# TODO: Add possibility to specify logger
|
100
|
+
def log(message)
|
101
|
+
puts "#{' ' * deepness}→ #{message}"
|
42
102
|
end
|
43
103
|
|
44
104
|
private
|
45
105
|
|
46
|
-
|
47
|
-
|
106
|
+
def initialize_errors
|
107
|
+
@errors = Messages.new(
|
108
|
+
break_on_add: @config[:break_on_error],
|
109
|
+
raise_on_add: @config[:raise_on_error],
|
110
|
+
rollback_on_add: @config[:use_transactions] && @config[:rollback_on_error]
|
111
|
+
)
|
112
|
+
end
|
48
113
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
114
|
+
def initialize_warnings
|
115
|
+
@warnings = Messages.new(
|
116
|
+
break_on_add: @config[:break_on_warning],
|
117
|
+
raise_on_add: @config[:raise_on_warning],
|
118
|
+
rollback_on_add: @config[:use_transactions] && @config[:rollback_on_warning]
|
119
|
+
)
|
55
120
|
end
|
56
121
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
122
|
+
def run_steps
|
123
|
+
within_transaction do
|
124
|
+
self.class.steps.each do |name, step|
|
125
|
+
@launched_steps << name if step.run(self, benchmark: benchmark)
|
126
|
+
|
127
|
+
break if @errors.break? || @warnings.break?
|
61
128
|
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Run steps with parameter `always` if they weren't launched because of errors/warnings
|
133
|
+
def run_steps_with_always
|
134
|
+
self.class.steps.each do |name, step|
|
135
|
+
next if !step.always || @launched_steps.include?(name)
|
136
|
+
|
137
|
+
@launched_steps << name if step.run(self)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def copy_warnings_to_parent_service
|
142
|
+
return if !@parent_service || !@config[:load_warnings]
|
143
|
+
|
144
|
+
@parent_service.warnings.copy_from(
|
145
|
+
@warnings,
|
146
|
+
break: @config[:self_break_on_warning],
|
147
|
+
rollback: @config[:self_rollback_on_warning]
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
def copy_errors_to_parent_service
|
152
|
+
return if !@parent_service || !@config[:load_errors]
|
153
|
+
|
154
|
+
@parent_service.errors.copy_from(
|
155
|
+
@errors,
|
156
|
+
break: @config[:self_break_on_error],
|
157
|
+
rollback: @config[:self_rollback_on_error]
|
158
|
+
)
|
159
|
+
end
|
160
|
+
|
161
|
+
def load_defaults_and_validate
|
162
|
+
@outputs.load_defaults
|
163
|
+
@arguments.load_defaults
|
164
|
+
@arguments.validate!
|
165
|
+
end
|
166
|
+
|
167
|
+
def log_header
|
168
|
+
log "🏎 Run service #{self.class}"
|
169
|
+
end
|
170
|
+
|
171
|
+
def within_transaction
|
172
|
+
if @config[:use_transactions] && defined?(ActiveRecord::Base)
|
173
|
+
ActiveRecord::Base.transaction(requires_new: true) { yield }
|
62
174
|
else
|
63
175
|
yield
|
64
176
|
end
|
@@ -0,0 +1,33 @@
|
|
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
|
+
# TODO: Do we need `.dup` here?
|
26
|
+
args = @parent_service.arguments.extend_with_context(args) if @parent_service
|
27
|
+
args[:deepness] += 1 if args[:deepness]
|
28
|
+
|
29
|
+
args
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
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
|