use_case 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -0
- data/Gemfile.lock +51 -0
- data/LICENSE +22 -0
- data/Rakefile +8 -0
- data/Readme.md +298 -0
- data/lib/use_case.rb +82 -0
- data/lib/use_case/outcome.rb +92 -0
- data/lib/use_case/validator.rb +52 -0
- data/lib/use_case/version.rb +28 -0
- data/test.rb +96 -0
- data/test/sample_use_case.rb +92 -0
- data/test/test_helper.rb +28 -0
- data/test/use_case/outcome_test.rb +144 -0
- data/test/use_case/validator_test.rb +51 -0
- data/test/use_case_test.rb +91 -0
- data/use_case.gemspec +19 -0
- metadata +97 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
GIT
|
2
|
+
remote: https://github.com/solnic/virtus.git
|
3
|
+
revision: 97a161f19d20a961094112464075c284d68d35e4
|
4
|
+
specs:
|
5
|
+
virtus (1.0.0.beta1)
|
6
|
+
abstract_type (~> 0.0.5)
|
7
|
+
adamantium (~> 0.0.7)
|
8
|
+
backports (~> 3.0, >= 3.1.0)
|
9
|
+
coercible (~> 0.2)
|
10
|
+
descendants_tracker (~> 0.0.1)
|
11
|
+
|
12
|
+
PATH
|
13
|
+
remote: .
|
14
|
+
specs:
|
15
|
+
use_case (0.1.0)
|
16
|
+
|
17
|
+
GEM
|
18
|
+
remote: http://rubygems.org/
|
19
|
+
specs:
|
20
|
+
abstract_type (0.0.5)
|
21
|
+
backports (~> 3.0, >= 3.0.3)
|
22
|
+
activemodel (3.2.12)
|
23
|
+
activesupport (= 3.2.12)
|
24
|
+
builder (~> 3.0.0)
|
25
|
+
activesupport (3.2.12)
|
26
|
+
i18n (~> 0.6)
|
27
|
+
multi_json (~> 1.0)
|
28
|
+
adamantium (0.0.7)
|
29
|
+
backports (~> 3.0, >= 3.0.3)
|
30
|
+
ice_nine (~> 0.7.0)
|
31
|
+
backports (3.1.1)
|
32
|
+
builder (3.0.4)
|
33
|
+
coercible (0.2.0)
|
34
|
+
backports (~> 3.0, >= 3.1.0)
|
35
|
+
descendants_tracker (~> 0.0.1)
|
36
|
+
descendants_tracker (0.0.1)
|
37
|
+
i18n (0.6.4)
|
38
|
+
ice_nine (0.7.0)
|
39
|
+
minitest (4.7.0)
|
40
|
+
multi_json (1.6.1)
|
41
|
+
rake (10.0.3)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
activemodel
|
48
|
+
minitest (~> 4)
|
49
|
+
rake
|
50
|
+
use_case!
|
51
|
+
virtus!
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Gitorious AS
|
2
|
+
|
3
|
+
Christian Johansen (christian@gitorious.com)
|
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/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
# Use Case
|
2
|
+
|
3
|
+
Compose non-trivial business logic into use cases, that combine:
|
4
|
+
|
5
|
+
* Input parameter abstractions; type safety and coercion, and white-listing of
|
6
|
+
supported input for any given operation
|
7
|
+
* Pre-conditions: System-level conditions that must be met, e.g. "a user must be
|
8
|
+
logged in" etc.
|
9
|
+
* Input parameter validation: ActiveRecord-like validations as composable
|
10
|
+
objects. Combine specific sets of validation rules for the same input in
|
11
|
+
different contexts etc.
|
12
|
+
* Commands: Avoid defensive coding by performing the core actions in commands
|
13
|
+
that receive type-converted input, and are only executed when pre-conditions
|
14
|
+
are met and input validated.
|
15
|
+
|
16
|
+
## Example
|
17
|
+
|
18
|
+
At its very simplest, a use case wraps a command. A command is any Ruby object
|
19
|
+
with an `execute` method that receives one argument (input parameters). Because
|
20
|
+
UseCase is intended as a mechanism for implementing non-trivial use cases, the
|
21
|
+
following contrived example will seem particularly useless.
|
22
|
+
|
23
|
+
```rb
|
24
|
+
require "use_case"
|
25
|
+
|
26
|
+
class HelloWorldCommand
|
27
|
+
def execute(params)
|
28
|
+
puts "Hello, #{params[:place]}!"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class PrintHelloWorld
|
33
|
+
include UseCase
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
command(HelloWorldCommand.new)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Usage
|
41
|
+
PrintHelloWorld.new.execute(:place => "World")
|
42
|
+
```
|
43
|
+
|
44
|
+
Not that you are free to design your constructors any way you want.
|
45
|
+
|
46
|
+
## Useful example
|
47
|
+
|
48
|
+
A more useful example will use every aspect of a `UseCase`. Pre-conditions are
|
49
|
+
conditions not directly related to input parameters, and whose failure signifies
|
50
|
+
other forms of errors than simple validation errors. If you have a Rails
|
51
|
+
application that uses controller filters, then those are very likely good
|
52
|
+
candidates for pre-conditions.
|
53
|
+
|
54
|
+
The following example is a simplified use case from
|
55
|
+
[Gitorious](http://gitorious.org) where we want to create a new repository. To
|
56
|
+
do this, we need a user that can admin the project under which we want the new
|
57
|
+
repository to live.
|
58
|
+
|
59
|
+
This example illustrates how to solve common design challenges in Rails
|
60
|
+
applications, but that does not mean that `UseCase` is only useful to Rails
|
61
|
+
applications.
|
62
|
+
|
63
|
+
```rb
|
64
|
+
require "use_case"
|
65
|
+
require "virtus"
|
66
|
+
|
67
|
+
# Input parameters can be sanitized and pre-processed anyway you like. One nice
|
68
|
+
# way to go about it is to use Datamapper 2's Virtus gem to define a parameter
|
69
|
+
# set.
|
70
|
+
#
|
71
|
+
# This class uses Project.find to look up a project by id if project_id is
|
72
|
+
# provided and project is not. This is the only class that has
|
73
|
+
class NewRepositoryInput
|
74
|
+
include Virtus
|
75
|
+
attribute :name, String
|
76
|
+
attribute :description, String
|
77
|
+
attribute :project, Project
|
78
|
+
attribute :project_id, Integer
|
79
|
+
|
80
|
+
def project
|
81
|
+
@project ||= Project.find(@project_id)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Validate new repositories. Extremely simplified example.
|
86
|
+
NewRepositoryValidator = UseCase::Validator.define do
|
87
|
+
validates_presence_of :name, :project
|
88
|
+
end
|
89
|
+
|
90
|
+
# This is often implemented as a controller filter in many Rails apps.
|
91
|
+
# Unfortunately that means we have to duplicate the check when exposing the use
|
92
|
+
# case in other contexts (e.g. a stand-alone API app, console API etc).
|
93
|
+
class UserLoggedInPrecondition
|
94
|
+
# The constructor is only used by us and can look and do whever we want
|
95
|
+
def initialize(user)
|
96
|
+
@user = user
|
97
|
+
end
|
98
|
+
|
99
|
+
# A pre-condition must define this method
|
100
|
+
# Params is an instance of NewRepositoryInput
|
101
|
+
def satiesfied?(params)
|
102
|
+
!@user.nil?
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Another pre-condition that uses app-wide state
|
107
|
+
class ProjectAdminPrecondition
|
108
|
+
def initialize(auth, user)
|
109
|
+
@auth = auth
|
110
|
+
@user = user
|
111
|
+
end
|
112
|
+
|
113
|
+
def satiesfied?(params)
|
114
|
+
@auth.can_admin?(@user, params.project)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# The business logic. Here we can safely assume that all pre-conditions are
|
119
|
+
# satiesfied, and that input is valid and has the correct type.
|
120
|
+
class CreateRepositoryCommand
|
121
|
+
def initialize(user)
|
122
|
+
@user = user
|
123
|
+
end
|
124
|
+
|
125
|
+
# Params is an instance of NewRepositoryInput
|
126
|
+
def execute(params)
|
127
|
+
params.project.repositories.create(:name => params.name, :user => @user)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# The UseCase - this is just wiring together the various classes
|
132
|
+
class CreateRepository
|
133
|
+
include UseCase
|
134
|
+
|
135
|
+
# There's no contract to satiesfy with the constructor - design it to receive
|
136
|
+
# any dependencies you need.
|
137
|
+
def initialize(auth, user)
|
138
|
+
input_class(NewRepositoryInput)
|
139
|
+
pre_condition(UserLoggedInPrecondition.new(user))
|
140
|
+
pre_condition(ProjectAdminPrecondition.new(auth, user))
|
141
|
+
# Multiple validators can be added if needed
|
142
|
+
validator(NewRepositoryValidator)
|
143
|
+
command(CreateRepositoryCommand.new(user))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Finally, the actual usage. This example is a Rails controller
|
148
|
+
class RepositoryController < ApplicationController
|
149
|
+
include Gitorious::Authorization # Adds stuff like can_admin?(actor, thing)
|
150
|
+
|
151
|
+
# ...
|
152
|
+
|
153
|
+
def create
|
154
|
+
outcome = CreateRepository.new(self, current_user).execute(params)
|
155
|
+
|
156
|
+
outcome.pre_condition_failed do |condition|
|
157
|
+
redirect_to(login_path) and return if condition.is_a?(UserLoggedInPrecondition)
|
158
|
+
flash[:error] = "You're not allowed to do that"
|
159
|
+
redirect_to project_path
|
160
|
+
end
|
161
|
+
|
162
|
+
outcome.failure do |model|
|
163
|
+
# Render form with validation errors
|
164
|
+
render :new, :locals => { :repository => model }
|
165
|
+
end
|
166
|
+
|
167
|
+
outcome.success do |repository|
|
168
|
+
redirect_to(repository_path(repository))
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
## Input sanitation
|
175
|
+
|
176
|
+
In your `UseCase` instance (typically in the constructor), you can call the
|
177
|
+
`input_class` method to specify which class is used to santize inputs. If you do
|
178
|
+
not use this, inputs are forwarded to pre-conditions and commands untouched
|
179
|
+
(i.e. as a `Hash`).
|
180
|
+
|
181
|
+
Datamapper 2's [Virtus](https://github.com/solnic/virtus) is a very promising
|
182
|
+
solution for input sanitation and some level of type-safety. If you provide a
|
183
|
+
`Virtus` backed class as `input_class` you will get an instance of that class as
|
184
|
+
`params` in pre-conditions and commands.
|
185
|
+
|
186
|
+
## Validations
|
187
|
+
|
188
|
+
The validator uses `ActiveModel::Validations`, so any Rails validation can go in
|
189
|
+
here. The main difference is that the validator is created as a stand-alone
|
190
|
+
object that can be used with any model instance. This design allows you to
|
191
|
+
define multiple context-sensitive validations for a single object.
|
192
|
+
|
193
|
+
You can of course provide your own validation if you want - any object that
|
194
|
+
defines `call(object)` and returns something that responds to `valid?` is good.
|
195
|
+
I am following the Datamapper project closely in this area.
|
196
|
+
|
197
|
+
Because `UseCase::Validation` is not a required part of `UseCase`, and people
|
198
|
+
may want to control their own dependencies, `activemodel` is _not_ a hard
|
199
|
+
dependency. To use this feature, `gem install activemodel`.
|
200
|
+
|
201
|
+
## Pre-conditions
|
202
|
+
|
203
|
+
A pre-condition is any object that responds to `satiesfied?(params)` where
|
204
|
+
params will either be a `Hash` or an instance of whatever you passed to
|
205
|
+
`input_class`. The method should return `true/false`. If it raises, the outcome
|
206
|
+
of the use case will call the `pre_condition_failed` block with the raised
|
207
|
+
error. If it fails, the `pre_condition_failed` block will be called with the
|
208
|
+
pre-condition instance that failed.
|
209
|
+
|
210
|
+
## Commands
|
211
|
+
|
212
|
+
A command is any Ruby object that defines an `execute(params)` method. Its
|
213
|
+
return value will be passed to the outcome's `success` block. Any errors raised
|
214
|
+
by this method is not rescued, so be sure to wrap `use_case.execute(params)` in
|
215
|
+
a rescue block if you're worried that it raises.
|
216
|
+
|
217
|
+
## Use cases
|
218
|
+
|
219
|
+
A use case simply glues together all the components. Define a class, include
|
220
|
+
`UseCase`, and configure the instance in the constructor. The constructor can
|
221
|
+
take any arguments you like, making this solution suitable for DI (dependency
|
222
|
+
injection) style designs.
|
223
|
+
|
224
|
+
The use case can optionally call `input_class` once, `pre_condition` multiple
|
225
|
+
times, and `validator` multiple times. It *must* call `command` once with the
|
226
|
+
command object.
|
227
|
+
|
228
|
+
## Outcomes
|
229
|
+
|
230
|
+
`UseCase#execute` returns an `Outcome`. You can use the outcome in primarily two
|
231
|
+
ways. The primary approach is one that takes blocks for the three situations:
|
232
|
+
`success(&block)`, `failure(&block)`, and `pre_condition_failed(&block)`. Only
|
233
|
+
one of these will ever be called. This allows you to declaratively describe
|
234
|
+
further flow in your program.
|
235
|
+
|
236
|
+
For use on the console and other situations, this style is not the most
|
237
|
+
convenient. For that reason each of the three methods above can also be called
|
238
|
+
without a block, and they always return something:
|
239
|
+
|
240
|
+
* `success` returns the command result
|
241
|
+
* `failure` returns the validation object (e.g. `failure.errors.inspect`)
|
242
|
+
* `pre_condition_failed` returns the pre-condition that failed, *or* an
|
243
|
+
exception object, if a pre-condition raised an exception.
|
244
|
+
|
245
|
+
In addition to these, the outcome object responds to `success?` and
|
246
|
+
`pre_condition_failed?`.
|
247
|
+
|
248
|
+
## Inspiration and design considerations
|
249
|
+
|
250
|
+
This small library is very much inspired by
|
251
|
+
[Mutations](http://github.com/cypriss/mutations). Nice as it is, I found it to
|
252
|
+
be a little limiting in terms of what kinds of commands it could comfortably
|
253
|
+
encapsulate. Treating everything as a hash of inputs makes it hard to do things
|
254
|
+
like "redirect if there's no user, render form if there are validation errors
|
255
|
+
and redirect to new object if successful".
|
256
|
+
|
257
|
+
As I started working on my own solution I quickly recognized the power in
|
258
|
+
separating input parameter type constraints/coercions from validation rules.
|
259
|
+
This is another area where UseCase differs from Mutations. UseCase is probably
|
260
|
+
slightly more "enterprise" than Mutations, but fits the kinds of problems I
|
261
|
+
intend to solve with it better than Mutations did.
|
262
|
+
|
263
|
+
## Testing
|
264
|
+
|
265
|
+
Using UseCase will allow you to test almost all logic completely without loading
|
266
|
+
Rails. In the example above, the input conversion is the only place that
|
267
|
+
directly touches any classes from the Rails application. The rest of the classes
|
268
|
+
work by the "data in, data out" principle, meaning you can easily test them with
|
269
|
+
any kind of object (which spares you of loading heavy ActiveRecord-bound models,
|
270
|
+
running opaque controller tets etc).
|
271
|
+
|
272
|
+
## License
|
273
|
+
|
274
|
+
UseCase is free software licensed under the MIT license.
|
275
|
+
|
276
|
+
```
|
277
|
+
The MIT License (MIT)
|
278
|
+
|
279
|
+
Copyright (C) 2013 Gitorious AS
|
280
|
+
|
281
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
282
|
+
of this software and associated documentation files (the "Software"), to deal
|
283
|
+
in the Software without restriction, including without limitation the rights
|
284
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
285
|
+
copies of the Software, and to permit persons to whom the Software is
|
286
|
+
furnished to do so, subject to the following conditions:
|
287
|
+
|
288
|
+
The above copyright notice and this permission notice shall be included in all
|
289
|
+
copies or substantial portions of the Software.
|
290
|
+
|
291
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
292
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
293
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
294
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
295
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
296
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
297
|
+
SOFTWARE.
|
298
|
+
```
|
data/lib/use_case.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "use_case/outcome"
|
26
|
+
require "use_case/validator"
|
27
|
+
require "ostruct"
|
28
|
+
|
29
|
+
module UseCase
|
30
|
+
def input_class(input_class)
|
31
|
+
@input_class = input_class
|
32
|
+
end
|
33
|
+
|
34
|
+
def validator(validator)
|
35
|
+
validators << validator
|
36
|
+
end
|
37
|
+
|
38
|
+
def pre_condition(pc)
|
39
|
+
pre_conditions << pc
|
40
|
+
end
|
41
|
+
|
42
|
+
def command(command)
|
43
|
+
@command = command
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute(params)
|
47
|
+
input = @input_class && @input_class.new(params) || params
|
48
|
+
|
49
|
+
if outcome = verify_pre_conditions(input)
|
50
|
+
return outcome
|
51
|
+
end
|
52
|
+
|
53
|
+
if outcome = validate_params(input)
|
54
|
+
return outcome
|
55
|
+
end
|
56
|
+
|
57
|
+
SuccessfulOutcome.new(self, @command.execute(input))
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def verify_pre_conditions(input)
|
62
|
+
pre_conditions.each do |pc|
|
63
|
+
begin
|
64
|
+
return PreConditionFailed.new(self, pc) if !pc.satiesfied?(input)
|
65
|
+
rescue Exception => err
|
66
|
+
return PreConditionFailed.new(self, err)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def validate_params(input)
|
73
|
+
validators.each do |validator|
|
74
|
+
result = validator.call(input)
|
75
|
+
return FailedOutcome.new(self, result) if !result.valid?
|
76
|
+
end
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def pre_conditions; @pre_conditions ||= []; end
|
81
|
+
def validators; @validators ||= []; end
|
82
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
module UseCase
|
27
|
+
class Outcome
|
28
|
+
attr_reader :use_case
|
29
|
+
|
30
|
+
def initialize(use_case = nil)
|
31
|
+
@use_case = use_case
|
32
|
+
end
|
33
|
+
|
34
|
+
def pre_condition_failed?; false; end
|
35
|
+
def success?; false; end
|
36
|
+
def success; end
|
37
|
+
def pre_condition_failed; end
|
38
|
+
def failure; end
|
39
|
+
end
|
40
|
+
|
41
|
+
class SuccessfulOutcome < Outcome
|
42
|
+
def initialize(use_case = nil, result = nil)
|
43
|
+
super(use_case)
|
44
|
+
@result = result
|
45
|
+
end
|
46
|
+
|
47
|
+
def success?; true; end
|
48
|
+
|
49
|
+
def success
|
50
|
+
yield @result if block_given?
|
51
|
+
@result
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
"#<UseCase::SuccessfulOutcome: #{@result}>"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class PreConditionFailed < Outcome
|
60
|
+
def initialize(use_case = nil, pre_condition = nil)
|
61
|
+
super(use_case)
|
62
|
+
@pre_condition = pre_condition
|
63
|
+
end
|
64
|
+
|
65
|
+
def pre_condition_failed?; true; end
|
66
|
+
|
67
|
+
def pre_condition_failed
|
68
|
+
yield @pre_condition if block_given?
|
69
|
+
@pre_condition
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
"#<UseCase::PreConditionFailed: #{@pre_condition}>"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class FailedOutcome < Outcome
|
78
|
+
def initialize(use_case = nil, errors = nil)
|
79
|
+
super(use_case)
|
80
|
+
@errors = errors
|
81
|
+
end
|
82
|
+
|
83
|
+
def failure
|
84
|
+
yield @errors if block_given?
|
85
|
+
@errors
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_s
|
89
|
+
"#<UseCase::FailedOutcome: #{@errors}>"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "active_model"
|
26
|
+
|
27
|
+
module UseCase
|
28
|
+
module Validator
|
29
|
+
def self.define(&block)
|
30
|
+
klass = Class.new do
|
31
|
+
include ActiveModel::Validations
|
32
|
+
|
33
|
+
def initialize(target)
|
34
|
+
@target = target
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(name, *args, &block)
|
38
|
+
@target.send(name, *args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.call(object)
|
42
|
+
validator = new(object)
|
43
|
+
validator.valid?
|
44
|
+
validator
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
klass.instance_eval(&block)
|
49
|
+
klass
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
|
26
|
+
module UseCase
|
27
|
+
VERSION = "0.1.0"
|
28
|
+
end
|
data/test.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "virtus"
|
2
|
+
require "use_case"
|
3
|
+
|
4
|
+
class Project
|
5
|
+
attr_reader :title
|
6
|
+
def initialize(title); @title = title; end
|
7
|
+
def self.find(id); new("<Project> My name is #{id}"); end
|
8
|
+
end
|
9
|
+
|
10
|
+
class User
|
11
|
+
attr_reader :name
|
12
|
+
def initialize(name); @name = name; end
|
13
|
+
def self.find(id); new("<User> My name is #{id}"); end
|
14
|
+
end
|
15
|
+
|
16
|
+
class NewRepositoryInput
|
17
|
+
include Virtus
|
18
|
+
|
19
|
+
attribute :name, String
|
20
|
+
attribute :description, String
|
21
|
+
attribute :merge_requests_enabled, Boolean, :default => true
|
22
|
+
attribute :private_repository, Boolean, :default => true
|
23
|
+
|
24
|
+
attribute :user, User
|
25
|
+
attribute :user_id, Integer
|
26
|
+
attribute :project, Project
|
27
|
+
attribute :project_id, Integer
|
28
|
+
|
29
|
+
def project; @project ||= Project.find(@project_id); end
|
30
|
+
def user; @user ||= User.find(@user_id); end
|
31
|
+
end
|
32
|
+
|
33
|
+
class NewRepositoryValidator
|
34
|
+
include UseCase::Validator
|
35
|
+
validates_presence_of :name, :description, :merge_requests_enabled, :private_repository
|
36
|
+
end
|
37
|
+
|
38
|
+
class UserLoggedInPrecondition
|
39
|
+
def initialize(user)
|
40
|
+
@user = user
|
41
|
+
end
|
42
|
+
|
43
|
+
def satiesfied?(params)
|
44
|
+
@user[:id] == 42
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class ProjectAdminPrecondition
|
49
|
+
def initialize(user)
|
50
|
+
@user = user
|
51
|
+
end
|
52
|
+
|
53
|
+
def satiesfied?(params)
|
54
|
+
@user[:name] == params.name
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class CreateRepositoryCommand
|
59
|
+
def initialize(user)
|
60
|
+
@user = user
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute(params)
|
64
|
+
@user.merge(params)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class CreateRepository
|
69
|
+
include UseCase
|
70
|
+
|
71
|
+
def initialize(user)
|
72
|
+
input_class(NewRepositoryInput)
|
73
|
+
pre_condition(UserLoggedInPrecondition.new(user))
|
74
|
+
pre_condition(ProjectAdminPrecondition.new(user))
|
75
|
+
validator(NewRepositoryValidator)
|
76
|
+
command(CreateRepositoryCommand.new(user))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
### Example
|
81
|
+
|
82
|
+
outcome = CreateRepository.new({ :id => 42, :name => "Boy" }).execute({ :name => "Boy" })
|
83
|
+
|
84
|
+
outcome.precondition_failed do |pc|
|
85
|
+
puts "Pre-condition failed! #{pc}"
|
86
|
+
end
|
87
|
+
|
88
|
+
outcome.success do |result|
|
89
|
+
puts "Your request was successful! #{result}"
|
90
|
+
end
|
91
|
+
|
92
|
+
outcome.failure do |errors|
|
93
|
+
puts "There was a failure #{errors}"
|
94
|
+
end
|
95
|
+
|
96
|
+
puts outcome.to_s
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "virtus"
|
26
|
+
require "use_case"
|
27
|
+
|
28
|
+
class Model
|
29
|
+
attr_reader :id, :name
|
30
|
+
|
31
|
+
def initialize(id, name)
|
32
|
+
@id = id
|
33
|
+
@name = name
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s; "#<#{self.class.name}[id: #{id}, name: #{name}]>"; end
|
37
|
+
def self.find(id); new(id, "From #{self.class.name}.find"); end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Project < Model; end
|
41
|
+
class Repository < Model; end
|
42
|
+
|
43
|
+
class User < Model
|
44
|
+
def can_admin?; @can_admin; end
|
45
|
+
def can_admin=(ca); @can_admin = ca; end
|
46
|
+
end
|
47
|
+
|
48
|
+
class NewRepositoryInput
|
49
|
+
include Virtus
|
50
|
+
attribute :name, String
|
51
|
+
end
|
52
|
+
|
53
|
+
NewRepositoryValidator = UseCase::Validator.define do
|
54
|
+
validates_presence_of :name
|
55
|
+
end
|
56
|
+
|
57
|
+
class UserLoggedInPrecondition
|
58
|
+
def initialize(user); @user = user; end
|
59
|
+
def satiesfied?(params); @user && @user.id == 42; end
|
60
|
+
end
|
61
|
+
|
62
|
+
class ProjectAdminPrecondition
|
63
|
+
def initialize(user); @user = user; end
|
64
|
+
def satiesfied?(params); @user.can_admin?; end
|
65
|
+
end
|
66
|
+
|
67
|
+
class CreateRepositoryCommand
|
68
|
+
def initialize(user); @user = user; end
|
69
|
+
def execute(params); Repository.new(1349, params.name); end
|
70
|
+
end
|
71
|
+
|
72
|
+
class CreateRepository
|
73
|
+
include UseCase
|
74
|
+
|
75
|
+
def initialize(user)
|
76
|
+
input_class(NewRepositoryInput)
|
77
|
+
pre_condition(UserLoggedInPrecondition.new(user))
|
78
|
+
pre_condition(ProjectAdminPrecondition.new(user))
|
79
|
+
validator(NewRepositoryValidator)
|
80
|
+
command(CreateRepositoryCommand.new(user))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class ExplodingRepository
|
85
|
+
include UseCase
|
86
|
+
|
87
|
+
def initialize(user)
|
88
|
+
cmd = CreateRepositoryCommand.new(user)
|
89
|
+
def cmd.execute(params); raise "Crash!"; end
|
90
|
+
command(cmd)
|
91
|
+
end
|
92
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "bundler/setup"
|
26
|
+
require "minitest/autorun"
|
27
|
+
|
28
|
+
Bundler.require(:default, :test)
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "test_helper"
|
26
|
+
require "use_case/outcome"
|
27
|
+
|
28
|
+
describe UseCase::Outcome do
|
29
|
+
it "exposes use case" do
|
30
|
+
use_case = 42
|
31
|
+
outcome = UseCase::Outcome.new(use_case)
|
32
|
+
|
33
|
+
assert_equal use_case, outcome.use_case
|
34
|
+
end
|
35
|
+
|
36
|
+
it "defaults to not failing and not being successful (noop)" do
|
37
|
+
outcome = UseCase::Outcome.new
|
38
|
+
outcome.success { fail "Shouldn't succeed" }
|
39
|
+
outcome.pre_condition_failed { fail "Shouldn't have failed pre-conditions" }
|
40
|
+
outcome.failure { fail "Shouldn't fail" }
|
41
|
+
|
42
|
+
refute outcome.pre_condition_failed?
|
43
|
+
refute outcome.success?
|
44
|
+
end
|
45
|
+
|
46
|
+
describe UseCase::SuccessfulOutcome do
|
47
|
+
it "exposes use case" do
|
48
|
+
use_case = { :id => 42 }
|
49
|
+
outcome = UseCase::SuccessfulOutcome.new(use_case)
|
50
|
+
|
51
|
+
assert_equal use_case, outcome.use_case
|
52
|
+
end
|
53
|
+
|
54
|
+
it "does not fail" do
|
55
|
+
outcome = UseCase::SuccessfulOutcome.new
|
56
|
+
outcome.pre_condition_failed { fail "Shouldn't have failed pre-conditions" }
|
57
|
+
outcome.failure { fail "Shouldn't fail" }
|
58
|
+
|
59
|
+
refute outcome.pre_condition_failed?
|
60
|
+
assert outcome.success?
|
61
|
+
end
|
62
|
+
|
63
|
+
it "yields and returns result" do
|
64
|
+
result = 42
|
65
|
+
yielded_result = nil
|
66
|
+
outcome = UseCase::SuccessfulOutcome.new(nil, result)
|
67
|
+
returned_result = outcome.success { |res| yielded_result = res }
|
68
|
+
|
69
|
+
assert_equal result, yielded_result
|
70
|
+
assert_equal result, returned_result
|
71
|
+
end
|
72
|
+
|
73
|
+
it "gets result without block" do
|
74
|
+
outcome = UseCase::SuccessfulOutcome.new(nil, 42)
|
75
|
+
assert_equal 42, outcome.success
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe UseCase::PreConditionFailed do
|
80
|
+
it "exposes use case" do
|
81
|
+
use_case = { :id => 42 }
|
82
|
+
outcome = UseCase::PreConditionFailed.new(use_case)
|
83
|
+
|
84
|
+
assert_equal use_case, outcome.use_case
|
85
|
+
end
|
86
|
+
|
87
|
+
it "does not succeed or fail" do
|
88
|
+
outcome = UseCase::PreConditionFailed.new
|
89
|
+
outcome.success { fail "Shouldn't succeed" }
|
90
|
+
outcome.failure { fail "Shouldn't fail" }
|
91
|
+
|
92
|
+
assert outcome.pre_condition_failed?
|
93
|
+
refute outcome.success?
|
94
|
+
end
|
95
|
+
|
96
|
+
it "yields and returns failed pre-condition" do
|
97
|
+
pre_condition = 42
|
98
|
+
yielded_pc = nil
|
99
|
+
outcome = UseCase::PreConditionFailed.new(nil, pre_condition)
|
100
|
+
returned_pc = outcome.pre_condition_failed { |pc| yielded_pc = pc }
|
101
|
+
|
102
|
+
assert_equal pre_condition, yielded_pc
|
103
|
+
assert_equal pre_condition, returned_pc
|
104
|
+
end
|
105
|
+
|
106
|
+
it "gets pre_condition without block" do
|
107
|
+
outcome = UseCase::PreConditionFailed.new(nil, 42)
|
108
|
+
assert_equal 42, outcome.pre_condition_failed
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe UseCase::FailedOutcome do
|
113
|
+
it "exposes use case" do
|
114
|
+
use_case = { :id => 42 }
|
115
|
+
outcome = UseCase::FailedOutcome.new(use_case)
|
116
|
+
|
117
|
+
assert_equal use_case, outcome.use_case
|
118
|
+
end
|
119
|
+
|
120
|
+
it "does not succeed or fail pre-conditions" do
|
121
|
+
outcome = UseCase::FailedOutcome.new
|
122
|
+
outcome.success { fail "Shouldn't succeed" }
|
123
|
+
outcome.pre_condition_failed { fail "Shouldn't fail pre-conditions" }
|
124
|
+
|
125
|
+
refute outcome.pre_condition_failed?
|
126
|
+
refute outcome.success?
|
127
|
+
end
|
128
|
+
|
129
|
+
it "yields and returns validation failure" do
|
130
|
+
failure = 42
|
131
|
+
yielded_result = nil
|
132
|
+
outcome = UseCase::FailedOutcome.new(nil, failure)
|
133
|
+
returned_result = outcome.failure { |result| yielded_result = result }
|
134
|
+
|
135
|
+
assert_equal failure, yielded_result
|
136
|
+
assert_equal failure, returned_result
|
137
|
+
end
|
138
|
+
|
139
|
+
it "gets failure without block" do
|
140
|
+
outcome = UseCase::FailedOutcome.new(nil, 42)
|
141
|
+
assert_equal 42, outcome.failure
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "test_helper"
|
26
|
+
require "use_case/validator"
|
27
|
+
|
28
|
+
NewPersonValidator = UseCase::Validator.define do
|
29
|
+
validates_presence_of :name
|
30
|
+
end
|
31
|
+
|
32
|
+
class Person
|
33
|
+
attr_accessor :name
|
34
|
+
end
|
35
|
+
|
36
|
+
describe UseCase::Validator do
|
37
|
+
it "passes valid object" do
|
38
|
+
person = Person.new
|
39
|
+
person.name = "Christian"
|
40
|
+
result = NewPersonValidator.call(person)
|
41
|
+
|
42
|
+
assert result.valid?
|
43
|
+
end
|
44
|
+
|
45
|
+
it "fails invalid object" do
|
46
|
+
result = NewPersonValidator.call(Person.new)
|
47
|
+
|
48
|
+
refute result.valid?
|
49
|
+
assert_equal 1, result.errors.count
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# --
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2013 Gitorious AS
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
# of this software and associated documentation files (the "Software"), to deal
|
9
|
+
# in the Software without restriction, including without limitation the rights
|
10
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
# copies of the Software, and to permit persons to whom the Software is
|
12
|
+
# furnished to do so, subject to the following conditions:
|
13
|
+
#
|
14
|
+
# The above copyright notice and this permission notice shall be included in all
|
15
|
+
# copies or substantial portions of the Software.
|
16
|
+
#
|
17
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
# SOFTWARE.
|
24
|
+
#++
|
25
|
+
require "test_helper"
|
26
|
+
require "use_case"
|
27
|
+
require "sample_use_case"
|
28
|
+
|
29
|
+
describe UseCase do
|
30
|
+
before do
|
31
|
+
@logged_in_user = User.new(42, "Christian")
|
32
|
+
@logged_in_user.can_admin = true
|
33
|
+
end
|
34
|
+
|
35
|
+
it "fails first pre-condition; no user logged in" do
|
36
|
+
outcome = CreateRepository.new(nil).execute({})
|
37
|
+
|
38
|
+
outcome.pre_condition_failed do |pc|
|
39
|
+
assert_equal UserLoggedInPrecondition, pc.class
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "fails second pre-condition; user cannot admin" do
|
44
|
+
@logged_in_user.can_admin = false
|
45
|
+
outcome = CreateRepository.new(@logged_in_user).execute({})
|
46
|
+
|
47
|
+
outcome.pre_condition_failed do |pc|
|
48
|
+
assert_equal ProjectAdminPrecondition, pc.class
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "fails with error if pre-condition raises" do
|
53
|
+
def @logged_in_user.id; raise "Oops!"; end
|
54
|
+
outcome = CreateRepository.new(@logged_in_user).execute({})
|
55
|
+
|
56
|
+
outcome.pre_condition_failed do |pc|
|
57
|
+
assert_equal RuntimeError, pc.class
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "fails on input validation" do
|
62
|
+
outcome = CreateRepository.new(@logged_in_user).execute({})
|
63
|
+
|
64
|
+
validation = outcome.failure do |v|
|
65
|
+
refute v.valid?
|
66
|
+
assert_equal 1, v.errors.count
|
67
|
+
assert v.errors[:name]
|
68
|
+
end
|
69
|
+
|
70
|
+
refute_nil validation
|
71
|
+
end
|
72
|
+
|
73
|
+
it "executes command" do
|
74
|
+
outcome = CreateRepository.new(@logged_in_user).execute({ :name => "My repository" })
|
75
|
+
|
76
|
+
result = outcome.success do |res|
|
77
|
+
assert_equal "My repository", res.name
|
78
|
+
assert res.is_a?(Repository)
|
79
|
+
end
|
80
|
+
|
81
|
+
refute_nil result
|
82
|
+
end
|
83
|
+
|
84
|
+
it "raises if command raises" do
|
85
|
+
use_case = ExplodingRepository.new(@logged_in_user)
|
86
|
+
|
87
|
+
assert_raises RuntimeError do
|
88
|
+
use_case.execute(nil)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/use_case.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "./lib/use_case/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "use_case"
|
5
|
+
s.version = UseCase::VERSION
|
6
|
+
s.author = "Christian Johansen"
|
7
|
+
s.email = "christian@gitorious.com"
|
8
|
+
s.homepage = "http://gitorious.org/gitorious/use_case"
|
9
|
+
s.summary = s.description = ""
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.test_files = `git ls-files test`.split("\n")
|
13
|
+
s.require_path = "lib"
|
14
|
+
|
15
|
+
s.add_development_dependency "minitest", "~> 4"
|
16
|
+
s.add_development_dependency "rake"
|
17
|
+
|
18
|
+
s.required_ruby_version = ">= 1.9.2"
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: use_case
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Christian Johansen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: minitest
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '4'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: ''
|
47
|
+
email: christian@gitorious.com
|
48
|
+
executables: []
|
49
|
+
extensions: []
|
50
|
+
extra_rdoc_files: []
|
51
|
+
files:
|
52
|
+
- Gemfile
|
53
|
+
- Gemfile.lock
|
54
|
+
- LICENSE
|
55
|
+
- Rakefile
|
56
|
+
- Readme.md
|
57
|
+
- lib/use_case.rb
|
58
|
+
- lib/use_case/outcome.rb
|
59
|
+
- lib/use_case/validator.rb
|
60
|
+
- lib/use_case/version.rb
|
61
|
+
- test.rb
|
62
|
+
- test/sample_use_case.rb
|
63
|
+
- test/test_helper.rb
|
64
|
+
- test/use_case/outcome_test.rb
|
65
|
+
- test/use_case/validator_test.rb
|
66
|
+
- test/use_case_test.rb
|
67
|
+
- use_case.gemspec
|
68
|
+
homepage: http://gitorious.org/gitorious/use_case
|
69
|
+
licenses: []
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 1.9.2
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.24
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: ''
|
92
|
+
test_files:
|
93
|
+
- test/sample_use_case.rb
|
94
|
+
- test/test_helper.rb
|
95
|
+
- test/use_case/outcome_test.rb
|
96
|
+
- test/use_case/validator_test.rb
|
97
|
+
- test/use_case_test.rb
|