light-services 0.6.3 → 2.0.0.rc3

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/config/rubocop_linter_action.yml +4 -0
  3. data/.github/workflows/ci.yml +63 -0
  4. data/.gitignore +11 -4
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +22 -8
  7. data/CHANGELOG.md +1 -0
  8. data/CODE_OF_CONDUCT.md +55 -30
  9. data/Gemfile +16 -2
  10. data/Gemfile.lock +102 -0
  11. data/LICENSE.txt +1 -1
  12. data/README.md +3 -149
  13. data/Rakefile +2 -2
  14. data/bin/console +4 -4
  15. data/lib/light/services.rb +4 -7
  16. data/lib/light/services/base.rb +151 -30
  17. data/lib/light/services/base_with_context.rb +33 -0
  18. data/lib/light/services/class_based_collection/base.rb +88 -0
  19. data/lib/light/services/class_based_collection/mount.rb +33 -0
  20. data/lib/light/services/collection/arguments.rb +34 -0
  21. data/lib/light/services/collection/base.rb +55 -0
  22. data/lib/light/services/collection/outputs.rb +16 -0
  23. data/lib/light/services/config.rb +63 -0
  24. data/lib/light/services/exceptions.rb +3 -2
  25. data/lib/light/services/messages.rb +67 -45
  26. data/lib/light/services/settings/argument.rb +50 -0
  27. data/lib/light/services/settings/output.rb +34 -0
  28. data/lib/light/services/settings/step.rb +77 -0
  29. data/lib/light/services/version.rb +1 -1
  30. data/light-services.gemspec +21 -25
  31. metadata +28 -122
  32. data/.codeclimate.yml +0 -18
  33. data/.ruby-gemset +0 -1
  34. data/.ruby-version +0 -1
  35. data/.travis.yml +0 -33
  36. data/Appraisals +0 -13
  37. data/gemfiles/rails_5_0.gemfile +0 -7
  38. data/gemfiles/rails_5_0.gemfile.lock +0 -151
  39. data/gemfiles/rails_5_1.gemfile +0 -7
  40. data/gemfiles/rails_5_1.gemfile.lock +0 -151
  41. data/gemfiles/rails_5_2.gemfile +0 -7
  42. data/gemfiles/rails_5_2.gemfile.lock +0 -159
  43. data/lib/light/services/callbacks.rb +0 -54
  44. data/lib/light/services/outputs.rb +0 -70
  45. data/lib/light/services/parameters.rb +0 -96
@@ -0,0 +1,55 @@
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
+ set(name, deep_dup(settings.default))
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def deep_dup(object)
50
+ Marshal.load(Marshal.dump(object))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Collection to store outputs values
4
+ module Light
5
+ module Services
6
+ module Collection
7
+ class Outputs < Base
8
+ private
9
+
10
+ def settings_collection
11
+ @instance.class.outputs
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Light
4
+ module Services
5
+ class << self
6
+ def configure
7
+ yield config
8
+ end
9
+
10
+ def config
11
+ @config ||= Config.new
12
+ end
13
+ end
14
+
15
+ class Config
16
+ # Constants
17
+ DEFAULTS = {
18
+ use_transactions: true,
19
+
20
+ load_errors: true,
21
+ break_on_error: true,
22
+ raise_on_error: false,
23
+ rollback_on_error: true,
24
+
25
+ load_warnings: true,
26
+ break_on_warning: false,
27
+ raise_on_warning: false,
28
+ rollback_on_warning: false
29
+ }.freeze
30
+
31
+ # Getters / Setters
32
+ attr_accessor :use_transactions,
33
+ :load_errors, :break_on_error, :raise_on_error, :rollback_on_error,
34
+ :load_warnings, :break_on_warning, :raise_on_warning, :rollback_on_warning
35
+
36
+ def initialize
37
+ reset_to_defaults!
38
+ end
39
+
40
+ def set(key, value)
41
+ instance_variable_set("@#{key}", value)
42
+ end
43
+
44
+ def get(key)
45
+ instance_variable_get("@#{key}")
46
+ end
47
+
48
+ def reset_to_defaults!
49
+ DEFAULTS.each do |key, value|
50
+ set(key, value)
51
+ end
52
+ end
53
+
54
+ def to_h
55
+ DEFAULTS.keys.map { |key| [key, get(key)] }.to_h
56
+ end
57
+
58
+ def merge(config)
59
+ to_h.merge(config)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -3,7 +3,8 @@
3
3
  module Light
4
4
  module Services
5
5
  class Error < StandardError; end
6
- class ParamRequired < Light::Services::Error; end
7
- class ParamType < Light::Services::Error; end
6
+ class ArgTypeError < Error; end
7
+ class NoStepError < Error; end
8
+ class TwoConditions < Error; end
8
9
  end
9
10
  end
@@ -1,80 +1,102 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # This class stores errors and warnings
3
4
  module Light
4
5
  module Services
5
6
  class Messages
6
- def initialize
7
- @storage = {}
7
+ def initialize(config)
8
+ @break = false
9
+ @config = config
10
+ @messages = {}
8
11
  end
9
12
 
10
- def add(key, message, rollback: true)
11
- storage[key] ||= []
12
- storage[key] << message
13
+ def add(key, message, opts = {})
14
+ @messages[key] ||= []
15
+ @messages[key] += [*message]
13
16
 
14
- rollback! if rollback
17
+ raise!(key, message)
18
+ break!(opts[:break])
19
+ rollback!(opts[:rollback])
15
20
  end
16
21
 
17
- def from_record(record, rollback: true)
18
- return unless record.errors.any?
22
+ def break?
23
+ @break
24
+ end
19
25
 
20
- record.errors.messages.each do |key, messages|
21
- messages.each do |message|
22
- add(key, message, rollback: false)
26
+ def copy_from(entity, opts = {})
27
+ if defined?(ActiveRecord::Base) && entity.is_a?(ActiveRecord::Base)
28
+ copy_from(entity.errors.messages, opts)
29
+ elsif entity.respond_to?(:each)
30
+ entity.each do |key, message|
31
+ add(key, message, opts)
23
32
  end
33
+ else
34
+ # TODO: Update error
35
+ raise Light::Services::Error
24
36
  end
25
-
26
- rollback! if rollback
27
37
  end
28
38
 
29
- def from_service(service, rollback: true)
30
- return unless service.errors.any?
31
-
32
- service.errors.each do |key, message|
33
- add(key, message, rollback: false)
39
+ def copy_to(entity)
40
+ if defined?(ActiveRecord::Base) && entity.is_a?(ActiveRecord::Base)
41
+ each do |key, message|
42
+ entity.errors.add(key, message)
43
+ end
44
+ elsif entity.is_a?(Hash)
45
+ each do |key, message|
46
+ entity[key] ||= []
47
+ entity[key] << message
48
+ end
49
+ else
50
+ # TODO: Update error
51
+ raise Light::Services::Error
34
52
  end
35
53
 
36
- rollback! if rollback
54
+ entity
37
55
  end
38
56
 
39
- def delete(key)
40
- storage.delete(key)
41
- end
57
+ def errors_to_record(record)
58
+ if !defined?(ActiveRecord::Base) || !record.is_a?(ActiveRecord::Base)
59
+ # TODO: Update error
60
+ raise Light::Services::Error
61
+ end
42
62
 
43
- def blank?
44
- storage.empty?
45
- end
63
+ errors.each do |key, message|
64
+ record.errors.add(key, message)
65
+ end
46
66
 
47
- def any?
48
- !blank?
67
+ record
49
68
  end
50
69
 
51
- def to_hash
52
- storage
70
+ def method_missing(method, *args, &block)
71
+ if @messages.respond_to?(method)
72
+ @messages.public_send(method, *args, &block)
73
+ else
74
+ super
75
+ end
53
76
  end
54
77
 
55
- def flatten
56
- to_hash.flat_map do |key, messages|
57
- messages.map do |message|
58
- [key, message]
59
- end
60
- end
78
+ def respond_to_missing?(method, include_private = false)
79
+ @messages.respond_to?(method, include_private) || super
61
80
  end
62
81
 
63
- def each
64
- flatten.each do |key, message|
65
- yield key, message
66
- end
82
+ private
83
+
84
+ def break!(break_execution)
85
+ return unless break_execution.nil? ? @config[:break_on_add] : break_execution
86
+
87
+ @break = true
67
88
  end
68
89
 
69
- alias to_h to_hash
90
+ def raise!(key, message)
91
+ return unless @config[:raise_on_add]
70
92
 
71
- private
93
+ raise Light::Services::Error, "#{key.to_s.capitalize} #{message}"
94
+ end
72
95
 
73
- # Getters / Setters
74
- attr_accessor :storage
96
+ def rollback!(rollback)
97
+ return if !defined?(ActiveRecord::Rollback) || !(rollback.nil? ? @config[:rollback_on_add] : rollback)
75
98
 
76
- def rollback!
77
- raise ActiveRecord::Rollback if defined?(ActiveRecord::Rollback)
99
+ raise ActiveRecord::Rollback
78
100
  end
79
101
  end
80
102
  end
@@ -0,0 +1,50 @@
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
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
+ define_methods
22
+ end
23
+
24
+ def valid_type?(value)
25
+ return if !@type || [*@type].any? do |type|
26
+ if type == :boolean
27
+ value.is_a?(TrueClass) || value.is_a?(FalseClass)
28
+ else
29
+ value.is_a?(type)
30
+ end
31
+ end
32
+
33
+ raise Light::Services::ArgTypeError, "#{@service_class} argument `#{name}` must be " \
34
+ "a #{[*@type].join(', ')} (currently: #{value.class})"
35
+ end
36
+
37
+ private
38
+
39
+ def define_methods
40
+ name = @name
41
+
42
+ @service_class.define_method(@name) { @arguments.get(name) }
43
+ @service_class.define_method("#{@name}?") { !!@arguments.get(name) } # rubocop:disable Style/DoubleNegation
44
+ @service_class.define_method("#{@name}=") { |value| @arguments.set(name, value) }
45
+ @service_class.send(:private, "#{@name}=")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This class defines settings for output
4
+ module Light
5
+ module Services
6
+ module Settings
7
+ class Output
8
+ # Getters
9
+ attr_reader :name, :default_exists, :default
10
+
11
+ def initialize(name, service_class, opts = {})
12
+ @name = name
13
+ @service_class = service_class
14
+
15
+ @default_exists = opts.key?(:default)
16
+ @default = opts.delete(:default)
17
+
18
+ define_methods
19
+ end
20
+
21
+ private
22
+
23
+ def define_methods
24
+ name = @name
25
+
26
+ @service_class.define_method(@name) { @outputs.get(name) }
27
+ @service_class.define_method("#{@name}?") { !!@outputs.get(name) } # rubocop:disable Style/DoubleNegation
28
+ @service_class.define_method("#{@name}=") { |value| @outputs.set(name, value) }
29
+ @service_class.send(:private, "#{@name}=")
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This class defines settings for step
4
+ module Light
5
+ module Services
6
+ module Settings
7
+ class Step
8
+ # Getters
9
+ attr_reader :name, :always
10
+
11
+ def initialize(name, service_class, opts = {})
12
+ @name = name
13
+ @service_class = service_class
14
+
15
+ @if = opts[:if]
16
+ @unless = opts[:unless]
17
+ @always = opts[:always]
18
+
19
+ if @if && @unless
20
+ raise Light::Services::TwoConditions, "#{service_class} `if` and `unless` cannot be specified " \
21
+ "for the step `#{name}` at the same time"
22
+ end
23
+
24
+ if @always && (@if || @unless)
25
+ raise Light::Services::Error, "#{service_class} `always` cannot be combined with `if` and `unless`"
26
+ end
27
+ end
28
+
29
+ def run(instance, benchmark: false)
30
+ return false unless run?(instance)
31
+
32
+ if instance.respond_to?(name, true)
33
+ if benchmark
34
+ time = Benchmark.ms do
35
+ instance.send(name)
36
+ end
37
+
38
+ instance.log "⏱️ Step #{name} took #{time}ms"
39
+ else
40
+ instance.send(name)
41
+ end
42
+
43
+ true
44
+ else
45
+ raise Light::Services::NoStepError, "Cannot find step `#{name}` in service `#{@service_class}`"
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def run?(instance)
52
+ return false if instance.done?
53
+
54
+ if @if
55
+ check_condition(@if, instance)
56
+ elsif @unless
57
+ !check_condition(@unless, instance)
58
+ else
59
+ true
60
+ end
61
+ end
62
+
63
+ def check_condition(condition, instance)
64
+ case condition
65
+ when Symbol
66
+ instance.send(condition)
67
+ when Proc
68
+ condition.call
69
+ else
70
+ raise Light::Services::Error, "#{@service_class} condition should be a Symbol or Proc " \
71
+ "for the step `#{@name}` (currently: #{condition.class})"
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end