all_systems 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +240 -0
- data/Rakefile +17 -0
- data/lib/all_systems.rb +7 -0
- data/lib/all_systems/errors.rb +6 -0
- data/lib/all_systems/interactor.rb +65 -0
- data/lib/all_systems/version.rb +3 -0
- data/test/interactor/error_test.rb +35 -0
- data/test/interactor/invalid_outcomes_test.rb +63 -0
- data/test/interactor/outcome_test.rb +135 -0
- data/test/interactor/output_test.rb +80 -0
- data/test/interactor/report_helper_test.rb +64 -0
- data/test/test_config.rb +6 -0
- data/usecase.gemspec +25 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 275aae9e309c5a4d6f18fe23d05d4bb7866da786
|
4
|
+
data.tar.gz: ef9a2045793ed0faeeab07c3cf17c9ce7696cf66
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 974da988fad1bb04fb61d9a6a014504aa6f2826472b54ee6bbbbfe022e63b44edb03b4627a16bea5e61a2c4f6901a0052615a1b8b0955f056cef0ce56813fccf
|
7
|
+
data.tar.gz: 4bebef0a58c8863eff9908e638aa7257aafe9b6041afe0f91d835337fff681aa3ff5f3a0b95c65affb3a83e2893f331fc990be02469f6beeb7be23994b91ba7e
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Peter Saxton
|
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,240 @@
|
|
1
|
+
# AllSystems
|
2
|
+
|
3
|
+
**Simple Ruby usecases/interactors/service-object to encapsulate business logic**
|
4
|
+
|
5
|
+
### Well what is it?
|
6
|
+
The three terms above are all used at various times to describe the use of a dedicated object separate to the delivery mechanism (read ApplicationController) to coordinate the calls on several domain objects (such as user models). Service object is sometimes used to describe the encapsulation of an external service that you system uses. E.g. you might have a Stripe service object, so I do not use that term. Also usecase seams to make more sense on a non technical level, so the Login usecase is what the customer does. It is achieved using the Login interactor, the Ruby object. A good starting point is this [article](https://netguru.co/blog/service-objects-in-rails-will-help) as well as the further reading listed. This [article](http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/) helps explain there place in the landscape of objeccts beyond MVC
|
7
|
+
|
8
|
+
### Overview
|
9
|
+
An interactor encapsulates a specific business interaction, often a user interaction, such as `LogIn` or `CreatePost`. The business logic is declared by defining a `go!` method. All possible outcomes are stated by defining a outcomes method. Each instance of the interactor executes the `go!` method once only to produce a single result. The result consists of an outcome and optional output. The outcome is a single :symbol to name the result. The output an array of zero or more values.
|
10
|
+
|
11
|
+
Results are reported within the `go!` method of the interactor.
|
12
|
+
|
13
|
+
```rb
|
14
|
+
Class WelcomeJohn < AllSystems::Interactor
|
15
|
+
def options
|
16
|
+
# Will always succeed
|
17
|
+
[:success]
|
18
|
+
end
|
19
|
+
|
20
|
+
def go!
|
21
|
+
new_user = {:name => 'John Smith'}
|
22
|
+
report :success, new_user
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
welcome = WelcomeJohn.new
|
27
|
+
welcome.result == [:success, new_user]
|
28
|
+
welcome.outcome == :success
|
29
|
+
welcome.output = [new_user]
|
30
|
+
```
|
31
|
+
|
32
|
+
The interactor outcome can then be used to decide response
|
33
|
+
|
34
|
+
```rb
|
35
|
+
welcome.on :success do |user|
|
36
|
+
puts "Hello #{user[:name]}"
|
37
|
+
end
|
38
|
+
```
|
39
|
+
|
40
|
+
### why?
|
41
|
+
|
42
|
+
Such a simple class that a library is almost not needed. I have found its value not in reduced work when making my specific interactors but in reduced testing for those interactor. Don't need to test things like single execution and predicate methods on specific interactors
|
43
|
+
|
44
|
+
## Installation
|
45
|
+
|
46
|
+
Add this line to your application's Gemfile:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
gem 'usecase'
|
50
|
+
```
|
51
|
+
|
52
|
+
And then execute:
|
53
|
+
|
54
|
+
$ bundle
|
55
|
+
|
56
|
+
Or install it yourself as:
|
57
|
+
|
58
|
+
$ gem install usecase
|
59
|
+
|
60
|
+
## Usage
|
61
|
+
|
62
|
+
### Example 1 *Flipping a coin*
|
63
|
+
|
64
|
+
```rb
|
65
|
+
Class FlipCoin < AllSystems::Interactor
|
66
|
+
def outcomes
|
67
|
+
[:heads, :tails]
|
68
|
+
end
|
69
|
+
|
70
|
+
def go!
|
71
|
+
report_tails if [true, false].sample
|
72
|
+
report_heads
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
filp = FlipCoin.new
|
77
|
+
|
78
|
+
flip.result
|
79
|
+
# => [:heads]
|
80
|
+
|
81
|
+
filp.outcome
|
82
|
+
# => :heads
|
83
|
+
|
84
|
+
flip.output?
|
85
|
+
# => []
|
86
|
+
|
87
|
+
flip.heads?
|
88
|
+
# => true
|
89
|
+
|
90
|
+
flip.tails?
|
91
|
+
# => false
|
92
|
+
|
93
|
+
flip.other?
|
94
|
+
# raise UnknownMethodError
|
95
|
+
|
96
|
+
flip.heads do
|
97
|
+
puts "Hooray"
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
Example 2
|
102
|
+
|
103
|
+
```rb
|
104
|
+
class Customer
|
105
|
+
# One of several customer actions
|
106
|
+
class PasswordReset < AllSystems::Interactor
|
107
|
+
def initialize(context, id, params)
|
108
|
+
@context = context
|
109
|
+
@id = id
|
110
|
+
@params = params
|
111
|
+
end
|
112
|
+
|
113
|
+
attr_reader :context, :id, :params
|
114
|
+
|
115
|
+
def outcomes
|
116
|
+
[:succeded, :account_unknown, :user_unknown, :not_permitted, :invalid_details]
|
117
|
+
end
|
118
|
+
|
119
|
+
def go!
|
120
|
+
report_account_unknown id, unless account
|
121
|
+
report_user_unknown if authority.guest?
|
122
|
+
report_not_permitted unless authority == account || authority.admin?
|
123
|
+
report_invalid_details form unless form.valid?
|
124
|
+
account.password = form.password
|
125
|
+
account.save
|
126
|
+
send_email
|
127
|
+
report_succeeded account
|
128
|
+
end
|
129
|
+
|
130
|
+
def send_email
|
131
|
+
context.customer_mailer.password_reset
|
132
|
+
end
|
133
|
+
|
134
|
+
def form
|
135
|
+
@form ||= Form.new params
|
136
|
+
end
|
137
|
+
|
138
|
+
def account
|
139
|
+
@account ||= Customers[id]
|
140
|
+
end
|
141
|
+
|
142
|
+
def authority
|
143
|
+
@authority ||= context.current_user
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# use in controller
|
150
|
+
class CustomerController
|
151
|
+
def password_reset(id)
|
152
|
+
reset = Customer::Password.new(self, id, request.POST['customer'])
|
153
|
+
|
154
|
+
reset.succeeded do |customer| # 204: No Content
|
155
|
+
flash['success'] = 'Password update successful'
|
156
|
+
redirect customer_page(customer), 204
|
157
|
+
end
|
158
|
+
|
159
|
+
reset.unknown_account do |id| # 404: Not found
|
160
|
+
flash['error'] = "account: #{id} not found"
|
161
|
+
redirect customers_page, 404
|
162
|
+
end
|
163
|
+
|
164
|
+
reset.unknow_user do # 401: Unauthenticated
|
165
|
+
flash['error'] = 'Login required'
|
166
|
+
redirect login_page, 401
|
167
|
+
end
|
168
|
+
|
169
|
+
reset.not_permitted do # 403: Forbidden
|
170
|
+
flash['error'] = 'Not authorized'
|
171
|
+
redirect customer_page, 403
|
172
|
+
end
|
173
|
+
|
174
|
+
reset.invalid_details do |form| # 400: bad request
|
175
|
+
status = 400
|
176
|
+
render :new, :locals => {:form => form}
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
```
|
181
|
+
establish, deduce, ascertain, settle, evaluate
|
182
|
+
|
183
|
+
## Docs
|
184
|
+
|
185
|
+
**#go!** `interactor.go! => raise AbstractMethodError`
|
186
|
+
|
187
|
+
Abstract method that will always raise an error. Should be over written in for specific interactors
|
188
|
+
|
189
|
+
**#outcomes** `interactor.outcomes => []`
|
190
|
+
|
191
|
+
Should be over written in for specific interactors to return list of possible outcomes
|
192
|
+
|
193
|
+
**#name** `interactor.name => class_name`
|
194
|
+
|
195
|
+
Returns the name of the class or Anonymous if class not set to constant
|
196
|
+
|
197
|
+
**(private)#report** `interactor.report(outcome, *output) => terminate with result`
|
198
|
+
|
199
|
+
Use within the interactor to report that an outcome state has been reached with optional output. Terminates execution of go!
|
200
|
+
|
201
|
+
**#outcome** `interactor.outcome => symbol`
|
202
|
+
|
203
|
+
Returns the outcome of goning the interactor
|
204
|
+
|
205
|
+
**#outcome?(outcome)** `interactor.outcome?(outcome) => boolean`
|
206
|
+
|
207
|
+
Does the outcome match the predicate outcome.
|
208
|
+
|
209
|
+
**#output** `interactor.output => [*output]`
|
210
|
+
|
211
|
+
Returns an array of output from goning the interactor
|
212
|
+
|
213
|
+
**#on(:outcome)** `interactor.on(:outcome, &block) => block return value`
|
214
|
+
|
215
|
+
If the interactors out come was the same as given here then the output is yielded to the block, else no action.
|
216
|
+
|
217
|
+
|
218
|
+
**#<outcome>?** `interactor.<outcome>? => boolean`
|
219
|
+
|
220
|
+
Was the outcome equal to the method name, raises error if method name not one of possible outcomes
|
221
|
+
|
222
|
+
**#<outcome>** `interactor.<outcome> &block => block_return_value`
|
223
|
+
|
224
|
+
Yields output to block if outcome equal to method name, raises error if method name not one of possible outcomes
|
225
|
+
|
226
|
+
**#report_<outcome>** `interactor.report_<outcome>(*output) => terminate with result`
|
227
|
+
|
228
|
+
Use within the interactor to report that an outcome state has been reached with optional output. Terminates execution of go!
|
229
|
+
|
230
|
+
|
231
|
+
## Upcoming
|
232
|
+
8. actions on class passed to instance *possible to declare action before use*
|
233
|
+
|
234
|
+
## Contributing
|
235
|
+
|
236
|
+
1. Fork it ( https://github.com/[my-github-username]/usecase/fork )
|
237
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
238
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
239
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
240
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rake/testtask'
|
4
|
+
test_tasks = Dir['test/*/'].map { |d| File.basename(d) }
|
5
|
+
|
6
|
+
test_tasks.each do |folder|
|
7
|
+
Rake::TestTask.new("test:#{folder}") do |test|
|
8
|
+
test.pattern = "test/#{folder}/**/*_test.rb"
|
9
|
+
test.verbose = true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Run application test suite"
|
14
|
+
Rake::TestTask.new("test") do |test|
|
15
|
+
test.pattern = "test/**/*_test.rb"
|
16
|
+
test.verbose = true
|
17
|
+
end
|
data/lib/all_systems.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module AllSystems
|
2
|
+
class Interactor
|
3
|
+
def go!
|
4
|
+
raise AbstractMethodError, "please define #{__method__} for #{name} interactor"
|
5
|
+
end
|
6
|
+
|
7
|
+
def outcomes
|
8
|
+
# TODO throw error on abstract
|
9
|
+
[]
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
self.class.name || 'Anonymous'
|
14
|
+
end
|
15
|
+
|
16
|
+
def outcome
|
17
|
+
result.first
|
18
|
+
end
|
19
|
+
|
20
|
+
def outcome?(predicate)
|
21
|
+
raise UnknownOutcomeError unless outcomes.include? predicate
|
22
|
+
predicate == outcome
|
23
|
+
end
|
24
|
+
|
25
|
+
def output
|
26
|
+
result.drop 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def on(conditional_outcome)
|
30
|
+
yield *output if outcome? conditional_outcome
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def go
|
36
|
+
catch(:report) do
|
37
|
+
go!
|
38
|
+
raise NoOutcomeError, "#{name} concluded without reporting an outcome"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def result
|
43
|
+
@result ||= go
|
44
|
+
end
|
45
|
+
|
46
|
+
def report(*result)
|
47
|
+
raise UnknownOutcomeReportError unless outcomes.include? result.first
|
48
|
+
throw :report, result
|
49
|
+
end
|
50
|
+
|
51
|
+
def method_missing(method_symbol, *args, &block)
|
52
|
+
case method_symbol
|
53
|
+
when *outcomes
|
54
|
+
return on method_symbol, &block
|
55
|
+
when /report_([^?]+)/
|
56
|
+
report $1.to_sym, *args
|
57
|
+
when /([^?]+)\?/
|
58
|
+
super unless outcomes.include? $1.to_sym
|
59
|
+
outcome? $1.to_sym
|
60
|
+
else
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative '../test_config'
|
2
|
+
|
3
|
+
module AllSystems
|
4
|
+
class InteractorErrorTest < MiniTest::Test
|
5
|
+
NoGoInteractor = Class.new(AllSystems::Interactor)
|
6
|
+
|
7
|
+
def test_raises_error_for_no_go_bang_method
|
8
|
+
interactor = NoGoInteractor.new
|
9
|
+
err = assert_raises AbstractMethodError do
|
10
|
+
interactor.outcome
|
11
|
+
end
|
12
|
+
assert_includes err.message, 'go!'
|
13
|
+
end
|
14
|
+
|
15
|
+
NoOutcomeInteractor = Class.new(AllSystems::Interactor) do
|
16
|
+
def go! ; end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_raises_error_if_go_does_not_report_outcome
|
20
|
+
interactor = NoOutcomeInteractor.new
|
21
|
+
err = assert_raises NoOutcomeError do
|
22
|
+
interactor.outcome
|
23
|
+
end
|
24
|
+
assert_includes err.message, 'NoOutcomeInteractor'
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_raises_correct_error_for_unnamed_interactor
|
28
|
+
interactor = Class.new(AllSystems::Interactor).new
|
29
|
+
err = assert_raises AbstractMethodError do
|
30
|
+
interactor.outcome
|
31
|
+
end
|
32
|
+
assert_includes err.message, 'Anonymous interactor'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative '../test_config'
|
2
|
+
|
3
|
+
module AllSystems
|
4
|
+
class InteractorInvalidOutcomeTest < MiniTest::Test
|
5
|
+
def interactor_klass
|
6
|
+
@interactor_klass ||= Class.new(AllSystems::Interactor) do
|
7
|
+
def initialize(pass)
|
8
|
+
@pass = pass
|
9
|
+
end
|
10
|
+
|
11
|
+
def outcomes
|
12
|
+
[:success]
|
13
|
+
end
|
14
|
+
|
15
|
+
def go!
|
16
|
+
report :success if @pass
|
17
|
+
report :random
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_cant_report_unknown_outcome
|
23
|
+
interactor = interactor_klass.new false
|
24
|
+
assert_raises AllSystems::UnknownOutcomeReportError do
|
25
|
+
interactor.outcome
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_cant_check_unknown_outcome
|
30
|
+
interactor = interactor_klass.new true
|
31
|
+
assert_raises AllSystems::UnknownOutcomeError do
|
32
|
+
interactor.outcome? :random
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_cant_define_callback_for_unknown_outcome
|
37
|
+
interactor = interactor_klass.new true
|
38
|
+
assert_raises AllSystems::UnknownOutcomeError do
|
39
|
+
interactor.on :random do
|
40
|
+
flunk
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_no_method_error_for_invalid_outcomes
|
46
|
+
interactor = interactor_klass.new true
|
47
|
+
assert_raises NoMethodError do
|
48
|
+
interactor.random do
|
49
|
+
flunk
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_no_method_error_for_invalid_outcome_queries
|
55
|
+
interactor = interactor_klass.new true
|
56
|
+
assert_raises NoMethodError do
|
57
|
+
interactor.random? do
|
58
|
+
flunk
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require_relative '../test_config'
|
2
|
+
|
3
|
+
module AllSystems
|
4
|
+
class InteractorOutcomeTest < MiniTest::Test
|
5
|
+
def interactor_klass
|
6
|
+
@interactor_klass ||= Class.new(AllSystems::Interactor) do
|
7
|
+
def initialize(pass)
|
8
|
+
@pass = pass
|
9
|
+
end
|
10
|
+
|
11
|
+
def outcomes
|
12
|
+
[:success, :failure]
|
13
|
+
end
|
14
|
+
|
15
|
+
def go!
|
16
|
+
report :success if @pass
|
17
|
+
report :failure
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_reports_outcome_as_success_for_pass
|
23
|
+
interactor = interactor_klass.new true
|
24
|
+
assert_equal :success, interactor.outcome
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_reports_outcome_as_failure_for_no_pass
|
28
|
+
interactor = interactor_klass.new false
|
29
|
+
assert_equal :failure, interactor.outcome
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_confirms_outcome_success_for_pass
|
33
|
+
interactor = interactor_klass.new true
|
34
|
+
assert_equal true, interactor.outcome?(:success)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_denys_outcome_success_for_no_pass
|
38
|
+
interactor = interactor_klass.new false
|
39
|
+
assert_equal false, interactor.outcome?(:success)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_success_query_is_true_for_pass
|
43
|
+
interactor = interactor_klass.new true
|
44
|
+
assert_equal true, interactor.success?
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_success_query_is_false_for_no_pass
|
48
|
+
interactor = interactor_klass.new false
|
49
|
+
assert_equal false, interactor.success?
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_failure_query_is_true_for_no_pass
|
53
|
+
interactor = interactor_klass.new false
|
54
|
+
assert_equal true, interactor.failure?
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_failure_query_is_false_for_pass
|
58
|
+
interactor = interactor_klass.new true
|
59
|
+
assert_equal false, interactor.failure?
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_calls_on_success_action_for_pass
|
63
|
+
interactor = interactor_klass.new true
|
64
|
+
mock = MiniTest::Mock.new
|
65
|
+
mock.expect :report, true
|
66
|
+
interactor.on :success do
|
67
|
+
mock.report
|
68
|
+
end
|
69
|
+
mock.verify
|
70
|
+
end
|
71
|
+
|
72
|
+
# TODO decide
|
73
|
+
# def test_calls_on_either_action_for_pass
|
74
|
+
# interactor = interactor_klass.new true
|
75
|
+
# mock = MiniTest::Mock.new
|
76
|
+
# mock.expect :report, true
|
77
|
+
# interactor.on :success, :failue do
|
78
|
+
# mock.report
|
79
|
+
# end
|
80
|
+
# mock.verify
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# def test_calls_on_either_action_for_no_pass
|
84
|
+
# interactor = interactor_klass.new false
|
85
|
+
# mock = MiniTest::Mock.new
|
86
|
+
# mock.expect :report, true
|
87
|
+
# interactor.on :success, :failure do
|
88
|
+
# mock.report
|
89
|
+
# end
|
90
|
+
# mock.verify
|
91
|
+
# end
|
92
|
+
|
93
|
+
def test_doesnt_call_on_success_action_for_no_pass
|
94
|
+
interactor = interactor_klass.new false
|
95
|
+
interactor.on :success do
|
96
|
+
flunk 'Should not process'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_calls_success_action_for_pass
|
101
|
+
interactor = interactor_klass.new true
|
102
|
+
mock = MiniTest::Mock.new
|
103
|
+
mock.expect :report, true
|
104
|
+
interactor.success do
|
105
|
+
mock.report
|
106
|
+
end
|
107
|
+
mock.verify
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_doesnt_call_success_for_no_pass
|
111
|
+
interactor = interactor_klass.new false
|
112
|
+
interactor.success do
|
113
|
+
flunk 'Should not be a success'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_calls_failure_action_for_no_pass
|
118
|
+
interactor = interactor_klass.new false
|
119
|
+
mock = MiniTest::Mock.new
|
120
|
+
mock.expect :report, true
|
121
|
+
interactor.failure do
|
122
|
+
mock.report
|
123
|
+
end
|
124
|
+
mock.verify
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_doesnt_call_failure_for_pass
|
128
|
+
interactor = interactor_klass.new true
|
129
|
+
interactor.failure do
|
130
|
+
flunk 'Should not be a failure'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative '../test_config'
|
2
|
+
|
3
|
+
module AllSystems
|
4
|
+
class InteractorOutputTest < MiniTest::Test
|
5
|
+
def interactor_klass
|
6
|
+
@interactor_klass ||= Class.new(AllSystems::Interactor) do
|
7
|
+
def initialize(value)
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def outcomes
|
12
|
+
[:none, :one, :two, :unique]
|
13
|
+
end
|
14
|
+
|
15
|
+
def go!
|
16
|
+
case @value
|
17
|
+
when 0
|
18
|
+
report :none
|
19
|
+
when 1
|
20
|
+
report :one, 1
|
21
|
+
when 2
|
22
|
+
report :two, 1, 2
|
23
|
+
when :unique
|
24
|
+
report :unique, Class.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_empty_out_put_array_when_none_given
|
31
|
+
interactor = interactor_klass.new 0
|
32
|
+
assert_equal [], interactor.output
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_callback_recives_no_output
|
36
|
+
interactor = interactor_klass.new 0
|
37
|
+
mock = MiniTest::Mock.new
|
38
|
+
mock.expect :report, true, []
|
39
|
+
interactor.none do |*args|
|
40
|
+
mock.report *args
|
41
|
+
end
|
42
|
+
mock.verify
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_single_item_output
|
46
|
+
interactor = interactor_klass.new 1
|
47
|
+
assert_equal [1], interactor.output
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_callback_recives_single_output
|
51
|
+
interactor = interactor_klass.new 1
|
52
|
+
mock = MiniTest::Mock.new
|
53
|
+
mock.expect :report, true, [1]
|
54
|
+
interactor.one do |*args|
|
55
|
+
mock.report *args
|
56
|
+
end
|
57
|
+
mock.verify
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_two_item_output
|
61
|
+
interactor = interactor_klass.new 2
|
62
|
+
assert_equal [1, 2], interactor.output
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_callback_recives_double_output
|
66
|
+
interactor = interactor_klass.new 2
|
67
|
+
mock = MiniTest::Mock.new
|
68
|
+
mock.expect :report, true, [1, 2]
|
69
|
+
interactor.two do |*args|
|
70
|
+
mock.report *args
|
71
|
+
end
|
72
|
+
mock.verify
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_go_is_only_executed_once
|
76
|
+
interactor = interactor_klass.new :unique
|
77
|
+
assert_equal interactor.output.first, interactor.output.first
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative '../test_config'
|
2
|
+
|
3
|
+
module AllSystems
|
4
|
+
class InteractorReportHelperTest < MiniTest::Test
|
5
|
+
def interactor_klass
|
6
|
+
@interactor_klass ||= Class.new(AllSystems::Interactor) do
|
7
|
+
def initialize(pass)
|
8
|
+
@pass = pass
|
9
|
+
end
|
10
|
+
|
11
|
+
def outcomes
|
12
|
+
[:success, :failure]
|
13
|
+
end
|
14
|
+
|
15
|
+
def go!
|
16
|
+
report_success :item if @pass
|
17
|
+
report_failure
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_reports_outcome_as_success_for_pass
|
23
|
+
interactor = interactor_klass.new true
|
24
|
+
assert_equal :success, interactor.outcome
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_reports_outoutput_for_pass
|
28
|
+
interactor = interactor_klass.new true
|
29
|
+
assert_equal [:item], interactor.output
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_reports_outcome_as_failure_for_no_pass
|
33
|
+
interactor = interactor_klass.new false
|
34
|
+
assert_equal :failure, interactor.outcome
|
35
|
+
end
|
36
|
+
class Undefined < AllSystems::Interactor
|
37
|
+
def outcomes
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
|
41
|
+
def go!
|
42
|
+
report_created
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_handles_undefined_outcomes
|
47
|
+
assert_raises AllSystems::UnknownOutcomeReportError do
|
48
|
+
Undefined.new().outcome
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Nonedefined < AllSystems::Interactor
|
53
|
+
def go!
|
54
|
+
report_created
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_handles_nonedefined_outcomes
|
59
|
+
assert_raises AllSystems::UnknownOutcomeReportError do
|
60
|
+
Nonedefined.new().outcome
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/test/test_config.rb
ADDED
data/usecase.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'all_systems/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "all_systems"
|
8
|
+
spec.version = AllSystems::VERSION
|
9
|
+
spec.authors = ["Peter Saxton"]
|
10
|
+
spec.email = ["peterhsaxton@gmail.com"]
|
11
|
+
spec.summary = %q{Simple usecase/interactor/service objects to encapsulate business logic}
|
12
|
+
spec.description = %q{An interactor encapsulates the action of specific business usecase. For example a `LogIn` or `CreatePost`. It executes this action by coordinating the interaction of over business objects in the system.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
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.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.4.3"
|
24
|
+
spec.add_development_dependency "minitest-reporters", "~> 1.0.6"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: all_systems
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Saxton
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-19 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.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
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.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 5.4.3
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 5.4.3
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-reporters
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.0.6
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.0.6
|
69
|
+
description: An interactor encapsulates the action of specific business usecase. For
|
70
|
+
example a `LogIn` or `CreatePost`. It executes this action by coordinating the interaction
|
71
|
+
of over business objects in the system.
|
72
|
+
email:
|
73
|
+
- peterhsaxton@gmail.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- ".gitignore"
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/all_systems.rb
|
84
|
+
- lib/all_systems/errors.rb
|
85
|
+
- lib/all_systems/interactor.rb
|
86
|
+
- lib/all_systems/version.rb
|
87
|
+
- test/interactor/error_test.rb
|
88
|
+
- test/interactor/invalid_outcomes_test.rb
|
89
|
+
- test/interactor/outcome_test.rb
|
90
|
+
- test/interactor/output_test.rb
|
91
|
+
- test/interactor/report_helper_test.rb
|
92
|
+
- test/test_config.rb
|
93
|
+
- usecase.gemspec
|
94
|
+
homepage: ''
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.4.6
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: Simple usecase/interactor/service objects to encapsulate business logic
|
118
|
+
test_files:
|
119
|
+
- test/interactor/error_test.rb
|
120
|
+
- test/interactor/invalid_outcomes_test.rb
|
121
|
+
- test/interactor/outcome_test.rb
|
122
|
+
- test/interactor/output_test.rb
|
123
|
+
- test/interactor/report_helper_test.rb
|
124
|
+
- test/test_config.rb
|