light-services 0.5.4 → 2.0.0.beta1

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 (48) hide show
  1. checksums.yaml +5 -5
  2. data/.github/config/rubocop_linter_action.yml +4 -0
  3. data/.github/workflows/ci.yml +63 -0
  4. data/.gitignore +3 -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 +127 -27
  17. data/lib/light/services/base_with_context.rb +32 -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 +77 -34
  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 +62 -0
  29. data/lib/light/services/version.rb +1 -1
  30. data/light-services.gemspec +21 -24
  31. metadata +34 -127
  32. data/.codeclimate.yml +0 -16
  33. data/.ruby-gemset +0 -1
  34. data/.travis.yml +0 -29
  35. data/Appraisals +0 -21
  36. data/gemfiles/rails_4_0.gemfile +0 -7
  37. data/gemfiles/rails_4_0.gemfile.lock +0 -115
  38. data/gemfiles/rails_4_1.gemfile +0 -7
  39. data/gemfiles/rails_4_1.gemfile.lock +0 -119
  40. data/gemfiles/rails_4_2.gemfile +0 -7
  41. data/gemfiles/rails_4_2.gemfile.lock +0 -146
  42. data/gemfiles/rails_5_0.gemfile +0 -7
  43. data/gemfiles/rails_5_0.gemfile.lock +0 -150
  44. data/gemfiles/rails_5_1.gemfile +0 -7
  45. data/gemfiles/rails_5_1.gemfile.lock +0 -150
  46. data/lib/light/services/callbacks.rb +0 -54
  47. data/lib/light/services/outputs.rb +0 -70
  48. data/lib/light/services/parameters.rb +0 -96
@@ -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,65 +1,108 @@
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)
11
- storage[key] ||= []
12
- storage[key] << message
13
- end
13
+ def add(key, message, opts = {})
14
+ @messages[key] ||= []
14
15
 
15
- def from_record(record)
16
- record.errors.to_h.each do |key, message|
17
- add(key, message)
16
+ if message.is_a?(Array)
17
+ @messages[key] += message
18
+ else
19
+ @messages[key] << message
18
20
  end
19
- end
20
21
 
21
- def from_service(service)
22
- service.errors.each do |key, message|
23
- add(key, message)
24
- end
22
+ raise!(key, message)
23
+ break!(opts[:break])
24
+ rollback!(opts[:rollback])
25
25
  end
26
26
 
27
- def delete(key)
28
- storage.delete(key)
27
+ def break?
28
+ @break
29
29
  end
30
30
 
31
- def blank?
32
- storage.empty?
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
33
42
  end
34
43
 
35
- def any?
36
- !blank?
37
- 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
38
58
 
39
- def to_hash
40
- storage
59
+ entity
41
60
  end
42
61
 
43
- def flatten
44
- to_hash.flat_map do |key, messages|
45
- messages.map do |message|
46
- [key, message]
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
67
+
68
+ errors.each do |key, message|
69
+ record.errors.add(key, message)
48
70
  end
71
+
72
+ record
49
73
  end
50
74
 
51
- def each
52
- flatten.each do |key, message|
53
- 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
54
80
  end
55
81
  end
56
82
 
57
- alias to_h to_hash
83
+ def respond_to_missing?(method, include_private = false)
84
+ @messages.respond_to?(method, include_private) || super
85
+ end
58
86
 
59
87
  private
60
88
 
61
- # Getters/Setters
62
- 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)
103
+
104
+ raise ActiveRecord::Rollback
105
+ end
63
106
  end
64
107
  end
65
108
  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,62 @@
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)
26
+ return false unless run?(instance)
27
+
28
+ if instance.respond_to?(name, true)
29
+ instance.send(name)
30
+ true
31
+ else
32
+ raise Light::Services::NoStepError, "Cannot find step `#{name}` in service `#{@service_class}`"
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def run?(instance)
39
+ if @if
40
+ check_condition(@if, instance)
41
+ elsif @unless
42
+ !check_condition(@unless, instance)
43
+ else
44
+ true
45
+ end
46
+ end
47
+
48
+ def check_condition(condition, instance)
49
+ case condition
50
+ when Symbol
51
+ instance.public_send(condition)
52
+ when Proc
53
+ condition.call
54
+ else
55
+ raise Light::Services::Error, "#{@service_class} condition should be a Symbol or Proc " \
56
+ "for the step `#{@name}` (currently: #{condition.class})"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Light
4
4
  module Services
5
- VERSION = '0.5.4'
5
+ VERSION = "2.0.0.beta1"
6
6
  end
7
7
  end
@@ -1,35 +1,32 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
 
4
- lib = File.expand_path('../lib', __FILE__)
5
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
- require 'light/services/version'
3
+ require_relative "lib/light/services/version"
7
4
 
8
5
  Gem::Specification.new do |spec|
9
- spec.name = 'light-services'
6
+ spec.name = "light-services"
10
7
  spec.version = Light::Services::VERSION
11
- spec.authors = ['Andrew Emelianenko']
12
- spec.email = ['emelianenko.web@gmail.com']
8
+ spec.authors = ["Andrew Emelianenko"]
9
+ spec.email = ["emelianenko.web@gmail.com"]
13
10
 
14
- spec.summary = 'Light pattern Services Object for Ruby/Rails'
15
- spec.description = 'Light pattern Services Object for Ruby/Rails from Light Ruby'
16
- spec.homepage = 'https://github.com/light-ruby/light-services'
17
- spec.license = 'MIT'
11
+ spec.summary = "Powerful implementation of Service Object pattern for Ruby and Rails"
12
+ spec.description = "Powerful implementation of Service Object pattern for Ruby and Rails"
13
+ spec.homepage = "https://github.com/light-ruby/light-services"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
18
16
 
19
- spec.files = `git ls-files -z`
20
- .split("\x0")
21
- .reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
22
18
 
23
- spec.bindir = 'exe'
24
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
- spec.require_paths = ['lib']
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/light-ruby/light-services"
21
+ spec.metadata["changelog_uri"] = "https://github.com/light-ruby/light-services/blob/master/CHANGELOG.md"
26
22
 
27
- spec.add_dependency 'rails', '> 4.0'
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
28
 
29
- spec.add_development_dependency 'bundler', '~> 1.12'
30
- spec.add_development_dependency 'rake', '~> 10.0'
31
- spec.add_development_dependency 'appraisal', '~> 2.1'
32
- spec.add_development_dependency 'rspec', '~> 3.0'
33
- spec.add_development_dependency 'simplecov', '~> 0.11.2'
34
- spec.add_development_dependency 'codeclimate-test-reporter'
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
35
32
  end