Readme.md in use_case-0.5.0 vs Readme.md in use_case-0.6.0
- old
+ new
@@ -28,11 +28,11 @@
The following example is a simplified use case from
[Gitorious](http://gitorious.org) where we want to create a new repository. To
do this, we need a user that can admin the project under which we want the new
repository to live.
-This example illustrates how to solve common design challenges in Rails
+_NB!_ This example illustrates how to solve common design challenges in Rails
applications; that does not mean that `UseCase` is only useful to Rails
applications.
First, let's look at what your Rails controller will look like using a
`UseCase`:
@@ -155,33 +155,34 @@
# any dependencies you need.
def initialize(auth, user)
input_class(NewRepositoryInput)
pre_condition(UserLoggedInPrecondition.new(user))
pre_condition(ProjectAdminPrecondition.new(auth, user))
- # Multiple validators can be added if needed
- validator(NewRepositoryValidator)
- command(CreateRepositoryCommand.new(user))
+ # A command has 0, 1 or many validators (e.g. :validators => [...])
+ # The use case can span multiple commands (see below)
+ command(CreateRepositoryCommand.new(user), :validator => NewRepositoryValidator)
end
end
```
## The use case pipeline at a glance
This is the high-level overview of how `UseCase` strings up a pipeline
for you to plug in various kinds of business logic:
```
-User input (-> input sanitation) (-> pre-conditions) (-> builder) (-> validations) -> command (-> command...)
+User input (-> input sanitation) (-> pre-conditions) [(-> builder) (-> validations) -> command]*
```
-* Start with a hash of user input
-* Optionally wrap this in an object that performs type-coercion,
- enforces types etc. By default, input will be wrapped in an `OpenStruct`
-* Optionally run pre-conditions on the santized input
-* Optionally refine input by running it through a pre-execution "builder"
-* Optionally (refined) input through one or more validators
-* Execute command(s) with (refined) input
+1. Start with a hash of user input
+2. Optionally wrap this in an object that performs type-coercion,
+ enforces types etc.
+3. Optionally run pre-conditions on the santized input
+4. Optionally refine input by running it through a pre-execution "builder"
+5. Optionally run (refined) input through one or more validators
+6. Execute command(s) with (refined) input
+7. Repeat steps 4-7 as necessary
## Input sanitation
In your `UseCase` instance (typically in the constructor), you can call the
`input_class` method to specify which class is used to santize inputs. If you do
@@ -203,31 +204,33 @@
pre-condition instance that failed.
## Validations
The validator uses `ActiveModel::Validations`, so any Rails validation can go in
-here. The main difference is that the validator is created as a stand-alone
-object that can be used with any model instance. This design allows you to
-define multiple context-sensitive validations for a single object.
+here (except for `validates_uniqueness_of`, which apparently comes from
+elsewhere - see example below for how to work around this). The main difference
+is that the validator is created as a stand-alone object that can be used with
+any model instance. This design allows you to define multiple context-sensitive
+validations for a single object.
You can of course provide your own validation if you want - any object that
defines `call(object)` and returns something that responds to `valid?` is good.
-I am following the Datamapper project closely in this area.
+I am following the Datamapper2 project closely in this area.
Because `UseCase::Validation` is not a required part of `UseCase`, and people
may want to control their own dependencies, `activemodel` is _not_ a hard
dependency. To use this feature, `gem install activemodel`.
## Builders
When user input has passed input sanitation and pre-conditions have
been satisfied, you can optionally pipe input through a "builder"
-before handing it over to validations and the commands.
+before handing it over to validations and a command.
-The builder should be an object with a `build` method. The method will
-be called with santized input. The return value from `build` will be
-passed on to validators and the commands.
+The builder should be an object with a `build` or a `call` method (if it has
+both, `build` will be preferred). The method will be called with santized input.
+The return value will be passed on to validators and the commands.
Builders can be useful if you want to run validations on a domain
object rather than directly on "dumb" input.
### Example
@@ -282,14 +285,13 @@
class CreateUser
include UseCase
def initialize
input_class(NewUserInput)
- validator(UserValidator)
cmd = NewUserCommand.new
- builder(cmd) # Use the command as a builder too
- command(cmd)
+ # Use the command as a builder too
+ command(cmd, :builder => cmd, :validator => UserValidator)
end
end
# Usage:
outcome = CreateUser.new.execute(:name => "Chris")
@@ -308,23 +310,30 @@
return value will be passed to the outcome's `success` block. Any errors raised
by this method is not rescued, so be sure to wrap `use_case.execute(params)` in
a rescue block if you're worried that it raises. Better yet, detect known causes
of exceptions in a pre-condition so you know that the command does not raise.
-A use case can execute multiple commands. When you do, the result of the first
-command will be the input to the second command and so on. The result of the
-last command will be the final `outcome.result`.
-
## Use cases
A use case simply glues together all the components. Define a class, include
`UseCase`, and configure the instance in the constructor. The constructor can
take any arguments you like, making this solution suitable for DI (dependency
injection) style designs.
The use case can optionally call `input_class` once, `pre_condition` multiple
-times, and `validator` multiple times. It *must* call `command` once with the
-command object.
+times, and `command` multiple times.
+
+When using multiple commands, input sanitation with the `input_class` is
+performed once only. Pre-conditions are also only checked once - before any
+commands are executed. The use case will then execute the commands:
+
+```
+command_1: sanitizied_input -> (builder ->) (validators ->) command
+command_n: command_n-1 result -> (builder ->) (validators ->) command
+```
+
+In other words, all commands except the first one will be executed with the
+result of the previous command as input.
## Outcomes
`UseCase#execute` returns an `Outcome`. You can use the outcome in primarily two
ways. The primary approach is one that takes blocks for the three situations: