light-services 0.5.4 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|