light-services 0.5.4 → 2.0.0.beta1

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