light-services 0.6.2 → 2.0.0.rc2

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 (51) 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 +101 -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 +142 -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 +47 -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 +68 -44
  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 +71 -0
  29. data/lib/light/services/version.rb +1 -1
  30. data/light-services.gemspec +21 -24
  31. metadata +28 -129
  32. data/.codeclimate.yml +0 -18
  33. data/.ruby-gemset +0 -1
  34. data/.ruby-version +0 -1
  35. data/.travis.yml +0 -36
  36. data/Appraisals +0 -25
  37. data/gemfiles/rails_4_0.gemfile +0 -7
  38. data/gemfiles/rails_4_0.gemfile.lock +0 -115
  39. data/gemfiles/rails_4_1.gemfile +0 -7
  40. data/gemfiles/rails_4_1.gemfile.lock +0 -118
  41. data/gemfiles/rails_4_2.gemfile +0 -7
  42. data/gemfiles/rails_4_2.gemfile.lock +0 -144
  43. data/gemfiles/rails_5_0.gemfile +0 -7
  44. data/gemfiles/rails_5_0.gemfile.lock +0 -151
  45. data/gemfiles/rails_5_1.gemfile +0 -7
  46. data/gemfiles/rails_5_1.gemfile.lock +0 -151
  47. data/gemfiles/rails_5_2.gemfile +0 -7
  48. data/gemfiles/rails_5_2.gemfile.lock +0 -159
  49. data/lib/light/services/callbacks.rb +0 -54
  50. data/lib/light/services/outputs.rb +0 -70
  51. data/lib/light/services/parameters.rb +0 -96
@@ -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
@@ -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,78 +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.to_h.each do |key, message|
21
- 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)
32
+ end
33
+ else
34
+ # TODO: Update error
35
+ raise Light::Services::Error
22
36
  end
23
-
24
- rollback! if rollback
25
37
  end
26
38
 
27
- def from_service(service, rollback: true)
28
- return unless service.errors.any?
29
-
30
- service.errors.each do |key, message|
31
- 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
32
52
  end
33
53
 
34
- rollback! if rollback
54
+ entity
35
55
  end
36
56
 
37
- def delete(key)
38
- storage.delete(key)
39
- 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
40
62
 
41
- def blank?
42
- storage.empty?
43
- end
63
+ errors.each do |key, message|
64
+ record.errors.add(key, message)
65
+ end
44
66
 
45
- def any?
46
- !blank?
67
+ record
47
68
  end
48
69
 
49
- def to_hash
50
- 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
51
76
  end
52
77
 
53
- def flatten
54
- to_hash.flat_map do |key, messages|
55
- messages.map do |message|
56
- [key, message]
57
- end
58
- end
78
+ def respond_to_missing?(method, include_private = false)
79
+ @messages.respond_to?(method, include_private) || super
59
80
  end
60
81
 
61
- def each
62
- flatten.each do |key, message|
63
- yield key, message
64
- 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
65
88
  end
66
89
 
67
- alias to_h to_hash
90
+ def raise!(key, message)
91
+ return unless @config[:raise_on_add]
68
92
 
69
- private
93
+ raise Light::Services::Error, "#{key.to_s.capitalize} #{message}"
94
+ end
70
95
 
71
- # Getters / Setters
72
- attr_accessor :storage
96
+ def rollback!(rollback)
97
+ return if !defined?(ActiveRecord::Rollback) || !(rollback.nil? ? @config[:rollback_on_add] : rollback)
73
98
 
74
- def rollback!
75
- raise ActiveRecord::Rollback if defined?(ActiveRecord::Rollback)
99
+ raise ActiveRecord::Rollback
76
100
  end
77
101
  end
78
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,71 @@
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
+ end
24
+
25
+ def run(instance, benchmark: false)
26
+ return false unless run?(instance)
27
+
28
+ if instance.respond_to?(name, true)
29
+ if benchmark
30
+ time = Benchmark.ms do
31
+ instance.send(name)
32
+ end
33
+
34
+ instance.log "⏱️ Step #{name} took #{time}ms"
35
+ else
36
+ instance.send(name)
37
+ end
38
+
39
+ true
40
+ else
41
+ raise Light::Services::NoStepError, "Cannot find step `#{name}` in service `#{@service_class}`"
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def run?(instance)
48
+ if @if
49
+ check_condition(@if, instance)
50
+ elsif @unless
51
+ !check_condition(@unless, instance)
52
+ else
53
+ true
54
+ end
55
+ end
56
+
57
+ def check_condition(condition, instance)
58
+ case condition
59
+ when Symbol
60
+ instance.send(condition)
61
+ when Proc
62
+ condition.call
63
+ else
64
+ raise Light::Services::Error, "#{@service_class} condition should be a Symbol or Proc " \
65
+ "for the step `#{@name}` (currently: #{condition.class})"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end