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.
@@ -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