light-services 0.6.0 → 2.0.0.rc1

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 +8 -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 +74 -41
  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,74 +1,107 @@
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] ||= []
13
15
 
14
- rollback! if rollback
15
- end
16
-
17
- def from_record(record, rollback: true)
18
- record.errors.to_h.each do |key, message|
19
- add(key, message)
16
+ if message.is_a?(Array)
17
+ @messages[key] += message
18
+ else
19
+ @messages[key] << message
20
20
  end
21
21
 
22
- rollback! if rollback
22
+ raise!(key, message)
23
+ break!(opts[:break])
24
+ rollback!(opts[:rollback])
23
25
  end
24
26
 
25
- def from_service(service, rollback: true)
26
- service.errors.each do |key, message|
27
- add(key, message)
28
- end
29
-
30
- rollback! if rollback
27
+ def break?
28
+ @break
31
29
  end
32
30
 
33
- def delete(key)
34
- storage.delete(key)
31
+ def copy_from(entity, opts = {})
32
+ if defined?(ActiveRecord::Base) && entity.is_a?(ActiveRecord::Base)
33
+ copy_from(entity.errors.messages, opts)
34
+ elsif entity.respond_to?(:each)
35
+ entity.each do |key, message|
36
+ add(key, message, opts)
37
+ end
38
+ else
39
+ # TODO: Update error
40
+ raise Light::Services::Error
41
+ end
35
42
  end
36
43
 
37
- def blank?
38
- storage.empty?
39
- end
44
+ def copy_to(entity)
45
+ if defined?(ActiveRecord::Base) && entity.is_a?(ActiveRecord::Base)
46
+ each do |key, message|
47
+ entity.errors.add(key, message)
48
+ end
49
+ elsif entity.is_a?(Hash)
50
+ each do |key, message|
51
+ entity[key] ||= []
52
+ entity[key] << message
53
+ end
54
+ else
55
+ # TODO: Update error
56
+ raise Light::Services::Error
57
+ end
40
58
 
41
- def any?
42
- !blank?
59
+ entity
43
60
  end
44
61
 
45
- def to_hash
46
- storage
47
- end
62
+ def errors_to_record(record)
63
+ if !defined?(ActiveRecord::Base) || !record.is_a?(ActiveRecord::Base)
64
+ # TODO: Update error
65
+ raise Light::Services::Error
66
+ end
48
67
 
49
- def flatten
50
- to_hash.flat_map do |key, messages|
51
- messages.map do |message|
52
- [key, message]
53
- end
68
+ errors.each do |key, message|
69
+ record.errors.add(key, message)
54
70
  end
71
+
72
+ record
55
73
  end
56
74
 
57
- def each
58
- flatten.each do |key, message|
59
- yield key, message
75
+ def method_missing(method, *args, &block)
76
+ if @messages.respond_to?(method)
77
+ @messages.public_send(method, *args, &block)
78
+ else
79
+ super
60
80
  end
61
81
  end
62
82
 
63
- alias to_h to_hash
83
+ def respond_to_missing?(method, include_private = false)
84
+ @messages.respond_to?(method, include_private) || super
85
+ end
64
86
 
65
87
  private
66
88
 
67
- # Getters / Setters
68
- attr_accessor :storage
89
+ def break!(break_execution)
90
+ return unless break_execution.nil? ? @config[:break_on_add] : break_execution
91
+
92
+ @break = true
93
+ end
94
+
95
+ def raise!(key, message)
96
+ return unless @config[:raise_on_add]
97
+
98
+ raise Light::Services::Error, "#{key.to_s.capitalize} #{message}"
99
+ end
100
+
101
+ def rollback!(rollback)
102
+ return if !defined?(ActiveRecord::Rollback) || !(rollback.nil? ? @config[:rollback_on_add] : rollback)
69
103
 
70
- def rollback!
71
- raise ActiveRecord::Rollback if defined?(ActiveRecord::Rollback)
104
+ raise ActiveRecord::Rollback
72
105
  end
73
106
  end
74
107
  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