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 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