simple_service 1.4.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/README.md +111 -123
- data/lib/simple_service.rb +78 -8
- data/lib/simple_service/configuration.rb +9 -0
- data/lib/simple_service/result.rb +56 -0
- data/lib/simple_service/version.rb +1 -1
- data/spec/simple_service_spec.rb +176 -1
- data/spec/support/basic_service.rb +19 -0
- data/spec/support/command_one.rb +21 -0
- data/spec/support/command_two.rb +15 -0
- data/spec/support/looping_service.rb +20 -0
- metadata +12 -28
- data/example/hello_world.rb +0 -29
- data/example/nested_organizer.rb +0 -29
- data/example/nested_services.rb +0 -29
- data/example/override_organizer_call_method.rb +0 -39
- data/lib/simple_service/command.rb +0 -28
- data/lib/simple_service/commands/validates_commands_not_empty.rb +0 -16
- data/lib/simple_service/commands/validates_commands_properly_inherit.rb +0 -24
- data/lib/simple_service/commands/validates_expected_keys.rb +0 -19
- data/lib/simple_service/context.rb +0 -32
- data/lib/simple_service/ensure_organizer_is_valid.rb +0 -22
- data/lib/simple_service/exceptions.rb +0 -10
- data/lib/simple_service/organizer.rb +0 -113
- data/lib/simple_service/service_base.rb +0 -169
- data/spec/lib/command_spec.rb +0 -111
- data/spec/lib/commands/validates_commands_not_empty_spec.rb +0 -28
- data/spec/lib/commands/validates_commands_properly_inherit_spec.rb +0 -34
- data/spec/lib/commands/validates_expected_keys_spec.rb +0 -60
- data/spec/lib/ensure_organizer_is_valid_spec.rb +0 -17
- data/spec/lib/organizer_spec.rb +0 -216
@@ -1,28 +0,0 @@
|
|
1
|
-
module SimpleService
|
2
|
-
class Command
|
3
|
-
|
4
|
-
include ServiceBase::InstanceMethods
|
5
|
-
extend ServiceBase::ClassMethods
|
6
|
-
|
7
|
-
attr_accessor :context
|
8
|
-
|
9
|
-
def initialize(context={})
|
10
|
-
@context = context
|
11
|
-
setup_call_chain
|
12
|
-
define_getters_and_setters
|
13
|
-
|
14
|
-
unless skip_validation
|
15
|
-
ValidatesExpectedKeys.new(provided_keys: context.keys).call
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# call is where the command's behavior is defined
|
20
|
-
# call should be overriden by whatever class inherits from
|
21
|
-
# this class
|
22
|
-
def call
|
23
|
-
error_msg = "#{self.class} - does not define a call method"
|
24
|
-
raise SimpleService::CallNotDefinedError , error_msg
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module SimpleService
|
2
|
-
class ValidatesCommandsNotEmpty < Command
|
3
|
-
|
4
|
-
expects :provided_commands
|
5
|
-
|
6
|
-
skip_validation true # prevent infinite loop
|
7
|
-
|
8
|
-
def call
|
9
|
-
if provided_commands.nil? || provided_commands.empty?
|
10
|
-
error_msg = 'This Organizer class does not contain any command definitions'
|
11
|
-
raise SimpleService::OrganizerCommandsNotDefinedError, error_msg
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
16
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module SimpleService
|
2
|
-
class ValidatesCommandsProperlyInherit < Command
|
3
|
-
|
4
|
-
expects :provided_commands
|
5
|
-
|
6
|
-
skip_validation true # prevent infinite loop
|
7
|
-
|
8
|
-
def call
|
9
|
-
# valid commands inherit from Command and do not inherit from service
|
10
|
-
# reject all valid commands and anything left over is invalid
|
11
|
-
invalid_commands = provided_commands.to_a.reject do |command|
|
12
|
-
command.ancestors.include?(SimpleService::Command) ||
|
13
|
-
command.ancestors.include?(SimpleService::Organizer)
|
14
|
-
end
|
15
|
-
|
16
|
-
if invalid_commands.any?
|
17
|
-
error_msg = invalid_commands.join(', ') +
|
18
|
-
' - must inherit from SimpleService::Command'
|
19
|
-
raise SimpleService::CommandParentClassInvalidError, error_msg
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
24
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module SimpleService
|
2
|
-
class ValidatesExpectedKeys < Command
|
3
|
-
|
4
|
-
expects :expected_keys, :provided_keys
|
5
|
-
|
6
|
-
skip_validation true # prevent infinite loop
|
7
|
-
|
8
|
-
def call
|
9
|
-
arguments_not_included = expected_keys.to_a - provided_keys.to_a
|
10
|
-
|
11
|
-
if arguments_not_included.any?
|
12
|
-
error_msg = 'keys required by the organizer but not found in the context: ' +
|
13
|
-
arguments_not_included.join(', ')
|
14
|
-
raise ExpectedKeyError, error_msg
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module SimpleService
|
2
|
-
|
3
|
-
# TODO: move context logic here?
|
4
|
-
module Context
|
5
|
-
|
6
|
-
def success?
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
def success!
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
def failure?
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
def failure!
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
def update_getters_and_setters
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
def prune!(*keys)
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module SimpleService
|
2
|
-
class EnsureOrganizerIsValid < Organizer
|
3
|
-
|
4
|
-
expects :expected_keys, :provided_keys, :provided_commands
|
5
|
-
|
6
|
-
# makes sure validtion is only done once, prevents an infinite loop
|
7
|
-
skip_validation true
|
8
|
-
|
9
|
-
commands ValidatesExpectedKeys,
|
10
|
-
ValidatesCommandsNotEmpty,
|
11
|
-
ValidatesCommandsProperlyInherit
|
12
|
-
|
13
|
-
def call
|
14
|
-
super
|
15
|
-
|
16
|
-
# dont return the keys within this internal service
|
17
|
-
# so we dont pollute external service objects
|
18
|
-
context.select { |k,v| !expects.include?(k) }
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
module SimpleService
|
2
|
-
|
3
|
-
class OrganizerCommandsNotDefinedError < StandardError; end;
|
4
|
-
class CommandParentClassInvalidError < StandardError; end;
|
5
|
-
class ExpectedKeyError < StandardError; end;
|
6
|
-
class CallNotDefinedError < StandardError; end;
|
7
|
-
class ReturnKeyError < StandardError; end;
|
8
|
-
class InvalidArgumentError < StandardError; end;
|
9
|
-
|
10
|
-
end
|
@@ -1,113 +0,0 @@
|
|
1
|
-
module SimpleService
|
2
|
-
class Organizer
|
3
|
-
|
4
|
-
include ServiceBase::InstanceMethods
|
5
|
-
extend ServiceBase::ClassMethods
|
6
|
-
|
7
|
-
attr_accessor :context
|
8
|
-
|
9
|
-
def initialize(_context = {})
|
10
|
-
@context = validate_context(_context)
|
11
|
-
|
12
|
-
symbolize_context_keys
|
13
|
-
setup_call_chain
|
14
|
-
define_getters_and_setters
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.commands(*args)
|
18
|
-
@commands = args
|
19
|
-
end
|
20
|
-
|
21
|
-
def commands
|
22
|
-
self.class.instance_variable_get('@commands')
|
23
|
-
end
|
24
|
-
|
25
|
-
def call
|
26
|
-
# underscores used to disambiguate local vars from methods with the same name
|
27
|
-
with_validation do |_commands|
|
28
|
-
_commands.each do |command|
|
29
|
-
|
30
|
-
# halt further command calls if success has been set to false
|
31
|
-
# in a previously called command or halt is set
|
32
|
-
break if context[:success] == false || context[:halt] == true
|
33
|
-
|
34
|
-
# if command class defines "expects" then only feed the command
|
35
|
-
# those keys, otherwise just give it the entire context
|
36
|
-
_context = if command.get_expects.any?
|
37
|
-
{}.tap do |c|
|
38
|
-
command.get_expects.each {|key| c[key] = context[key] }
|
39
|
-
end
|
40
|
-
else
|
41
|
-
context
|
42
|
-
end
|
43
|
-
|
44
|
-
# also merge any optional keys
|
45
|
-
command.get_optional.each do |key|
|
46
|
-
_context[key] = context[key]
|
47
|
-
end
|
48
|
-
|
49
|
-
# instantiate and call the command
|
50
|
-
resulting_context = command.new(_context).call
|
51
|
-
|
52
|
-
# update the master context with the results of the command
|
53
|
-
@context.merge!(resulting_context)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
# allow execution of the service from the class level for those
|
59
|
-
# that prefer that style
|
60
|
-
def self.call(context = {})
|
61
|
-
self.new(context).call
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def validate_context(_context)
|
67
|
-
unless _context.class == Hash
|
68
|
-
raise InvalidArgumentError,
|
69
|
-
"Hash required as argument, but was given a #{_context.class}"
|
70
|
-
end
|
71
|
-
|
72
|
-
_context
|
73
|
-
end
|
74
|
-
|
75
|
-
def with_validation
|
76
|
-
# don't mess with the context if we are doing internal validation
|
77
|
-
add_validation_keys_to_context unless skip_validation
|
78
|
-
|
79
|
-
# ensure that the organizer and commands are setup correctly
|
80
|
-
# by injecting an internal service organizer into the stack of
|
81
|
-
# commands to be executed. Only include this if skip_validation is
|
82
|
-
# not set. Since both user defined and internal services use this code
|
83
|
-
# the skip_validation avoids an infinite loop
|
84
|
-
_commands = skip_validation ? commands : [EnsureOrganizerIsValid] + commands
|
85
|
-
|
86
|
-
yield(_commands)
|
87
|
-
|
88
|
-
# cleanup context keys that are used by the validation so the final
|
89
|
-
# return is clean even if the user does not define "returns" in their
|
90
|
-
# organizer
|
91
|
-
remove_validation_keys_from_context unless skip_validation
|
92
|
-
end
|
93
|
-
|
94
|
-
def add_validation_keys_to_context
|
95
|
-
context.merge!(validation_hash)
|
96
|
-
end
|
97
|
-
|
98
|
-
def remove_validation_keys_from_context
|
99
|
-
validation_hash.keys.each do |key|
|
100
|
-
context.delete(key)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def validation_hash
|
105
|
-
@validation_hash ||= {
|
106
|
-
provided_keys: context.keys,
|
107
|
-
expected_keys: expects,
|
108
|
-
provided_commands: commands
|
109
|
-
}
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
end
|
@@ -1,169 +0,0 @@
|
|
1
|
-
module SimpleService
|
2
|
-
module ServiceBase
|
3
|
-
|
4
|
-
module ClassMethods
|
5
|
-
def expects(*args)
|
6
|
-
@expects = args
|
7
|
-
end
|
8
|
-
|
9
|
-
def get_expects
|
10
|
-
@expects || []
|
11
|
-
end
|
12
|
-
|
13
|
-
def optional(*args)
|
14
|
-
@optional = args
|
15
|
-
end
|
16
|
-
|
17
|
-
def get_optional
|
18
|
-
@optional || []
|
19
|
-
end
|
20
|
-
|
21
|
-
def returns(*args)
|
22
|
-
@returns = args
|
23
|
-
end
|
24
|
-
|
25
|
-
def get_returns
|
26
|
-
@returns || []
|
27
|
-
end
|
28
|
-
|
29
|
-
def skip_validation(skip=true)
|
30
|
-
@skip_validation = skip
|
31
|
-
end
|
32
|
-
|
33
|
-
# allow execution of the service or commands from the
|
34
|
-
# class level for those that prefer that style
|
35
|
-
def call(context = {})
|
36
|
-
new(context).call
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
module InstanceMethods
|
42
|
-
|
43
|
-
def failed?
|
44
|
-
!successful?
|
45
|
-
end
|
46
|
-
|
47
|
-
def halted?
|
48
|
-
context.has_key?(:halt) || context[:halt] == true
|
49
|
-
end
|
50
|
-
|
51
|
-
def successful?
|
52
|
-
!context.has_key?(:success) || context[:success] == true
|
53
|
-
end
|
54
|
-
|
55
|
-
# sets up an "after" filter for #call
|
56
|
-
#
|
57
|
-
# allows user to implement #call in their individual
|
58
|
-
# command and organizer # classes without having to
|
59
|
-
# rely on super or executing another other method
|
60
|
-
# to do post #call housekeeping such as returning
|
61
|
-
# only specific context keys
|
62
|
-
def setup_call_chain
|
63
|
-
self.class.class_eval do
|
64
|
-
|
65
|
-
# grab the method object and hold onto it here
|
66
|
-
call_method = instance_method(:call)
|
67
|
-
|
68
|
-
# redefine the call method, execute the existing call method object,
|
69
|
-
# and then run return key checking...
|
70
|
-
define_method :call do
|
71
|
-
call_method.bind(self).call
|
72
|
-
return_context_with_success_status
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def symbolize_context_keys
|
78
|
-
context.keys.each do |key|
|
79
|
-
context[key.to_sym] = context.delete(key)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def return_context_with_success_status
|
84
|
-
_context = find_specified_return_keys
|
85
|
-
|
86
|
-
# only automatically set context[:success] on Organizers and only if its not already set
|
87
|
-
# by a command calling #failure!
|
88
|
-
if !_context.has_key?(:success) && organizer?
|
89
|
-
_context[:success] = true
|
90
|
-
end
|
91
|
-
|
92
|
-
_context
|
93
|
-
end
|
94
|
-
|
95
|
-
def find_specified_return_keys
|
96
|
-
if returns.nil? || returns.empty? || failed? || halted?
|
97
|
-
context
|
98
|
-
else
|
99
|
-
returns.inject({}) do |to_return, return_param|
|
100
|
-
if context.has_key?(return_param)
|
101
|
-
to_return[return_param] = context[return_param]
|
102
|
-
else
|
103
|
-
error_msg = "#{self.class} tried to return #{return_param}, but it did not exist in the context: #{context.inspect}"
|
104
|
-
raise ReturnKeyError, error_msg
|
105
|
-
end
|
106
|
-
|
107
|
-
to_return
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def expects
|
113
|
-
self.class.get_expects
|
114
|
-
end
|
115
|
-
|
116
|
-
def optional
|
117
|
-
self.class.get_optional
|
118
|
-
end
|
119
|
-
|
120
|
-
def returns
|
121
|
-
self.class.get_returns
|
122
|
-
end
|
123
|
-
|
124
|
-
def skip_validation
|
125
|
-
self.class.instance_variable_get('@skip_validation')
|
126
|
-
end
|
127
|
-
|
128
|
-
def all_context_keys
|
129
|
-
(expects + optional + returns + ['message', 'success']).uniq
|
130
|
-
end
|
131
|
-
|
132
|
-
def organizer?
|
133
|
-
self.class.ancestors.include?(SimpleService::Organizer)
|
134
|
-
end
|
135
|
-
|
136
|
-
def failure!(message = nil)
|
137
|
-
context[:success] = false
|
138
|
-
context[:halt] = true
|
139
|
-
context[:message] = message || 'There was a problem'
|
140
|
-
end
|
141
|
-
|
142
|
-
def success!(message = nil)
|
143
|
-
context[:success] = true
|
144
|
-
context[:halt] = true
|
145
|
-
context[:message] = message || 'Success! Returned early'
|
146
|
-
end
|
147
|
-
|
148
|
-
def define_getters_and_setters
|
149
|
-
all_context_keys.each do |key|
|
150
|
-
self.class.class_eval do
|
151
|
-
|
152
|
-
# getter
|
153
|
-
define_method key do
|
154
|
-
self.context[key]
|
155
|
-
end
|
156
|
-
|
157
|
-
# setter
|
158
|
-
define_method "#{key}=" do |val|
|
159
|
-
self.context[key] = val
|
160
|
-
end
|
161
|
-
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
end
|
167
|
-
|
168
|
-
end
|
169
|
-
end
|