Skip to content

Commit

Permalink
Update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
drexed committed Sep 27, 2024
1 parent 796a060 commit d711358
Showing 1 changed file with 85 additions and 231 deletions.
316 changes: 85 additions & 231 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
[![Build Status](https://travis-ci.org/drexed/lite-command.svg?branch=master)](https://travis-ci.org/drexed/lite-command)

Lite::Command provides an API for building simple and complex command based service objects.
It provides extensions for handling errors and memoization to improve your object workflow productivity.

## Installation

Expand All @@ -25,273 +24,128 @@ Or install it yourself as:
## Table of Contents

* [Setup](#setup)
* [Simple](#simple)
* [Complex](#complex)
* [Procedure](#procedure)
* [Extensions](#extensions)
* [Execution](#execution)
* [Context](#context)
* [Internals](#Internals)
* [Generator](#generator)

## Setup

`rails g command NAME` will generate the following file:

```erb
app/commands/[NAME]_command.rb
```

If a `ApplicationCommand` file in the `app/commands` directory is available, the
generator will create file that inherit from `ApplicationCommand` if not it will
fallback to `Lite::Command::Complex`.

## Simple

Simple commands are a traditional command service call objects.
It only exposes a `call` method that returns a value.

**Setup**
Defining a command is as simple as adding a call method.

```ruby
class CalculatePower < Lite::Command::Simple
class CalculatePower < Lite::Command::Base

# NOTE: This `execute` class method is required to use with call
def self.execute(a, b)
a**b
def call
# TODO: implement calculator
end

end
```

**Callers**

```ruby
CalculatePower.execute(2, 2) #=> 4

# - or -

CalculatePower.call(2, 3) #=> 8
```

## Complex

Complex commands are powerful command service call objects.
It can be extended to use error, memoization, and propagation mixins.

**Setup**

```ruby
class SearchMovies < Lite::Command::Complex

attr_reader :name

def initialize(name)
@name = name
end

# NOTE: This `execute` instance method is required to use with call
def execute
{ generate_fingerprint => movies_by_name }
end

private

def movies_by_name
HTTP.get("http://movies.com?title=#{name}")
end
## Execution

def generate_fingerprint
Digest::MD5.hexdigest(movies_by_name)
end

end
```
Executing a command can be done as an instance or class call.
It returns the command instance in a forzen state.
These will never call will never raise an execption, but will
be kept track of in its internal state.

**Caller**
**NOTE:** Class calls is the prefered format due to its readability.

```ruby
SearchMovies.execute('Toy Story') #=> { 'fingerprint_1' => [ 'Toy Story 1', ... ] }

# - or -

command = SearchMovies.new('Toy Story')
command.called? #=> false
command.call #=> { 'fingerprint_1' => [ 'Toy Story 1', ... ] }
command.called? #=> true
# Class call
CalculatePower.call(..args)

# - or -
# Instance call
caculator = CalculatePower.new(..args).call

command = SearchMovies.call('Toy Story')
command.called? #=> true
command.call #=> { 'fingerprint_1' => [ 'Toy Story 1', ... ] }
#=> <CalculatePower ...>
```

**Result**
Commands can be called with a `!` bang method to raise a
`Lite::Command::Fault` based exception or the original
`StandardError` based exception.

```ruby
command = SearchMovies.new('Toy Story')
command.result #=> nil

command.call #=> { 'fingerprint_1' => [ 'Toy Story 1', ... ] }
command.result #=> { 'fingerprint_1' => [ 'Toy Story 1', ... ] }

command.recall! #=> Clears the `call`, `cache`, `errors` variables and then re-performs the call
command.result #=> { 'fingerprint_2' => [ 'Toy Story 2', ... ] }
CalculatePower.call!(..args)
#=> raises Lite::Command::Fault
```

## Procedure

Procedures are used to run a collection of commands. It uses the the complex procedure API
so it has access to all the methods. The `execute` method is already defined to
handle most common procedure steps. It can be use directly or subclassed.
## Context

**Setup**

```ruby
class SearchChannels < Lite::Command::Procedure; end
```
Accessing the call arguments can be done through its internal context.
It can be used as internal storage to be accessed by it self and any
of its children commands.

```ruby
commands = [DisneyChannel.new, EspnChannel.new(current_station), MtvChannel.new]

procedure = SearchChannels.call(*commands)
procedure.result #=> ['disney: #3', 'espn: #59', 'mtv: #212']
procedure.steps #=> [<DisneyChannel @result="...">, <EspnChannel @result="...">, <MtvChannel @result="...">]

# - or -

# If the errors extension is added you can stop the procedure at first failure.
procedure = SearchChannels.new(*commands)
procedure.exit_on_failure = true
procedure.call
procedure.result #=> ['disney: #3']
procedure.failed_steps #=> [{ index: 1, step: 2, name: 'ErrorChannel', args: [current_station], errors: ['field error message'] }]
```

## Extensions
class CalculatePower < Lite::Command::Base

Extend complex (and procedures) base command with any of the following extensions:

### Errors (optional)

Learn more about using [Lite::Errors](https://github.com/drexed/lite-errors)

**Setup**

```ruby
class SearchMovies < Lite::Command::Complex
include Lite::Command::Extensions::Errors

# ... ommited ...

private

# Add a explicit and/or exception errors to the error pool
def generate_fingerprint
if movies_by_name.nil?
errors.add(:fingerprint, 'invalid md5 request value')
else
Digest::MD5.hexdigest(movies_by_name)
end
rescue ArgumentError => exception
merge_exception!(exception, key: :custom_error_key)
def call
context.result = context.a ** context.b
end

end
```

**Instance Callers**

```ruby
command = SearchMovies.call('Toy Story')
command.errors #=> Lite::Errors::Messages object

command.validate! #=> Raises Lite::Command::ValidationError if it has any errors
command.valid? #=> Alias for validate!

command.errored? #=> false
command.success? #=> true
command.failure? #=> Checks that it has been called and has errors
command.status #=> :failure

command.result! #=> Raises Lite::Command::ValidationError if it has any errors, if not it returns the result

# Use the following to merge errors from other commands or models
# with the default direction being `:from`
command.merge_errors!(command_2)
user_model.merge_errors!(command, direction: :to)
```

**Block Callers**

```ruby
# Useful for controllers or actions that depend on states.
SearchMovies.perform('Toy Story') do |result, success, failure|
success.call { redirect_to(movie_path, notice: "Movie can be found at: #{result}") }
failure.call { redirect_to(root_path, notice: "Movie cannot be found at: #{result}") }
end
```

### Propagation (optional)

Propagation methods help you perform an action on an object. If successful is
returns the result else it adds the object errors to the form object. Available
propagation methods are:
- `assign_and_return!(object, params)`
- `create_and_return!(klass, params)`
- `update_and_return!(object, params)`
- `destroy_and_return!(object)`
- `archive_and_return!(object)` (if using Lite::Archive)
- `save_and_return!(object)`

**Setup**

```ruby
class SearchMovies < Lite::Command::Complex
include Lite::Command::Extensions::Errors
include Lite::Command::Extensions::Propagation

# ... ommited ...

def execute
create_and_return!(User, name: 'John Doe')
end

end
command = CalculatePower.call(a: 2, b: 3)
command.context.result #=> 8
```

### Memoize (optional)

Learn more about using [Lite::Memoize](https://github.com/drexed/lite-memoize)
## Internals

#### States
State represents the state of the executable code. Once `execute`
is ran, it will always `complete` or `dnf` if a fault is thrown by a
child command.

- `pending`
- Command objects that have been initialized.
- `executing`
- Command objects actively executing code.
- `complete`
- Command objects that executed to completion.
- `dnf`
- Command objects that could NOT be executed to completion.
This could be as a result of a fault/exception on the
object itself or one of its children.

#### Statuses

Status represents the state of the callable code. If no fault
is thrown then a status of `success` is returned even if `call`
has not been executed. The list of status include (by severity):

- `success`
- No fault or exception
- `noop`
- Noop represents skipping completion of call execution early
an unsatisfied condition or logic check where there is no
point on proceeding.
- **eg:** account is sample: skip since its a non-alterable record
- `invalid`
- Invalid represents a stoppage of call execution due to
missing, bad, or corrupt data.
- **eg:** user not found: stop since rest of the call cant be executed
- `failure`
- Failure represents a stoppage of call execution due to
an unsatisfied condition or logic check where it blocks
proceeding any further.
- **eg:** record not found: stop since there is nothing todo
- `error`
- Error represents a caught exception for a call execution
that could not complete.
- **eg:** ApiServerError: stop since there was a 3rd party issue

## Generator

**Setup**

```ruby
class SearchMovies < Lite::Command::Complex
include Lite::Command::Extensions::Memoize

# ... ommited ...

private

# Sets the value in the cache
# Subsequent method calls gets the cached value
# This saves you the extra external HTTP.get call
def movies_by_name
cache.memoize { HTTP.get("http://movies.com?title=#{name}") }
end

# Gets the value in the cache
def generate_fingerprint
Digest::MD5.hexdigest(movies_by_name)
end
`rails g command NAME` will generate the following file:

end
```erb
app/commands/[NAME]_command.rb
```

**Callers**

```ruby
command = SearchMovies.call('Toy Story')
command.cache #=> Lite::Memoize::Instance object
```
If a `ApplicationCommand` file in the `app/commands` directory is available, the
generator will create file that inherit from `ApplicationCommand` if not it will
fallback to `Lite::Command::Base`.

## Development

Expand Down

0 comments on commit d711358

Please sign in to comment.