simple_service 1.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/.gitignore +17 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/LICENSE.txt +22 -0
- data/README.md +139 -0
- data/Rakefile +11 -0
- data/example/hello_world.rb +30 -0
- data/lib/simple_service/argument_validator.rb +57 -0
- data/lib/simple_service/command.rb +48 -0
- data/lib/simple_service/exceptions.rb +8 -0
- data/lib/simple_service/organizer.rb +38 -0
- data/lib/simple_service/service_base.rb +60 -0
- data/lib/simple_service/version.rb +3 -0
- data/lib/simple_service.rb +10 -0
- data/simple_service.gemspec +26 -0
- data/spec/lib/argument_validator_spec.rb +70 -0
- data/spec/lib/command_spec.rb +71 -0
- data/spec/lib/organizer_spec.rb +96 -0
- data/spec/simple_service_spec.rb +5 -0
- data/spec/spec_helper.rb +10 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: af47217423dfde7e55b44539cb67335bf973e827
|
4
|
+
data.tar.gz: 6f6955116fc1de25feb69748943bbd693f032fe1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a29bd75670cad144a923b95738dc20966cd2af9c5c777ee64603f7b498d16ac948e4ed0f1003cd2e15a4dc4d0d9e34c9f03a2782fdb2fb02876d973efb80a4a4
|
7
|
+
data.tar.gz: 078d232dc698930fec97d656566edce4d29007e144849f03d14c263e0f0a220eb487525bd3e37f9d0822c2daa2dbe4965b46136f60dc6ccdbb044564a0c1ceae
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Jarrod Spillers
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Jrod
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# SimpleService
|
2
|
+
|
3
|
+
[](https://codeclimate.com/github/jspillers/simple_service)
|
4
|
+
[](https://codeclimate.com/github/jspillers/simple_service)
|
5
|
+
[](https://travis-ci.org/jspillers/simple_service)
|
6
|
+
|
7
|
+
SimpleService gives you a way to organize service objects such that they adhere
|
8
|
+
to the single responsibility principle. Instead of writing large service objects
|
9
|
+
that perform multiple tasks, SimpleService allows you to breakdown tasks into a
|
10
|
+
set a sequentially performed Command objects. When properly designed, these command
|
11
|
+
objects can be reused in multiple different organizers minimizing code duplication.
|
12
|
+
|
13
|
+
When an organizer is instantiated a hash of arguments is passed in. This hash
|
14
|
+
is referred to as the context. The context hash is carried along throughout
|
15
|
+
the sequence of command executions and modified by each command. After a
|
16
|
+
successful run, the entire context hash (or a specified subset) is returned.
|
17
|
+
|
18
|
+
First, setup an Organizer class. An Organizer needs the following things defined:
|
19
|
+
|
20
|
+
* expects: keys that are required to be passed into initialize when an instance
|
21
|
+
of organizer is created. If not defined the organizer will accept arbitrary arguments.
|
22
|
+
* returns: keys that will be returned when the organizer has executed all of its commands
|
23
|
+
* commands: classes that define all the steps that the organizer will execute. The organizer
|
24
|
+
will call #execute on each command in order and the context hash is passed to each of
|
25
|
+
these commands. Any keys within the context that are modified will be merged back into
|
26
|
+
the organizer and passed along to the next command.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
class ProcessSomethingComplex < SimpleService::Organizer
|
30
|
+
|
31
|
+
# optional - ensures the following keys are provided during instantiation
|
32
|
+
# leave out to accept any arguments/keys
|
33
|
+
expects :something, :another_thing
|
34
|
+
|
35
|
+
# optional - specifies which keys get returned after #execute is called on
|
36
|
+
# an organizer instance
|
37
|
+
returns :modified_thing
|
38
|
+
|
39
|
+
# what steps comprise this service
|
40
|
+
# #execute will be called on an instance of each class in sequence
|
41
|
+
commands DoSomethingImportant, DoAnotherStep
|
42
|
+
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
Next, define all command classes that make up the service. Each should inherit
|
47
|
+
from SimpleService::Command and define similar things to the organizer:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class DoSomethingImportant < SimpleService::Command
|
51
|
+
|
52
|
+
# optional - creates getter/setter for each key specified,
|
53
|
+
# leave blank to accept arbitrary args
|
54
|
+
expects :something
|
55
|
+
|
56
|
+
# optional - creates getter/setter for each key specified,
|
57
|
+
# leave blank to return entire context hash
|
58
|
+
returns :modified_something, :another_thing
|
59
|
+
|
60
|
+
# required - this is where the work gets done, should only
|
61
|
+
# do one thing (single responsibility principle)
|
62
|
+
# getters and setters are available for each key specified
|
63
|
+
# in expects and returns. If not using expects and returns
|
64
|
+
# simply interact with the context hash directly
|
65
|
+
def execute
|
66
|
+
# uses getters and setters to modify the context
|
67
|
+
self.modified_something = self.something.to_i + 1
|
68
|
+
|
69
|
+
# or act directly on the context hash
|
70
|
+
context[:modified_something] = context[:something].to_i + 1
|
71
|
+
|
72
|
+
# no need to return anything specific, either the keys
|
73
|
+
# specified in returns will be returned or the entire
|
74
|
+
# context if no returns are defined
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
class DoSomethingImportant < SimpleService::Command
|
80
|
+
...
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
84
|
+
## Usage
|
85
|
+
|
86
|
+
Using the service is straight forward - just instantiate it, passing in the
|
87
|
+
intial context hash, and then call execute.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
starting_context = {
|
91
|
+
something: '1',
|
92
|
+
:another_thing: AnotherThing.new
|
93
|
+
}
|
94
|
+
modified_context = ProcessSomethingComplex.new(starting_context).execute
|
95
|
+
|
96
|
+
modified_context[:modified_thing] # => 2
|
97
|
+
```
|
98
|
+
|
99
|
+
If you are using this with a Rails app, placing top level services in
|
100
|
+
app/services/ and all commands in app/services/commands/ is recommended. If
|
101
|
+
not using rails, a similar structure would also be recommended.
|
102
|
+
|
103
|
+
## Inspiration and Rationale
|
104
|
+
|
105
|
+
This gem is heavily inspired by two very nice gems: [mutations](https://github.com/cypriss/mutations) and
|
106
|
+
[light-service](https://github.com/adomokos/light-service).
|
107
|
+
|
108
|
+
Mutations is a great gem, but lacks the concept of a top level organizer.
|
109
|
+
LightService brings in the notion of the organizer object, but doesn't create
|
110
|
+
instances of its action objects (what are referred to as commands here). Using
|
111
|
+
instances rather than class level execute definitions allows the use of private
|
112
|
+
methods within the command for more complex commands that still do a single thing.
|
113
|
+
|
114
|
+
The other goal of this gem is to do as little as possible above and beyond
|
115
|
+
just using plain old Ruby objects (PORO's). Things like error handling, logging,
|
116
|
+
and context status will be left up to the individual to implement in a way that
|
117
|
+
best suits their use case.
|
118
|
+
|
119
|
+
## Installation
|
120
|
+
|
121
|
+
Add this line to your application's Gemfile:
|
122
|
+
|
123
|
+
gem 'simple_service'
|
124
|
+
|
125
|
+
And then execute:
|
126
|
+
|
127
|
+
$ bundle
|
128
|
+
|
129
|
+
Or install it yourself as:
|
130
|
+
|
131
|
+
$ gem install simple_service
|
132
|
+
|
133
|
+
## Contributing
|
134
|
+
|
135
|
+
1. Fork it
|
136
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
137
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
138
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
139
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'pry'
|
3
|
+
require 'simple_service'
|
4
|
+
|
5
|
+
class ConcatName < SimpleService::Command
|
6
|
+
expects :first_name, :last_name
|
7
|
+
returns :name
|
8
|
+
|
9
|
+
def execute
|
10
|
+
self.name = "#{first_name} #{last_name}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class CreateHelloString < SimpleService::Command
|
15
|
+
expects :name
|
16
|
+
returns :hello
|
17
|
+
|
18
|
+
def execute
|
19
|
+
self.hello = "#{name}, say hello world!"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class SayHello < SimpleService::Organizer
|
24
|
+
expects :first_name, :last_name
|
25
|
+
returns :hello
|
26
|
+
commands ConcatName, CreateHelloString
|
27
|
+
end
|
28
|
+
|
29
|
+
result = SayHello.new(first_name: 'Ruby', last_name: 'Gem').execute
|
30
|
+
puts result[:hello]
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module SimpleService
|
2
|
+
class ArgumentValidator
|
3
|
+
|
4
|
+
attr_accessor :context, :expects, :commands
|
5
|
+
|
6
|
+
def initialize(opts)
|
7
|
+
@context = opts[:context]
|
8
|
+
@expects = opts[:expects]
|
9
|
+
@commands = opts[:commands]
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute
|
13
|
+
validate_expected_arguments
|
14
|
+
validate_commands_not_empty
|
15
|
+
validate_commands_properly_inherit
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def validate_expected_arguments
|
22
|
+
arguments_not_included = []
|
23
|
+
|
24
|
+
expects.each do |expected_arg|
|
25
|
+
arguments_not_included << expected_arg unless context.has_key?(expected_arg)
|
26
|
+
end
|
27
|
+
|
28
|
+
if arguments_not_included.any?
|
29
|
+
error_msg = 'keys required by the organizer but not found in the context: ' +
|
30
|
+
arguments_not_included.join(', ')
|
31
|
+
raise ExpectedKeyError, error_msg
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate_commands_not_empty
|
36
|
+
if commands.nil? || commands.empty?
|
37
|
+
error_msg = 'This Organizer class does not contain any command definitions'
|
38
|
+
raise SimpleService::OrganizerCommandsNotDefinedError, error_msg
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_commands_properly_inherit
|
44
|
+
invalid_command_inherit = commands.select do |command|
|
45
|
+
# does the command class inherit from SimpleService::Command
|
46
|
+
!(command.ancestors.include?(SimpleService::Command))
|
47
|
+
end
|
48
|
+
|
49
|
+
if invalid_command_inherit.any?
|
50
|
+
error_msg = invalid_command_inherit.join(', ') +
|
51
|
+
' - must inherit from SimpleService::Command'
|
52
|
+
raise SimpleService::CommandParentClassInvalidError, error_msg
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,48 @@
|
|
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_execute_chain
|
12
|
+
define_getters_and_setters
|
13
|
+
end
|
14
|
+
|
15
|
+
# execute is where the command's behavior is defined
|
16
|
+
# execute should be overriden by whatever class inherits from
|
17
|
+
# this class
|
18
|
+
def execute
|
19
|
+
error_msg = "#{self.class} - does not define an execute method"
|
20
|
+
raise SimpleService::ExecuteNotDefinedError , error_msg
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def all_specified_context_keys
|
26
|
+
(expects + returns)
|
27
|
+
end
|
28
|
+
|
29
|
+
def define_getters_and_setters
|
30
|
+
all_specified_context_keys.each do |key|
|
31
|
+
self.class.class_eval do
|
32
|
+
|
33
|
+
# getter
|
34
|
+
define_method key do
|
35
|
+
self.context[key]
|
36
|
+
end
|
37
|
+
|
38
|
+
# setter
|
39
|
+
define_method "#{key}=" do |val|
|
40
|
+
self.context[key] = val
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module SimpleService
|
2
|
+
|
3
|
+
class OrganizerCommandsNotDefinedError < StandardError; end;
|
4
|
+
class CommandParentClassInvalidError < StandardError; end;
|
5
|
+
class ExpectedKeyError < StandardError; end;
|
6
|
+
class ExecuteNotDefinedError < StandardError; end;
|
7
|
+
class ReturnKeyError < StandardError; end;
|
8
|
+
end
|
@@ -0,0 +1,38 @@
|
|
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 = context
|
11
|
+
|
12
|
+
ArgumentValidator.new(
|
13
|
+
context: context,
|
14
|
+
expects: expects,
|
15
|
+
returns: returns,
|
16
|
+
commands: commands
|
17
|
+
).execute
|
18
|
+
|
19
|
+
setup_execute_chain
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.commands(*args)
|
23
|
+
@commands = args
|
24
|
+
end
|
25
|
+
|
26
|
+
def commands
|
27
|
+
self.class.instance_variable_get('@commands')
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute
|
31
|
+
commands.each do |command|
|
32
|
+
@context.merge!(command.new(context).execute)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module SimpleService
|
2
|
+
module ServiceBase
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def expects(*args)
|
6
|
+
@expects = args
|
7
|
+
end
|
8
|
+
|
9
|
+
def returns(*args)
|
10
|
+
@returns = args
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
|
16
|
+
def setup_execute_chain
|
17
|
+
self.class.class_eval do
|
18
|
+
|
19
|
+
# grab the method object and hold onto it here
|
20
|
+
execute_method = instance_method(:execute)
|
21
|
+
|
22
|
+
# redefine the execute method, call the existing execute method object,
|
23
|
+
# and then run return key checking... allows user to implement execute in
|
24
|
+
# their individual command classes without having to call super or any
|
25
|
+
# other method to return only specific context keys
|
26
|
+
define_method :execute do
|
27
|
+
execute_method.bind(self).call
|
28
|
+
find_specified_return_keys
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_specified_return_keys
|
34
|
+
if returns.nil? || returns.empty?
|
35
|
+
context
|
36
|
+
else
|
37
|
+
returns.inject({}) do |to_return, return_param|
|
38
|
+
if context.has_key?(return_param)
|
39
|
+
to_return[return_param] = context[return_param]
|
40
|
+
else
|
41
|
+
error_msg = "#{self.class} tried to return #{return_param}, but it did not exist in the context: #{context.inspect}"
|
42
|
+
raise ReturnKeyError, error_msg
|
43
|
+
end
|
44
|
+
|
45
|
+
to_return
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def expects
|
51
|
+
self.class.instance_variable_get('@expects') || []
|
52
|
+
end
|
53
|
+
|
54
|
+
def returns
|
55
|
+
self.class.instance_variable_get('@returns') || []
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'simple_service/service_base'
|
2
|
+
require 'simple_service/argument_validator'
|
3
|
+
require 'simple_service/command'
|
4
|
+
require 'simple_service/exceptions'
|
5
|
+
require 'simple_service/organizer'
|
6
|
+
require 'simple_service/version'
|
7
|
+
|
8
|
+
module SimpleService
|
9
|
+
# Your code goes here...
|
10
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'simple_service/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'simple_service'
|
8
|
+
spec.version = SimpleService::VERSION
|
9
|
+
spec.authors = ['Jarrod Spillers']
|
10
|
+
spec.email = ['jarrod@stacktact.com']
|
11
|
+
spec.description = %q{A minimal service object composer with support for individual commands and top level organizer objects}
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = 'https://github.com/jspillers/simple_service'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.4.2'
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.2.0'
|
24
|
+
spec.add_development_dependency 'pry', '~> 0.10.1'
|
25
|
+
spec.add_development_dependency 'codeclimate-test-reporter'
|
26
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleService::ArgumentValidator do
|
4
|
+
|
5
|
+
context 'execute' do
|
6
|
+
|
7
|
+
class FooCommand < SimpleService::Command
|
8
|
+
def execute; true; end
|
9
|
+
end
|
10
|
+
|
11
|
+
class BadInheritanceCommand
|
12
|
+
def execute; true; end
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:valid_args) {{
|
16
|
+
context: { foo: 'bar'},
|
17
|
+
expects: [:foo],
|
18
|
+
returns: [:foo],
|
19
|
+
commands: [FooCommand]
|
20
|
+
}}
|
21
|
+
|
22
|
+
context 'when all arguments are valid' do
|
23
|
+
|
24
|
+
it 'does not raise error' do
|
25
|
+
expect {
|
26
|
+
SimpleService::ArgumentValidator.new(valid_args).execute
|
27
|
+
}.to_not raise_error
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'returns true' do
|
31
|
+
expect(
|
32
|
+
SimpleService::ArgumentValidator.new(valid_args).execute
|
33
|
+
).to eql true
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'raises error when context does not contain expected keys' do
|
39
|
+
expect {
|
40
|
+
args = valid_args.merge(expects: [:baz])
|
41
|
+
SimpleService::ArgumentValidator.new(args).execute
|
42
|
+
}.to raise_error(
|
43
|
+
SimpleService::ExpectedKeyError,
|
44
|
+
'keys required by the organizer but not found in the context: baz'
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'raises error when commands are not defined' do
|
49
|
+
expect {
|
50
|
+
args = valid_args.merge(commands: nil)
|
51
|
+
SimpleService::ArgumentValidator.new(args).execute
|
52
|
+
}.to raise_error(
|
53
|
+
SimpleService::OrganizerCommandsNotDefinedError,
|
54
|
+
'This Organizer class does not contain any command definitions'
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'raises error when commands do not inherit from SimpleService::Command' do
|
59
|
+
expect {
|
60
|
+
args = valid_args.merge(commands: [BadInheritanceCommand])
|
61
|
+
SimpleService::ArgumentValidator.new(args).execute
|
62
|
+
}.to raise_error(
|
63
|
+
SimpleService::CommandParentClassInvalidError,
|
64
|
+
'BadInheritanceCommand - must inherit from SimpleService::Command'
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleService::Command do
|
4
|
+
|
5
|
+
class ValidCommand < SimpleService::Command
|
6
|
+
|
7
|
+
expects :foo, :bar
|
8
|
+
returns :bar, :baz
|
9
|
+
|
10
|
+
def execute
|
11
|
+
context.merge!(
|
12
|
+
bar: 'modified',
|
13
|
+
baz: 'blah'
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class NoExecuteDefinedCommand < SimpleService::Command
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#execute' do
|
23
|
+
|
24
|
+
context 'when #returns is not empty' do
|
25
|
+
it 'returns the correct keys from the context' do
|
26
|
+
expect(
|
27
|
+
ValidCommand.new(foo: 'blah', bar: 'meh').execute
|
28
|
+
).to eql(bar: 'modified', baz: 'blah')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'raises error' do
|
33
|
+
|
34
|
+
it 'when command does not define an execute method' do
|
35
|
+
expect {
|
36
|
+
NoExecuteDefinedCommand.new.execute
|
37
|
+
}.to raise_error(
|
38
|
+
SimpleService::ExecuteNotDefinedError,
|
39
|
+
'NoExecuteDefinedCommand - does not define an execute method'
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'context' do
|
48
|
+
|
49
|
+
it 'defines getters for each expected key' do
|
50
|
+
expect(
|
51
|
+
ValidCommand.new(foo: 'blah', bar: 'meh')
|
52
|
+
).to respond_to :foo
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'defines setters for each expected key' do
|
56
|
+
command = ValidCommand.new(foo: 'blah', bar: 'meh')
|
57
|
+
command.foo = 'changed'
|
58
|
+
command.bar = 'changed'
|
59
|
+
|
60
|
+
expect(command.context).to eql({ foo: 'changed', bar: 'changed' })
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'getter updates @context' do
|
64
|
+
command = ValidCommand.new(foo: 'blah', bar: 'meh')
|
65
|
+
command.foo = 'changed'
|
66
|
+
expect(command.context).to eql({ foo: 'changed', bar: 'meh'})
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleService::Organizer do
|
4
|
+
|
5
|
+
context 'classes with expects and returns' do
|
6
|
+
|
7
|
+
class TestCommandOne < SimpleService::Command
|
8
|
+
expects :foo
|
9
|
+
returns :foo, :bar
|
10
|
+
def execute
|
11
|
+
context.merge!(bar: 'bar')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class TestCommandTwo < SimpleService::Command
|
16
|
+
expects :foo, :bar
|
17
|
+
returns :foo, :bar, :baz
|
18
|
+
def execute
|
19
|
+
context.merge!(baz: 'baz')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class TestOrganizer < SimpleService::Organizer
|
24
|
+
expects :foo
|
25
|
+
returns :foo, :bar, :baz
|
26
|
+
commands TestCommandOne, TestCommandTwo
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#execute' do
|
30
|
+
it 'returns the correct hash' do
|
31
|
+
expect(
|
32
|
+
TestOrganizer.new(foo: 'foo').execute
|
33
|
+
).to eql(foo: 'foo', bar: 'bar', baz: 'baz')
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'classes with only expects' do
|
41
|
+
|
42
|
+
class TestCommandThree < SimpleService::Command
|
43
|
+
expects :foo
|
44
|
+
def execute
|
45
|
+
context.merge!(bar: 'bar')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class TestCommandFour < SimpleService::Command
|
50
|
+
expects :foo, :bar
|
51
|
+
def execute
|
52
|
+
context.merge!(baz: 'baz')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class TestOrganizerTwo < SimpleService::Organizer
|
57
|
+
expects :foo
|
58
|
+
commands TestCommandThree, TestCommandFour
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#execute' do
|
62
|
+
it 'returns the entire context' do
|
63
|
+
expect(
|
64
|
+
TestOrganizerTwo.new(foo: 'foo', extra: 'extra').execute
|
65
|
+
).to eql(foo: 'foo', bar: 'bar', baz: 'baz', extra: 'extra')
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'service using getters and setters' do
|
73
|
+
|
74
|
+
class GetterSetterCommand < SimpleService::Command
|
75
|
+
expects :foo, :bar
|
76
|
+
returns :baz
|
77
|
+
def execute
|
78
|
+
self.baz = self.foo
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class GetterSetterOrganizer < SimpleService::Organizer
|
83
|
+
expects :foo, :bar
|
84
|
+
returns :baz
|
85
|
+
commands GetterSetterCommand
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns the correct hash' do
|
89
|
+
expect(
|
90
|
+
GetterSetterOrganizer.new(foo: 'baz', bar: 'bar').execute
|
91
|
+
).to eql({ baz: 'baz' })
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib', 'simple_service')
|
2
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
3
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
require 'codeclimate-test-reporter'
|
6
|
+
CodeClimate::TestReporter.start
|
7
|
+
|
8
|
+
require 'pry'
|
9
|
+
require 'rspec'
|
10
|
+
require 'simple_service'
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_service
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jarrod Spillers
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-04-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 10.4.2
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 10.4.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.2.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.2.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.10.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.10.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: codeclimate-test-reporter
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: A minimal service object composer with support for individual commands
|
84
|
+
and top level organizer objects
|
85
|
+
email:
|
86
|
+
- jarrod@stacktact.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- .rspec
|
93
|
+
- .travis.yml
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- example/hello_world.rb
|
100
|
+
- lib/simple_service.rb
|
101
|
+
- lib/simple_service/argument_validator.rb
|
102
|
+
- lib/simple_service/command.rb
|
103
|
+
- lib/simple_service/exceptions.rb
|
104
|
+
- lib/simple_service/organizer.rb
|
105
|
+
- lib/simple_service/service_base.rb
|
106
|
+
- lib/simple_service/version.rb
|
107
|
+
- simple_service.gemspec
|
108
|
+
- spec/lib/argument_validator_spec.rb
|
109
|
+
- spec/lib/command_spec.rb
|
110
|
+
- spec/lib/organizer_spec.rb
|
111
|
+
- spec/simple_service_spec.rb
|
112
|
+
- spec/spec_helper.rb
|
113
|
+
homepage: https://github.com/jspillers/simple_service
|
114
|
+
licenses:
|
115
|
+
- MIT
|
116
|
+
metadata: {}
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 2.4.2
|
134
|
+
signing_key:
|
135
|
+
specification_version: 4
|
136
|
+
summary: A minimal service object composer with support for individual commands and
|
137
|
+
top level organizer objects
|
138
|
+
test_files:
|
139
|
+
- spec/lib/argument_validator_spec.rb
|
140
|
+
- spec/lib/command_spec.rb
|
141
|
+
- spec/lib/organizer_spec.rb
|
142
|
+
- spec/simple_service_spec.rb
|
143
|
+
- spec/spec_helper.rb
|