Readme.md in use_case-0.3.0 vs Readme.md in use_case-0.4.0
- old
+ new
@@ -115,11 +115,11 @@
@user = user
end
# A pre-condition must define this method
# Params is an instance of NewRepositoryInput
- def satiesfied?(params)
+ def satisfied?(params)
!@user.nil?
end
end
# Another pre-condition that uses app-wide state
@@ -127,17 +127,17 @@
def initialize(auth, user)
@auth = auth
@user = user
end
- def satiesfied?(params)
+ def satisfied?(params)
@auth.can_admin?(@user, params.project)
end
end
# The business logic. Here we can safely assume that all pre-conditions are
-# satiesfied, and that input is valid and has the correct type.
+# satisfied, and that input is valid and has the correct type.
class CreateRepositoryCommand
def initialize(user)
@user = user
end
@@ -162,10 +162,27 @@
command(CreateRepositoryCommand.new(user))
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
+```
+
+* 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 with (refined) input
+
## 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
not use this, inputs are forwarded to pre-conditions and commands untouched
@@ -174,10 +191,19 @@
Datamapper 2's [Virtus](https://github.com/solnic/virtus) is a very promising
solution for input sanitation and some level of type-safety. If you provide a
`Virtus` backed class as `input_class` you will get an instance of that class as
`params` in pre-conditions and commands.
+## Pre-conditions
+
+A pre-condition is any object that responds to `satisfied?(params)` where
+params will either be a `Hash` or an instance of whatever you passed to
+`input_class`. The method should return `true/false`. If it raises, the outcome
+of the use case will call the `pre_condition_failed` block with the raised
+error. If it fails, the `pre_condition_failed` block will be called with the
+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
@@ -189,17 +215,93 @@
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`.
-## Pre-conditions
+## Builders
-A pre-condition is any object that responds to `satiesfied?(params)` where
-params will either be a `Hash` or an instance of whatever you passed to
-`input_class`. The method should return `true/false`. If it raises, the outcome
-of the use case will call the `pre_condition_failed` block with the raised
-error. If it fails, the `pre_condition_failed` block will be called with the
-pre-condition instance that failed.
+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 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 command.
+
+Builders can be useful if you want to run validations on a domain
+object rather than directly on "dumb" input.
+
+### Example
+
+In a Rails application, the builder is useful to wrap user input in an
+unsaved `ActiveRecord` instance. The unsaved object will be run
+through the validators, and (if found valid), the command can save it
+and perform additional tasks that you possibly do with `ActiveRecord`
+observers now.
+
+This example also shows how to express uniqueness validators when you
+move validations out of your `ActiveRecord` models.
+
+```rb
+require "activemodel"
+require "virtus"
+require "use_case"
+
+class User < ActiveRecord::Base
+ def uniq?
+ user = User.where("lower(name) = ?", name).first
+ user.nil? || user == self
+ end
+end
+
+UserValidator = UseCase::Validator.define do
+ validates_presence_of :name
+ validate :uniqueness
+
+ def uniqueness
+ errors.add(:name, "is taken") if !uniq?
+ end
+end
+
+class NewUserInput
+ include Virtus
+ attribute :name, String
+end
+
+class NewUserCommand
+ def execute(user)
+ user.save!
+ Mailer.user_signup(user).deliver
+ user
+ end
+
+ def build(params)
+ User.new(:name => params.name)
+ end
+end
+
+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)
+ end
+end
+
+# Usage:
+outcome = CreateUser.new.execute(:name => "Chris")
+outcome.success? #=> true
+outcome.result #=> #<User name: "Chris">
+```
+
+### Note
+
+I'm not thrilled by `builder` as a name/concept. Suggestions for a
+better name is welcome.
## Commands
A command is any Ruby object that defines an `execute(params)` method. Its
return value will be passed to the outcome's `success` block. Any errors raised