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,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ServiceObjects
|
4
|
+
|
5
|
+
# The exception to be risen by invalid services
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# object = ServiceObjects::Base.new
|
9
|
+
# fail ServiceObjects::Invalid.new object
|
10
|
+
class Invalid < ::RuntimeError
|
11
|
+
|
12
|
+
# @!scope class
|
13
|
+
# @!method new(object)
|
14
|
+
#
|
15
|
+
# Constructs the exception for given service object
|
16
|
+
#
|
17
|
+
# @example (see ServiceObjects::Invalid)
|
18
|
+
#
|
19
|
+
# @param [#messages] object
|
20
|
+
#
|
21
|
+
# @return [ServiceObjects::Invalid]
|
22
|
+
def initialize(object)
|
23
|
+
@object = object
|
24
|
+
validate
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!attribute [r] object
|
28
|
+
# Invalid service object
|
29
|
+
#
|
30
|
+
# @return [#messages]
|
31
|
+
attr_reader :object
|
32
|
+
|
33
|
+
# The array of messages from the invalid {#object}
|
34
|
+
#
|
35
|
+
# @return [Array<ServiceObjects::Message>]
|
36
|
+
def messages
|
37
|
+
Array(object.messages).flatten
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Checks if the {#object} respond to {#messages}
|
43
|
+
#
|
44
|
+
# @raise [TypeError]
|
45
|
+
# when the validation fails
|
46
|
+
#
|
47
|
+
# @return [undefined]
|
48
|
+
def validate
|
49
|
+
return if object.respond_to? :messages
|
50
|
+
fail TypeError.new "#{ object.inspect } doesn't respond to #messages"
|
51
|
+
end
|
52
|
+
|
53
|
+
end # class Invalid
|
54
|
+
|
55
|
+
end # module ServiceObjects
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ServiceObjects
|
4
|
+
|
5
|
+
# The base class for service listeners
|
6
|
+
#
|
7
|
+
# @example (see #new)
|
8
|
+
#
|
9
|
+
# @example (see #finalize)
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
class Listener < SimpleDelegator
|
13
|
+
|
14
|
+
# @!scope class
|
15
|
+
# @!method new(object)
|
16
|
+
# Listener object constructor
|
17
|
+
#
|
18
|
+
# Decorates given object by adding methods to be called by service objects
|
19
|
+
#
|
20
|
+
# @example Decorates an object
|
21
|
+
# object = Object.new
|
22
|
+
#
|
23
|
+
# listener = ServiceObjects::Listener.new object
|
24
|
+
# listener.send :__getobj__
|
25
|
+
# # => object
|
26
|
+
#
|
27
|
+
# @param [Object] object
|
28
|
+
#
|
29
|
+
# @return [ServiceObjects::Listener]
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
|
33
|
+
# The method called by {#finalize} when no other method has been checked
|
34
|
+
#
|
35
|
+
# @return [undefined]
|
36
|
+
#
|
37
|
+
# @abstract
|
38
|
+
def otherwise
|
39
|
+
end
|
40
|
+
|
41
|
+
# Calls {#otherwise} in case no existing method has been checked
|
42
|
+
#
|
43
|
+
# @example Calls {#otherwise} in case no method has been checked
|
44
|
+
# class MyListener < ServiceObjects::Listener
|
45
|
+
# def on_success
|
46
|
+
# "success"
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# def otherwise
|
50
|
+
# "nothing"
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# listener = MyListener.new
|
55
|
+
# listener.finalize
|
56
|
+
# # => "nothing"
|
57
|
+
#
|
58
|
+
# @example Calls {#otherwise} in case an undefined method has been checked
|
59
|
+
#
|
60
|
+
# listener = MyListener.new
|
61
|
+
# listener.respond_to? :on_error
|
62
|
+
# # => false
|
63
|
+
#
|
64
|
+
# listener.finalize
|
65
|
+
# # => "nothing"
|
66
|
+
#
|
67
|
+
# @example Skips {#otherwise} in case a defined method has been checked
|
68
|
+
#
|
69
|
+
# listener = MyListener.new
|
70
|
+
# listener.respond_to? :on_success
|
71
|
+
# # => true
|
72
|
+
#
|
73
|
+
# listener.finalize
|
74
|
+
# # => nil
|
75
|
+
#
|
76
|
+
# @return [self]
|
77
|
+
def finalize
|
78
|
+
otherwise unless @notified
|
79
|
+
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
# Checks whether the method is defined
|
84
|
+
#
|
85
|
+
# Remembers the fact that any defined method has been checked
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# respond_to? :finalize, false # => true
|
89
|
+
#
|
90
|
+
# @return [Boolean]
|
91
|
+
def respond_to?(*)
|
92
|
+
super ? (@notified = true) : false
|
93
|
+
end
|
94
|
+
|
95
|
+
end # class Listener
|
96
|
+
|
97
|
+
end # module ServiceObjects
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module ServiceObjects
|
5
|
+
|
6
|
+
# Describes messages published by service objects
|
7
|
+
class Message
|
8
|
+
include Comparable
|
9
|
+
|
10
|
+
# @!attribute [r] type
|
11
|
+
# The type of the message
|
12
|
+
#
|
13
|
+
# @return [String]
|
14
|
+
attr_reader :type
|
15
|
+
|
16
|
+
# @!attribute [r] text
|
17
|
+
# The text of the message
|
18
|
+
#
|
19
|
+
# @return [String]
|
20
|
+
attr_reader :text
|
21
|
+
|
22
|
+
# @!attribute [r] priority
|
23
|
+
# The priority level for the message
|
24
|
+
#
|
25
|
+
# If priority hasn't been set on initialization, sets it to -1.0 for errors,
|
26
|
+
# or 0.0 otherwise.
|
27
|
+
#
|
28
|
+
# @return [Float]
|
29
|
+
def priority
|
30
|
+
return @custom_priority.to_f if @custom_priority
|
31
|
+
type == "error" ? -1.0 : 0.0
|
32
|
+
end
|
33
|
+
|
34
|
+
# @!scope class
|
35
|
+
# @!method new(type:, text:, priority:)
|
36
|
+
# Constructs and freezes the message object
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# Message.new type: "info", text: "foo", priority: 3
|
40
|
+
#
|
41
|
+
# @param [#to_s] type
|
42
|
+
# @param [#to_s] text
|
43
|
+
# @param [#to_f] priority
|
44
|
+
# optional custom priority of the message (the less the higher)
|
45
|
+
#
|
46
|
+
# @return [ServiceObjects::Message]
|
47
|
+
# the frozen message object
|
48
|
+
def initialize(type:, text:, priority: nil)
|
49
|
+
@type = type.to_s.freeze
|
50
|
+
@text = text.to_s.freeze
|
51
|
+
@custom_priority = priority
|
52
|
+
freeze
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checks equality of the message to the other object
|
56
|
+
#
|
57
|
+
# @param [Object] other
|
58
|
+
#
|
59
|
+
# @return [true]
|
60
|
+
# if messages has the same attributes
|
61
|
+
# @return [false]
|
62
|
+
# if the other object is not a message or has different argument(s)
|
63
|
+
def ==(other)
|
64
|
+
(self <=> other) == 0
|
65
|
+
end
|
66
|
+
|
67
|
+
# Compares the message with the other object
|
68
|
+
#
|
69
|
+
# @param [Object] other
|
70
|
+
#
|
71
|
+
# @return [-1, 0, 1]
|
72
|
+
# if the argument is a message
|
73
|
+
# @return [nil]
|
74
|
+
# if the other object is not a message
|
75
|
+
def <=>(other)
|
76
|
+
return unless other.is_a? self.class
|
77
|
+
[:priority, :type, :text]
|
78
|
+
.map { |key| __compare_to__ other, by: key }
|
79
|
+
.detect { |value| value } || 0
|
80
|
+
end
|
81
|
+
|
82
|
+
# Converts the message object to hash
|
83
|
+
#
|
84
|
+
# @return [Hash]
|
85
|
+
def to_h
|
86
|
+
{ type: type, text: text }
|
87
|
+
end
|
88
|
+
|
89
|
+
# Converts the message object to json
|
90
|
+
#
|
91
|
+
# @return [String]
|
92
|
+
def to_json
|
93
|
+
to_h.to_json
|
94
|
+
end
|
95
|
+
|
96
|
+
# A human-readable representation of the message
|
97
|
+
#
|
98
|
+
# @return [String]
|
99
|
+
def inspect
|
100
|
+
%W(
|
101
|
+
#<ServiceObjects::Message:#{ object_id }
|
102
|
+
type=\"#{ type }\"
|
103
|
+
text=\"#{ text }\"
|
104
|
+
priority=#{ priority }>
|
105
|
+
).join(" ")
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def __compare_to__(other, by:)
|
111
|
+
value = (send(by) <=> other.send(by))
|
112
|
+
value == 0 ? nil : value
|
113
|
+
end
|
114
|
+
|
115
|
+
end # class Message
|
116
|
+
|
117
|
+
end # module ServiceObjects
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "naught"
|
3
|
+
|
4
|
+
module ServiceObjects #:nodoc:
|
5
|
+
|
6
|
+
# Implements Null Object pattern
|
7
|
+
#
|
8
|
+
# @note
|
9
|
+
# The constant is a singleton black hole object that returns
|
10
|
+
# itself to any method call
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# ServiceObjects::NULL.respond_to? :arbitrary_method
|
14
|
+
# # => true
|
15
|
+
#
|
16
|
+
# ServiceObjects::NULL.arbitrary_method
|
17
|
+
# # => ServiceObjects::NULL
|
18
|
+
#
|
19
|
+
# @see https://github.com/avdi/naught
|
20
|
+
# documentation for the 'naught' gem by Avdi Grimm
|
21
|
+
NULL = Naught.build do |config|
|
22
|
+
config.black_hole
|
23
|
+
config.singleton
|
24
|
+
end.instance
|
25
|
+
|
26
|
+
end # module ServiceObjects
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module ServiceObjects
|
4
|
+
|
5
|
+
# Utilitites for ServiceObjects module
|
6
|
+
module Utils
|
7
|
+
|
8
|
+
# Constructor of hash with keys, symbolized at any level
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# NormalHash.from { "foo" => "bar", "bar" => { "baz", "bar" => "baz" } }
|
12
|
+
# # => { foo: "baz", bar: { "baz", bar: "baz" } }
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
module NormalHash
|
16
|
+
|
17
|
+
# Recursively symbolizes keys of hash at any level
|
18
|
+
#
|
19
|
+
# @param [Hash] hash
|
20
|
+
#
|
21
|
+
# @return [Hash]
|
22
|
+
def self.from(hash)
|
23
|
+
return {} unless hash.is_a? Hash
|
24
|
+
|
25
|
+
hash.inject({}) do |out, (key, val)|
|
26
|
+
out.merge(key.to_sym => (val.is_a?(Hash) ? from(val) : val))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end # class NormalHash
|
31
|
+
|
32
|
+
end # module Utils
|
33
|
+
|
34
|
+
end # module ServiceObjects
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "service_objects/null"
|
3
|
+
require "service_objects/utils/normal_hash"
|
4
|
+
require "service_objects/listener"
|
5
|
+
require "service_objects/message"
|
6
|
+
require "service_objects/invalid"
|
7
|
+
require "service_objects/base"
|
8
|
+
|
9
|
+
# The namespace for the 'service_objects' gem code
|
10
|
+
module ServiceObjects
|
11
|
+
|
12
|
+
end # module ServiceObjects
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "service_objects/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "service_objects"
|
6
|
+
s.version = ServiceObjects::VERSION.dup
|
7
|
+
s.author = "Andrew Kozin"
|
8
|
+
s.email = "andrew.kozin@gmail.com"
|
9
|
+
s.homepage = "https://github.com/nepalez/service_objects"
|
10
|
+
s.summary = "Service objects (interactors)."
|
11
|
+
s.description = "Base class for objects, that implements" \
|
12
|
+
" both the Interactor and Observer design patterns."
|
13
|
+
s.license = "MIT"
|
14
|
+
s.platform = Gem::Platform::RUBY
|
15
|
+
s.required_ruby_version = "~> 2.1"
|
16
|
+
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
19
|
+
s.test_files = Dir["spec/**/*.rb"]
|
20
|
+
s.extra_rdoc_files = Dir["README.md", "LICENSE", "config/metrics/STYLEGUIDE"]
|
21
|
+
|
22
|
+
s.add_runtime_dependency "activemodel", "~> 4.1"
|
23
|
+
s.add_runtime_dependency "extlib", "~> 0.9"
|
24
|
+
s.add_runtime_dependency "naught", "~> 1.0"
|
25
|
+
s.add_runtime_dependency "wisper", "~> 1.6"
|
26
|
+
|
27
|
+
s.add_development_dependency "hexx-suit", "~> 0.0"
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Loads the RSpec test suit.
|
4
|
+
require "hexx-suit"
|
5
|
+
|
6
|
+
# Loads the RSpec support files.
|
7
|
+
Dir.chdir File.expand_path("..", __FILE__) do
|
8
|
+
Dir["./support/config/*.rb"].each { |file| require file }
|
9
|
+
end
|
10
|
+
|
11
|
+
# Loads runtime metrics in the current scope
|
12
|
+
Hexx::Suit.load_metrics_for(self)
|
13
|
+
|
14
|
+
# Loads the code of the module.
|
15
|
+
require "service_objects"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe ServiceObjects::Base do
|
4
|
+
|
5
|
+
let(:publisher) { Wisper::Publisher }
|
6
|
+
let(:helpers) { ServiceObjects::Helpers }
|
7
|
+
|
8
|
+
it "is Dependable" do
|
9
|
+
expect(described_class).to be_kind_of helpers::Dependable
|
10
|
+
end
|
11
|
+
|
12
|
+
it "is Parameterized" do
|
13
|
+
expect(described_class).to be_kind_of helpers::Parameterized
|
14
|
+
end
|
15
|
+
|
16
|
+
it "includes Messages helper" do
|
17
|
+
expect(described_class).to include helpers::Messages
|
18
|
+
end
|
19
|
+
|
20
|
+
it "includes Parameters helper" do
|
21
|
+
expect(described_class).to include helpers::Parameters
|
22
|
+
end
|
23
|
+
|
24
|
+
it "includes Validations helper" do
|
25
|
+
expect(described_class).to include helpers::Validations
|
26
|
+
end
|
27
|
+
|
28
|
+
it "includes Exceptions helper" do
|
29
|
+
expect(described_class).to include helpers::Exceptions
|
30
|
+
end
|
31
|
+
|
32
|
+
it "includes Wisper::Publisher" do
|
33
|
+
expect(described_class).to include publisher
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#run" do
|
37
|
+
|
38
|
+
it "is defined" do
|
39
|
+
expect(subject).to respond_to(:run).with(0).arguments
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end # describe ServiceObjects::Base
|