light-services 0.6.0 → 2.0.0.rc1

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