use_case 0.1.0 → 0.2.0
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.
- data/Readme.md +76 -62
- data/lib/use_case/outcome.rb +3 -0
- data/lib/use_case/validator.rb +1 -1
- data/lib/use_case/version.rb +1 -1
- data/test/use_case/validator_test.rb +23 -0
- metadata +2 -2
data/Readme.md
CHANGED
@@ -11,65 +11,84 @@ Compose non-trivial business logic into use cases, that combine:
|
|
11
11
|
different contexts etc.
|
12
12
|
* Commands: Avoid defensive coding by performing the core actions in commands
|
13
13
|
that receive type-converted input, and are only executed when pre-conditions
|
14
|
-
are met and input validated.
|
14
|
+
are met and input is validated.
|
15
15
|
|
16
16
|
## Example
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
`UseCase` is designed to break up and keep non-trivial workflows understandable
|
19
|
+
and decoupled. As such, a trivial example would not illustrate what is good
|
20
|
+
about it. The following example is simplified, yet still has enough aspects to
|
21
|
+
show how `UseCase` helps you break things up.
|
22
|
+
|
23
|
+
Pre-conditions are conditions not directly related to input parameters alone,
|
24
|
+
and whose failure signifies other forms of errors than simple validation errors.
|
25
|
+
If you have a Rails application that uses controller filters, then those are
|
26
|
+
very likely good candidates for pre-conditions.
|
27
|
+
|
28
|
+
The following example is a simplified use case from
|
29
|
+
[Gitorious](http://gitorious.org) where we want to create a new repository. To
|
30
|
+
do this, we need a user that can admin the project under which we want the new
|
31
|
+
repository to live.
|
32
|
+
|
33
|
+
This example illustrates how to solve common design challenges in Rails
|
34
|
+
applications; that does not mean that `UseCase` is only useful to Rails
|
35
|
+
applications.
|
36
|
+
|
37
|
+
First, let's look at what your Rails controller will look like using a
|
38
|
+
`UseCase`:
|
22
39
|
|
23
40
|
```rb
|
24
|
-
|
41
|
+
class RepositoryController < ApplicationController
|
42
|
+
include Gitorious::Authorization # Adds stuff like can_admin?(actor, thing)
|
25
43
|
|
26
|
-
|
27
|
-
def execute(params)
|
28
|
-
puts "Hello, #{params[:place]}!"
|
29
|
-
end
|
30
|
-
end
|
44
|
+
# ...
|
31
45
|
|
32
|
-
|
33
|
-
|
46
|
+
def create
|
47
|
+
outcome = CreateRepository.new(self, current_user).execute(params)
|
34
48
|
|
35
|
-
|
36
|
-
|
49
|
+
outcome.pre_condition_failed do |condition|
|
50
|
+
redirect_to(login_path) and return if condition.is_a?(UserLoggedInPrecondition)
|
51
|
+
flash[:error] = "You're not allowed to do that"
|
52
|
+
redirect_to project_path
|
53
|
+
end
|
54
|
+
|
55
|
+
outcome.failure do |model|
|
56
|
+
# Render form with validation errors
|
57
|
+
render :new, :locals => { :repository => model }
|
58
|
+
end
|
59
|
+
|
60
|
+
outcome.success do |repository|
|
61
|
+
redirect_to(repository_path(repository))
|
62
|
+
end
|
37
63
|
end
|
38
64
|
end
|
39
|
-
|
40
|
-
# Usage
|
41
|
-
PrintHelloWorld.new.execute(:place => "World")
|
42
65
|
```
|
43
66
|
|
44
|
-
|
67
|
+
Executing the use case in an `irb` session could look like this:
|
45
68
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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.
|
69
|
+
```rb
|
70
|
+
include Gitorious::Authorization
|
71
|
+
user = User.find_by_login("christian")
|
72
|
+
project = Project.find_by_name("gitorious")
|
73
|
+
outcome = CreateRepository.new(self, user).execute(:project => project,
|
74
|
+
:name => "use_case")
|
75
|
+
outcome.success? #=> true
|
76
|
+
outcome.result.name #=> "use_case"
|
77
|
+
```
|
58
78
|
|
59
|
-
|
60
|
-
applications, but that does not mean that `UseCase` is only useful to Rails
|
61
|
-
applications.
|
79
|
+
The code behind this use case follows:
|
62
80
|
|
63
81
|
```rb
|
64
82
|
require "use_case"
|
65
83
|
require "virtus"
|
66
84
|
|
67
|
-
# Input parameters can be sanitized and pre-processed
|
85
|
+
# Input parameters can be sanitized and pre-processed any way you like. One nice
|
68
86
|
# way to go about it is to use Datamapper 2's Virtus gem to define a parameter
|
69
87
|
# set.
|
70
88
|
#
|
71
89
|
# 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
|
90
|
+
# provided and project is not. This is the only class that directly touches
|
91
|
+
# classes from the Rails application.
|
73
92
|
class NewRepositoryInput
|
74
93
|
include Virtus
|
75
94
|
attribute :name, String
|
@@ -143,32 +162,6 @@ class CreateRepository
|
|
143
162
|
command(CreateRepositoryCommand.new(user))
|
144
163
|
end
|
145
164
|
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
165
|
```
|
173
166
|
|
174
167
|
## Input sanitation
|
@@ -269,6 +262,27 @@ work by the "data in, data out" principle, meaning you can easily test them with
|
|
269
262
|
any kind of object (which spares you of loading heavy ActiveRecord-bound models,
|
270
263
|
running opaque controller tets etc).
|
271
264
|
|
265
|
+
## Installation
|
266
|
+
|
267
|
+
$ gem install use_case
|
268
|
+
|
269
|
+
## Developing
|
270
|
+
|
271
|
+
$ bundle install
|
272
|
+
$ rake
|
273
|
+
|
274
|
+
## Contributing
|
275
|
+
|
276
|
+
* Clone repo
|
277
|
+
* Make changes
|
278
|
+
* Add test(s)
|
279
|
+
* Run tests
|
280
|
+
* If adding new abilities, add docs in Readme, or commit a working example
|
281
|
+
* Send patch, [pull request](http://github.com/cjohansen/use_case) or [merge request](http://gitorious.org/gitorious/use_case)
|
282
|
+
|
283
|
+
If you intend to add entirely new features, you might want to open an issue to
|
284
|
+
discuss it with me first.
|
285
|
+
|
272
286
|
## License
|
273
287
|
|
274
288
|
UseCase is free software licensed under the MIT license.
|
data/lib/use_case/outcome.rb
CHANGED
@@ -34,6 +34,7 @@ module UseCase
|
|
34
34
|
def pre_condition_failed?; false; end
|
35
35
|
def success?; false; end
|
36
36
|
def success; end
|
37
|
+
def result; end
|
37
38
|
def pre_condition_failed; end
|
38
39
|
def failure; end
|
39
40
|
end
|
@@ -51,6 +52,8 @@ module UseCase
|
|
51
52
|
@result
|
52
53
|
end
|
53
54
|
|
55
|
+
def result; @result; end
|
56
|
+
|
54
57
|
def to_s
|
55
58
|
"#<UseCase::SuccessfulOutcome: #{@result}>"
|
56
59
|
end
|
data/lib/use_case/validator.rb
CHANGED
data/lib/use_case/version.rb
CHANGED
@@ -29,6 +29,14 @@ NewPersonValidator = UseCase::Validator.define do
|
|
29
29
|
validates_presence_of :name
|
30
30
|
end
|
31
31
|
|
32
|
+
CustomValidator = UseCase::Validator.define do
|
33
|
+
validate :validate_custom
|
34
|
+
|
35
|
+
def validate_custom
|
36
|
+
errors.add(:name, "is not Dude") if name != "Dude"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
32
40
|
class Person
|
33
41
|
attr_accessor :name
|
34
42
|
end
|
@@ -48,4 +56,19 @@ describe UseCase::Validator do
|
|
48
56
|
refute result.valid?
|
49
57
|
assert_equal 1, result.errors.count
|
50
58
|
end
|
59
|
+
|
60
|
+
it "supports custom validators" do
|
61
|
+
result = CustomValidator.call(Person.new)
|
62
|
+
|
63
|
+
refute result.valid?
|
64
|
+
assert_equal 1, result.errors.count
|
65
|
+
end
|
66
|
+
|
67
|
+
it "passes custom validator" do
|
68
|
+
person = Person.new
|
69
|
+
person.name = "Dude"
|
70
|
+
result = CustomValidator.call(person)
|
71
|
+
|
72
|
+
assert result.valid?
|
73
|
+
end
|
51
74
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: use_case
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|