light-services 0.6.3 → 2.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
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