README.md
# RestMyCase [![Code Climate](https://codeclimate.com/github/goncalvesjoao/rest_my_case/badges/gpa.svg)](https://codeclimate.com/github/goncalvesjoao/rest_my_case) [![Build Status](https://travis-ci.org/goncalvesjoao/rest_my_case.svg?branch=master)](https://travis-ci.org/goncalvesjoao/rest_my_case) [![Test Coverage](https://codeclimate.com/github/goncalvesjoao/rest_my_case/badges/coverage.svg)](https://codeclimate.com/github/goncalvesjoao/rest_my_case) [![Gem Version](https://badge.fury.io/rb/rest_my_case.svg)](http://badge.fury.io/rb/rest_my_case)
Light Ruby gem with everything you need in a "The Clean Architecture" use case scenario.
Many thanks to [@tdantas](https://github.com/tdantas) and [@junhanamaki](https://github.com/junhanamaki) and a shout-out to [@joaquimadraz](https://github.com/joaquimadraz) and his [compel](https://github.com/joaquimadraz/compel) ruby validations gem.
---
## 1) Installation
Add this line to your application's Gemfile:
gem 'rest_my_case'
And then execute:
$ bundle
Or install it yourself as:
$ gem install rest_my_case
---
## 2) Ideology
Your business logic goes into separated use cases...
```ruby
class FindPost < RestMyCase::Base
def perform
context.post = Post.find(context.id)
end
end
class ArchivePost < RestMyCase::Base
depends FindPost
def perform
context.post.status = 'archived'
context.result = context.post.save
end
end
```
web framework should only act as a bridge between the user and your business logic.
```ruby
class PostsController < ApplicationController
def archive
@context = ArchivePost.perform id: params[:id]
if @context.result
redirect_to @context.post
else
render "archive" #view post.errors
end
end
end
```
Ideally your business logic should be a ruby gem that can be tested independently from your framework.
Checkout this step by step tutorial: (WIP) on how to isolate your code into a ruby gem and connect it to your rails or sinatra api.
---
## 3) Basic usage
```ruby
class BuildPost < RestMyCase::Base
def perform
puts context.id
puts context.post_attributes
end
end
```
```
irb> params = { id: 1, post: { title: 'my first post' } }
irb> context = BuildPost.perform id: params[:id], post_attributes: params[:post]
1
{:title=>"my first post"}
irb> context.id
1
```
The Hash passed down to **BuildPost.perform** will be available through an instance method called **#context** that will return an OpenStruct object initialized with that Hash (see more in section 7).
Executing **BuildPost.perform** will instantiate your use case and all of its **dependencies**, build a **context** with the contents of **params**, run your use case (and its dependencies) with that context and return it at the end.
## 3.1) Normal usage
Organize your use cases by single responsibilities and establish your use case flow through "dependencies".
```ruby
class FindPost < RestMyCase::Base
def perform
context.post = Post.find(context.id)
end
end
class BuildPost < RestMyCase::Base
depends FindPost
def perform
context.post.assign_attributes context.post_attributes
end
end
```
The class method **.depends** will make **BuildPost** dependent of **FindPost** which means that calling **BuildPost.perform** will run **FindPost#perform** first and **BuildPost#perform** last. Both use cases will share the same context.
```
irb> params = { id: 1, post: { title: 'my first post' } }
irb> context = BuildPost.perform id: params[:id], post_attributes: params[:post]
irb> context.post.name
"my first post"
```
---
## 4) Lifecycle
## 4.1) Waiting to be implemented methods
Methods: **#setup**, **#perform**, **#rollback** and **#final**
```ruby
class UseCase1 < RestMyCase::Base
def setup
puts 'UseCase1#setup'
end
def perform
puts 'UseCase1#perform'
error if context.should_fail
end
def rollback
puts 'UseCase1#rollback'
end
def final
puts 'UseCase1#final'
end
end
```
```
irb> UseCase1.perform #will print
"UseCase1#setup"
"UseCase1#perform"
"UseCase1#final"
```
Method **#rollback** will be called after **#perform** and before **#final** if **#error** is invoked inside a **#setup** of **#perform** (see more in section 5).
```
irb> UseCase1.perform(should_fail: true) #will print
"UseCase1#setup"
"UseCase1#perform"
"UseCase1#rollback"
"UseCase1#final"
```
Method **#final** will run last and always, no matter how many times **#error** was called.
---
### 4.2) Dependencies
Default behaviour is to run your dependencies (**#setup**, **#perform**, **#rollback** and **#final**) methods, first.
```ruby
class UseCase2 < RestMyCase::Base
def perform
puts 'UseCase2#perform'
end
end
class UseCase3 < RestMyCase::Base
def perform
puts 'UseCase3#perform'
end
end
class UseCase1 < RestMyCase::Base
depends UseCase2,
UseCase3
def perform
puts 'UseCase1#perform'
end
end
```
```
irb> UseCase1.perform #will print
"UseCase2#perform"
"UseCase3#perform"
"UseCase1#perform"
```
See section 8 for more examples.
---
## 5) Flow control methods
Methods | Behaviour
------- | ---------
**#abort** | Stops other remaining use cases from running and triggers **#rollback** on already executed use cases (in reverse order).
**#skip** | Will prevent **#perform** (of the use case that called **#skip**) from running and will not stop other use cases from running nor trigger a **#rollback** (only works by being used inside a **#setup** method).
**#error(error_message = '')** | Will do the same as **#abort** but will also push **error_message** to **#context.errors** array so you can track down what happen in what use case (see more in section 7).
**#invoke(*use_case_classes)** | Does the same as the class method **.depends** but executes the use cases on demand. Shares the context to them and if they call **#abort** on their side, the use case that **invoked** will also **abort**.
**#skip**, **#abort**, **#error** and **#invoke** have a "bang!" version that will raise a controlled exception, preventing the remaining lines of code from running.
```ruby
class UseCase1 < RestMyCase::Base
def perform
puts 'before #error!'
error!
puts 'after #error!'
end
end
```
```
irb> UseCase1.perform #will print only
"before #error!"
```
---
## 6) Configuration methods
Methods | Behaviour
------- | ---------
**.depends(*use_case_classes)** | Adds the **use_case_classes** array to the use case's **dependencies** list, that will be executed by order before the actual use case (see more in section 4).
**.required_context(*attributes)** | WIP.
**.context_reader(*methods)** | Defines getter methods that return **context.send method**, to help reduce the **context.method** boilerplate.
**.context_writer(*methods)** | Defines setter methods that set **context.send "#{method}=", value**, to help reduce the **context.method = value** boilerplate.
**.context_accessor(*methods)** | Calls both **.context_reader** and **.context_writer** methods.
**.silence_dependencies_abort=** | If **false** once a dependency calls **#abort(!)** the next in line dependencies will not be called (and **#rollback** will be called in reverse order) but if **true** all dependencies will run no matter how many times **#abort(!)** was called (usefull when you want to run multiple validations (see more in section 9).
---
## 7) **#context** methods
The returning object is an instance of **RestMyCase::Context::Base** class that inherits from **OpenStruct** and implements the following methods:
Methods | Behaviour
------- | ---------
**#attributes** | Alias to **#marshal_dump**, returns all of the context's stored data.
**#to_hash** | Serializes and unserializes **#attributes** turning any existing ruby objects into serialized strings.
**#values_at(*keys)** | Short for **#attributes.values_at(*keys)**, returns an array with correspondent values for each key.
**#valid?** | Checks if **#errors** is empty
**#ok?** | Alias to **#valid?**
**#success?** | Alias to **#ok?**
**#errors** | Array that gets 'pushed' with **{ message: error_message, class_name: UseCase.class.name }** (or **error_message** itself if **error_message** already a Hash) every time **UseCase#error(error_message)** is called.
If **defined?(ActiveModel)** is true, **ActiveModel::Serialization** will be included and in turn methods like **#to_json(options = {})** and **#serializable_hash(options = nil)** will become available.
---
### 8) Examples
If **UseCase1** depends on **UseCase2** and **UseCase3** in that respective order.
Running **UseCase1.perform** will pass down the context to each use case in the following manner:
#### 8.1) Given that no use case called the method(s) **#error(!)**
```
UseCase2#setup -> UseCase3#setup -> UseCase1#setup ->
UseCase2#perform -> UseCase3#perform -> UseCase1#perform ->
UseCase2#final -> UseCase3#final -> UseCase1#final
```
#### 8.2) Given that **UseCase3#setup** calls **#skip(!)**
```
UseCase2#setup -> UseCase3#setup -> UseCase1#setup ->
UseCase2#perform -> UseCase1#perform ->
UseCase2#final -> UseCase3#final -> UseCase1#final
```
#### 8.3) Given that **UseCase3#setup** calls **#error(!)**
```
UseCase2#setup -> UseCase3#setup ->
UseCase3#rollback -> UseCase2#rollback ->
UseCase2#final -> UseCase3#final -> UseCase1#final
```
#### 8.4) Given that **UseCase3#perform** calls **#error(!)**
```
UseCase2#setup -> UseCase3#setup -> UseCase1#setup ->
UseCase2#perform -> UseCase3#perform ->
UseCase3#rollback -> UseCase2#rollback ->
UseCase2#final -> UseCase3#final -> UseCase1#final
```
---
## 9) RestMyCase::Validator class
WIP
---
## 10) RestMyCase::Status module
```ruby
class UseCase1 < RestMyCase::Base
include RestMyCase::Status
end
```
Adds following methods:
Methods | Behaviour
------- | ---------
**#context** | Returns an instance of **RestMyCase::Context::Status**
**#status** | Returns **context.status** (see more in section 10.1)
**#failure(status, error_message = nil)** | WIP
**#failure!** is also present and does the same as the other flow control "bang!" methods (see section 5).
### 10.1) RestMyCase::Context::Status
WIP
---
## 11) RestMyCase::HttpStatus module (for seamless API integration)
```ruby
class UseCase1 < RestMyCase::Base
include RestMyCase::HttpStatus
end
```
Includes the module **RestMyCase::Status** and **#context** becomes an instance of **RestMyCase::Context::HttpStatus**.
### 11.1) RestMyCase::Context::HttpStatus
WIP