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