Readme.md in use_case-0.6.0 vs Readme.md in use_case-0.7.0

- old
+ new

@@ -153,37 +153,46 @@ # There's no contract to satiesfy with the constructor - design it to receive # any dependencies you need. def initialize(auth, user) input_class(NewRepositoryInput) - pre_condition(UserLoggedInPrecondition.new(user)) - pre_condition(ProjectAdminPrecondition.new(auth, 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) + add_pre_condition(UserLoggedInPrecondition.new(user)) + add_pre_condition(ProjectAdminPrecondition.new(auth, user)) + # A step is comprised of a command with 0, 1 or many validators + # (e.g. :validators => [...]) + # The use case can span multiple steps (see below) + step(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]* +User input (-> input sanitation) (-> pre-conditions) -> steps ``` 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 +4. Execute steps. The initial step is fed the sanitized input, each + following command is fed the result from the previous step. +Each step is a pipeline in its own right: + +``` +Step: (-> builder) (-> validations) -> command +``` + +1. Optionally refine input by running it through a pre-execution "builder" +2. Optionally run (refined) input through one or more validators +3. 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 @@ -220,31 +229,30 @@ 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 a command. +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 a command. 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. +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. +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. +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" @@ -287,49 +295,89 @@ def initialize input_class(NewUserInput) cmd = NewUserCommand.new # Use the command as a builder too - command(cmd, :builder => cmd, :validator => UserValidator) + step(cmd, :builder => cmd, :validator => UserValidator) end end # Usage: outcome = CreateUser.new.execute(:name => "Chris") outcome.success? #=> true outcome.result #=> #<User name: "Chris"> ``` +If the command fails to execute due to validation errors, using the builder +allows us to access the partial object for re-rendering forms etc. Because this +is such a common scenario, the command will automatically be used as the builder +as well if there is no explicit `:builder` option, and the command responds to +`build`. This means that the command in the previous example could be written as +so: + +```rb +class CreateUser + include UseCase + + def initialize + input_class(NewUserInput) + step(NewUserCommand.new, :validator => UserValidator) + end +end +``` + +When calling `execute` on this use case, we can observe the following flow: + +```rb +# This +params = { :name => "Dude" } +CreateUser.new.execute(params) + +# ...roughly expands to: +# (command is the command instance wired in the use case constructor) +input = NewUserInput.new(params) +prepared = command.build(input) + +if UserValidator.call(prepared).valid? + command.execute(prepared) +end +``` + ### Note -I'm not thrilled by `builder` as a name/concept. Suggestions for a -better name is welcome. +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 +A command is any Ruby object that defines an `execute(params)` method. +Alternately, it can be an object that responds to `call` (e.g. a lambda). Its 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. +If the command responds to the `build` message and there is no explicitly +configured `:builder` for the current step, the command is also used as a +builder (see example above, under "Builders"). + ## 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 `command` multiple times. +The use case can optionally call `input_class` once, `add_pre_condition` +multiple times, and `step` multiple times. -When using multiple commands, input sanitation with the `input_class` is +When using multiple steps, 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: +steps are executed. The use case will then execute the steps: ``` -command_1: sanitizied_input -> (builder ->) (validators ->) command -command_n: command_n-1 result -> (builder ->) (validators ->) command +step_1: sanitizied_input -> (builder ->) (validators ->) command +step_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.