use_case 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
- 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.
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
- require "use_case"
41
+ class RepositoryController < ApplicationController
42
+ include Gitorious::Authorization # Adds stuff like can_admin?(actor, thing)
25
43
 
26
- class HelloWorldCommand
27
- def execute(params)
28
- puts "Hello, #{params[:place]}!"
29
- end
30
- end
44
+ # ...
31
45
 
32
- class PrintHelloWorld
33
- include UseCase
46
+ def create
47
+ outcome = CreateRepository.new(self, current_user).execute(params)
34
48
 
35
- def initialize
36
- command(HelloWorldCommand.new)
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
- Not that you are free to design your constructors any way you want.
67
+ Executing the use case in an `irb` session could look like this:
45
68
 
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.
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
- 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.
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 anyway you like. One nice
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 has
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.
@@ -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
@@ -45,7 +45,7 @@ module UseCase
45
45
  end
46
46
  end
47
47
 
48
- klass.instance_eval(&block)
48
+ klass.class_eval(&block)
49
49
  klass
50
50
  end
51
51
  end
@@ -24,5 +24,5 @@
24
24
  #++
25
25
 
26
26
  module UseCase
27
- VERSION = "0.1.0"
27
+ VERSION = "0.2.0"
28
28
  end
@@ -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.1.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-26 00:00:00.000000000 Z
12
+ date: 2013-03-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest