service_objects 0.0.1
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.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.metrics +5 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +4 -0
- data/.yardopts +3 -0
- data/Gemfile +3 -0
- data/Guardfile +16 -0
- data/LICENSE +21 -0
- data/README.md +461 -0
- data/Rakefile +17 -0
- data/config/metrics/STYLEGUIDE +230 -0
- data/config/metrics/cane.yml +5 -0
- data/config/metrics/churn.yml +6 -0
- data/config/metrics/flay.yml +2 -0
- data/config/metrics/metric_fu.yml +14 -0
- data/config/metrics/pippi.yml +3 -0
- data/config/metrics/reek.yml +1 -0
- data/config/metrics/roodi.yml +24 -0
- data/config/metrics/rubocop.yml +75 -0
- data/config/metrics/saikuro.yml +3 -0
- data/config/metrics/simplecov.yml +6 -0
- data/config/metrics/yardstick.yml +37 -0
- data/lib/service_objects/base.rb +43 -0
- data/lib/service_objects/helpers/dependable.rb +63 -0
- data/lib/service_objects/helpers/exceptions.rb +64 -0
- data/lib/service_objects/helpers/messages.rb +96 -0
- data/lib/service_objects/helpers/parameterized.rb +85 -0
- data/lib/service_objects/helpers/parameters.rb +71 -0
- data/lib/service_objects/helpers/validations.rb +54 -0
- data/lib/service_objects/invalid.rb +55 -0
- data/lib/service_objects/listener.rb +97 -0
- data/lib/service_objects/message.rb +117 -0
- data/lib/service_objects/null.rb +26 -0
- data/lib/service_objects/utils/normal_hash.rb +34 -0
- data/lib/service_objects/version.rb +9 -0
- data/lib/service_objects.rb +12 -0
- data/service_objects.gemspec +28 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/tests/base_spec.rb +43 -0
- data/spec/tests/helpers/dependable_spec.rb +77 -0
- data/spec/tests/helpers/exceptions_spec.rb +112 -0
- data/spec/tests/helpers/messages_spec.rb +64 -0
- data/spec/tests/helpers/parameterized_spec.rb +136 -0
- data/spec/tests/helpers/parameters_spec.rb +71 -0
- data/spec/tests/helpers/validations_spec.rb +60 -0
- data/spec/tests/invalid_spec.rb +69 -0
- data/spec/tests/listener_spec.rb +50 -0
- data/spec/tests/message_spec.rb +191 -0
- data/spec/tests/null_spec.rb +17 -0
- data/spec/tests/utils/normal_hash_spec.rb +16 -0
- metadata +182 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "wisper"
|
3
|
+
|
4
|
+
require "service_objects/helpers/messages"
|
5
|
+
require "service_objects/helpers/validations"
|
6
|
+
require "service_objects/helpers/exceptions"
|
7
|
+
require "service_objects/helpers/dependable"
|
8
|
+
require "service_objects/helpers/parameters"
|
9
|
+
require "service_objects/helpers/parameterized"
|
10
|
+
|
11
|
+
module ServiceObjects
|
12
|
+
|
13
|
+
# Base class for service objects
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# AddItem = Class.new(ServiceObjects::Base)
|
17
|
+
#
|
18
|
+
# @see http://www.rubydoc.info/github/krisleech/wisper
|
19
|
+
# 'wisper' gem by Kris Leech
|
20
|
+
# @see http://apidock.com/rails/v4.1.8/ActiveModel/Validations
|
21
|
+
# ActiveModel::Validations
|
22
|
+
class Base
|
23
|
+
|
24
|
+
extend Helpers::Dependable
|
25
|
+
extend Helpers::Parameterized
|
26
|
+
|
27
|
+
include Helpers::Exceptions
|
28
|
+
include Helpers::Validations
|
29
|
+
include Wisper::Publisher
|
30
|
+
# @!parse include ServiceObjects::Helpers::Parameters
|
31
|
+
# @!parse include ServiceObjects::Helpers::Messages
|
32
|
+
# @!parse include ActiveModel::Validations
|
33
|
+
|
34
|
+
# @abstract
|
35
|
+
# Runs service object
|
36
|
+
#
|
37
|
+
# @return [undefined]
|
38
|
+
def run
|
39
|
+
end
|
40
|
+
|
41
|
+
end # class Base
|
42
|
+
|
43
|
+
end # module ServiceObjects
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ServiceObjects
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
|
7
|
+
# Features for service dependencies declaration
|
8
|
+
#
|
9
|
+
# @note
|
10
|
+
# A target class should be **extended** by the module
|
11
|
+
module Dependable
|
12
|
+
|
13
|
+
# Declares the dependency setter and getter
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# class AddFoo
|
17
|
+
# extend ServiceObjects::Helpers::Dependable
|
18
|
+
#
|
19
|
+
# depends_on :get_item, default: GetItem
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# service = AddFoo.new
|
23
|
+
# service.get_item
|
24
|
+
# # => GetItem
|
25
|
+
#
|
26
|
+
# # Depencency injection
|
27
|
+
# service.get_item = FindItem
|
28
|
+
# service.get_item
|
29
|
+
# # => FindItem
|
30
|
+
#
|
31
|
+
# # Resetting to default
|
32
|
+
# service.get_item = nil
|
33
|
+
# service.get_item
|
34
|
+
# # => GetItem
|
35
|
+
#
|
36
|
+
# @example Set to NULL Object by default
|
37
|
+
# class AddFoo
|
38
|
+
# extend ServiceObjects::Helpers::Dependable
|
39
|
+
#
|
40
|
+
# depends_on :get_item
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# service = AddFoo.new
|
44
|
+
# service.get_item
|
45
|
+
# # => <ServiceObjects::NULL>
|
46
|
+
#
|
47
|
+
# @param [#to_sym] name
|
48
|
+
# the name for the dependency
|
49
|
+
# @param [BaseObject] default (ServiceObjects::NULL)
|
50
|
+
# default implementation for the dependency
|
51
|
+
#
|
52
|
+
# @return [:depends_on]
|
53
|
+
# the name of the method
|
54
|
+
def depends_on(name, default: NULL)
|
55
|
+
attr_writer name
|
56
|
+
define_method(name) { instance_eval("@#{ name }") || default }
|
57
|
+
end
|
58
|
+
|
59
|
+
end # Dependable
|
60
|
+
|
61
|
+
end # module Helpers
|
62
|
+
|
63
|
+
end # module ServiceObjects
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ServiceObjects
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
|
7
|
+
# Features for escaping from runtime errors
|
8
|
+
#
|
9
|
+
# @note
|
10
|
+
# A target class should **include** the module
|
11
|
+
module Exceptions
|
12
|
+
|
13
|
+
# Re-raises standard errors as <tt>ServiceObjects::Invalid</tt>
|
14
|
+
#
|
15
|
+
# Mutates the current object by adding error messages
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# class MyClass
|
19
|
+
# includes ServiceObjects::Helpers::Exceptions
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# begin
|
23
|
+
# MyClass.new.escape { fail StandardError.new "foo" }
|
24
|
+
# rescue => err
|
25
|
+
# puts err.class.name
|
26
|
+
# puts messages
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # => ServiceObjects::Invalid
|
30
|
+
# # => [<ServiceObject::Message type="error" text="foo" ...>]
|
31
|
+
#
|
32
|
+
# @yield the block
|
33
|
+
#
|
34
|
+
# @raise [ServiceObjects::Invalid]
|
35
|
+
# if the block raises +StandardError+
|
36
|
+
#
|
37
|
+
# @return [Object] the value returned by the block
|
38
|
+
def escape
|
39
|
+
yield if block_given?
|
40
|
+
rescue => error
|
41
|
+
collect_messages_from error
|
42
|
+
raise Invalid.new(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @!parse include ServiceObjects::Helpers::Messages
|
48
|
+
def self.included(klass)
|
49
|
+
klass.include Messages
|
50
|
+
end
|
51
|
+
|
52
|
+
def collect_messages_from(error)
|
53
|
+
if error.is_a? Invalid
|
54
|
+
messages.concat(error.messages) if error.object != self
|
55
|
+
else
|
56
|
+
add_message type: "error", text: error.message
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end # module Exceptions
|
61
|
+
|
62
|
+
end # module Helpers
|
63
|
+
|
64
|
+
end # module ServiceObjects
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "extlib"
|
3
|
+
|
4
|
+
module ServiceObjects
|
5
|
+
|
6
|
+
# Contains helper objecs for the base service
|
7
|
+
module Helpers
|
8
|
+
|
9
|
+
# Features for collecting service messages
|
10
|
+
#
|
11
|
+
# @note
|
12
|
+
# A target class should **include** the module
|
13
|
+
module Messages
|
14
|
+
|
15
|
+
# Translates the text in the current scope with given options
|
16
|
+
#
|
17
|
+
# The method uses I18n.t library method.
|
18
|
+
#
|
19
|
+
# @example Returns a translation for symbolic argument
|
20
|
+
# class Test
|
21
|
+
# include ServiceObjects::Helpers::Messages
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# MyClass.new.translate :item
|
25
|
+
# # => translation not found: en.activemodel.messages.models.test.item
|
26
|
+
#
|
27
|
+
# @example Converts non-symbolic argument to a string.
|
28
|
+
# class Test
|
29
|
+
# include ServiceObjects::Helpers::Messages
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# MyClass.new.translate 1
|
33
|
+
# # => "1"
|
34
|
+
#
|
35
|
+
# @param [#to_s] text
|
36
|
+
# @param [Hash] options ({})
|
37
|
+
#
|
38
|
+
# @return [String]
|
39
|
+
def translate(text, options = {})
|
40
|
+
return text.to_s unless text.is_a? Symbol
|
41
|
+
I18n.t text, Utils::NormalHash.from(options).merge(scope: __scope__)
|
42
|
+
end
|
43
|
+
|
44
|
+
# A list of object messages
|
45
|
+
#
|
46
|
+
# @return [Array<ServiceObjects::Message>]
|
47
|
+
def messages
|
48
|
+
@messages ||= []
|
49
|
+
end
|
50
|
+
|
51
|
+
# Translates the text and adds a new message to the list of {#messages}
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# class MyClass
|
55
|
+
# include ServiceObjects::Helpers::Messages
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# object = MyClass.new
|
59
|
+
# object.add_message type: "foo", text: "bar", priority: 2.5
|
60
|
+
# object.messages
|
61
|
+
# # => [<ServiceObjects::Message type="foo" text="bar" priority=2.5>]
|
62
|
+
#
|
63
|
+
# @param [Hash] options
|
64
|
+
# the list of options for the message and its translation
|
65
|
+
#
|
66
|
+
# @option options [#to_s] :text
|
67
|
+
# the text of a new message to be translated via {#translate} method
|
68
|
+
# @option options [#to_s] :type
|
69
|
+
# the type of a new message
|
70
|
+
# @option options [#to_f] :priority
|
71
|
+
# optional priority fo a new message
|
72
|
+
#
|
73
|
+
# @return [Array<ServiceObjects::Message>] The updated {#messages}
|
74
|
+
def add_message(options)
|
75
|
+
params = Utils::NormalHash.from(options)
|
76
|
+
type = params.delete :type
|
77
|
+
priority = params.delete :priority
|
78
|
+
text = translate params.delete(:text), params
|
79
|
+
messages << Message.new(text: text, type: type, priority: priority)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def __scope__
|
85
|
+
@__scope__ ||= [:activemodel, :messages, :models, __class_name__.to_sym]
|
86
|
+
end
|
87
|
+
|
88
|
+
def __class_name__
|
89
|
+
self.class.name.split("::").map(&:snake_case).join("/")
|
90
|
+
end
|
91
|
+
|
92
|
+
end # module Messages
|
93
|
+
|
94
|
+
end # module Helpers
|
95
|
+
|
96
|
+
end # module Services
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ServiceObjects
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
|
7
|
+
# Features for service parameters declaration
|
8
|
+
#
|
9
|
+
# @note
|
10
|
+
# A target class should be **extended** by the module
|
11
|
+
module Parameterized
|
12
|
+
|
13
|
+
# Whitelists {.new} options assigned to params
|
14
|
+
#
|
15
|
+
# Mutates the current class by adding the corresponding attribute
|
16
|
+
# for every parameter.
|
17
|
+
#
|
18
|
+
# @example Whitelists options
|
19
|
+
# class AddFoo
|
20
|
+
# extend ServiceObjects::Helpers::Parameterized
|
21
|
+
#
|
22
|
+
# allows_params :foo
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# service = AddFoo.new foo: "foo", bar: "baz"
|
26
|
+
# service.params # => { foo: "foo" }
|
27
|
+
#
|
28
|
+
# @example Adds attributes as aliases to corresponding params
|
29
|
+
# class AddFoo
|
30
|
+
# extend ServiceObjects::Helpers::Parameterized
|
31
|
+
#
|
32
|
+
# allows_params :foo
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# service = AddFoo.new foo: "foo"
|
36
|
+
# service.foo # => "foo"
|
37
|
+
#
|
38
|
+
# service.params[:foo] = "bar"
|
39
|
+
# service.foo # => "bar"
|
40
|
+
#
|
41
|
+
# service.foo = "baz"
|
42
|
+
# service.foo # => "baz"
|
43
|
+
# service.params[:foo] # => "baz"
|
44
|
+
#
|
45
|
+
# @param [Array<#to_sym>] list
|
46
|
+
#
|
47
|
+
# @return [Array<Symbol>]
|
48
|
+
# the list of arguments
|
49
|
+
def allows_params(*list)
|
50
|
+
@whitelist = list.flatten.map(&:to_sym).freeze
|
51
|
+
__attr_params__
|
52
|
+
|
53
|
+
whitelist
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# @!parse include ServiceObjects::Helpers::Parameters
|
59
|
+
def self.extended(klass)
|
60
|
+
klass.include Parameters
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
def __attr_params__
|
65
|
+
whitelist.each(&method(:__attr_param_accessor__))
|
66
|
+
end
|
67
|
+
|
68
|
+
def __attr_param_accessor__(name)
|
69
|
+
__attr_param_reader__ name
|
70
|
+
__attr_param_writer__ name
|
71
|
+
end
|
72
|
+
|
73
|
+
def __attr_param_reader__(name)
|
74
|
+
define_method(name) { params[name] }
|
75
|
+
end
|
76
|
+
|
77
|
+
def __attr_param_writer__(name)
|
78
|
+
define_method("#{ name }=") { |value| params[name] = value }
|
79
|
+
end
|
80
|
+
|
81
|
+
end # module Parameters
|
82
|
+
|
83
|
+
end # module Helpers
|
84
|
+
|
85
|
+
end # module ServiceObjects
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ServiceObjects
|
4
|
+
|
5
|
+
module Helpers
|
6
|
+
|
7
|
+
# Features for whitelisting service options
|
8
|
+
#
|
9
|
+
# @note
|
10
|
+
# A target class should **include** the module
|
11
|
+
module Parameters
|
12
|
+
|
13
|
+
# @!scope class
|
14
|
+
# @!attribute [r] whitelist
|
15
|
+
# Returns the list of allowed parameters
|
16
|
+
#
|
17
|
+
# @return [Array<Symbol>]
|
18
|
+
|
19
|
+
# @!attribute [r] params
|
20
|
+
# Service object parameters
|
21
|
+
#
|
22
|
+
# @return [Hash]
|
23
|
+
attr_reader :params
|
24
|
+
|
25
|
+
# Service object initializer
|
26
|
+
#
|
27
|
+
# @example Normalizing options
|
28
|
+
# class AddFoo
|
29
|
+
# include ServiceObjects::Helpers::Parameters
|
30
|
+
#
|
31
|
+
# @whitelist = [:foo]
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# service = AddFoo.new("foo" => { "bar" => "baz" })
|
35
|
+
# service.params # => { foo: { bar: "baz" } }
|
36
|
+
#
|
37
|
+
# @example Whitelisting options
|
38
|
+
# class AddFoo
|
39
|
+
# include ServiceObjects::Helpers::Parameters
|
40
|
+
#
|
41
|
+
# @whitelist = [:foo]
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# service = AddFoo.new(foo: "bar", bar: "baz")
|
45
|
+
# service.params # => { foo: "bar" }
|
46
|
+
#
|
47
|
+
# @param [Hash] options
|
48
|
+
#
|
49
|
+
# @return [undefined]
|
50
|
+
def initialize(options = {})
|
51
|
+
@params = Utils::NormalHash.from(options).slice(*__whitelist__)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def self.included(klass)
|
57
|
+
klass
|
58
|
+
.singleton_class
|
59
|
+
.send(:define_method, :whitelist) { @whitelist ||= [] }
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
def __whitelist__
|
64
|
+
@whitelist ||= self.class.send :whitelist
|
65
|
+
end
|
66
|
+
|
67
|
+
end # module Parameters
|
68
|
+
|
69
|
+
end # module Helpers
|
70
|
+
|
71
|
+
end # module ServiceObjects
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "active_model"
|
3
|
+
|
4
|
+
module ServiceObjects
|
5
|
+
|
6
|
+
module Helpers
|
7
|
+
|
8
|
+
# Features for service attributes validation
|
9
|
+
#
|
10
|
+
# @note
|
11
|
+
# A target class should **include** the module
|
12
|
+
#
|
13
|
+
# @see http://apidock.com/rails/v4.1.8/ActiveModel/Validations
|
14
|
+
# ActiveModel::Validations
|
15
|
+
module Validations
|
16
|
+
|
17
|
+
# @!method valid?
|
18
|
+
# Runs validations and checks if the object is valid
|
19
|
+
# @return [Boolean]
|
20
|
+
|
21
|
+
# Raises <tt>ServiceObjects::Invalid</tt> when {#valid?} method fails
|
22
|
+
#
|
23
|
+
# Mutates the current object by populating its messages
|
24
|
+
# with errors, added by {#valid?}
|
25
|
+
#
|
26
|
+
# @raise [ServiceObjects::Invalid]
|
27
|
+
# when a validation fails
|
28
|
+
#
|
29
|
+
# @return [self] (not changed)
|
30
|
+
# when a validation passes
|
31
|
+
def validate!
|
32
|
+
return self if valid?
|
33
|
+
__errors__.each { |text| add_message text: text, type: "error" }
|
34
|
+
fail Invalid.new(self)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# @!parse include ServiceObjects::Helpers::Messages
|
40
|
+
# @!parse include ActiveModel::Validations
|
41
|
+
def self.included(klass)
|
42
|
+
klass.include ActiveModel::Validations, Messages
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def __errors__
|
47
|
+
errors.messages.values.flatten
|
48
|
+
end
|
49
|
+
|
50
|
+
end # module Validations
|
51
|
+
|
52
|
+
end # module Helpers
|
53
|
+
|
54
|
+
end # module ServiceObjects
|