Gem Build Status Maintainability Test Coverage

μ-case (Micro::Case)

Create simple and powerful use cases as objects.

The main project goals are: 1. Be simple to use and easy to learn (input » process / transform » output). 2. Promote referential transparency (transforming instead of modifying) and data integrity. 3. No callbacks (e.g: before, after, around). 4. Solve complex business logic, by allowing the composition of use cases. 5. Be fast and optimized (Check out the benchmarks section).

Table of Contents

Required Ruby version

>= 2.2.0

Dependencies

This project depends on Micro::Attribute gem. It is used to define the use case attributes.

Installation

Add this line to your application’s Gemfile:

ruby gem 'u-case'

And then execute:

$ bundle

Or install it yourself as:

$ gem install u-case

Usage

Micro::Case - How to define a use case?

```ruby class Multiply < Micro::Case # 1. Define its input as attributes attributes :a, :b

# 2. Define the method call! with its business logic def call!

# 3. Wrap the use case result/output using the `Success()` or `Failure()` methods
if a.is_a?(Numeric) && b.is_a?(Numeric)
  Success(a * b)
else
  Failure { '`a` and `b` attributes must be numeric' }
end   end end

==========================

# Calling a use case class # #==========================#

Success result

result = Multiply.call(a: 2, b: 2)

result.success? # true result.value # 4

Failure result

bad_result = Multiply.call(a: 2, b: ‘2’)

bad_result.failure? # true bad_result.value # “a and b attributes must be numeric”

—————————–

# Calling a use case instance # #—————————–#

result = Multiply.new(a: 2, b: 3).call

result.value # 6

Note:

# —- # The result of a Micro::Case.call # is an instance of Micro::Case::Result ```

⬆️ Back to Top

Micro::Case::Result - What is a use case result?

A Micro::Case::Result stores the use cases output data. These are their main methods: - #success? returns true if is a successful result. - #failure? returns true if is an unsuccessful result. - #value the result value itself. - #type a Symbol which gives meaning for the result, this is useful to declare different types of failures or success. - #on_success or #on_failure are hook methods that help you define the application flow. - #use_case if is a failure result, the use case responsible for it will be accessible through this method. This feature is handy to handle a flow failure (this topic will be covered ahead).

⬆️ Back to Top

What are the default result types?

Every result has a type and these are the defaults: - :ok when success - :error/:exception when failures

```ruby class Divide < Micro::Case attributes :a, :b

def call! invalid_attributes.empty? ? Success(a / b) : Failure(invalid_attributes) rescue => e Failure(e) end

private def invalid_attributes attributes.select { |_key, value| !value.is_a?(Numeric) } end end

Success result

result = Divide.call(a: 2, b: 2)

result.type # :ok result.value # 1 result.success? # true result.use_case # raises Micro::Case::Error::InvalidAccessToTheUseCaseObject: only a failure result can access its own use case

Failure result (type == :error)

bad_result = Divide.call(a: 2, b: ‘2’)

bad_result.type # :error bad_result.value # “b”=>”2” bad_result.failure? # true bad_result.use_case # #<Divide:0x0000 @__attributes=“b”=>”2”, @a=2, @b=”2”, @__result=#<Micro::Case::Result:0x0000 @use_case=#<Divide:0x0000 …>, @type=:error, @value=“b”=>”2”, @success=false»

Failure result (type == :exception)

err_result = Divide.call(a: 2, b: 0)

err_result.type # :exception err_result.value # <ZeroDivisionError: divided by 0> err_result.failure? # true err_result.use_case # #<Divide:0x0000 @__attributes=“b”=>0, @a=2, @b=0, @__result=#<Micro::Case::Result:0x0000 @use_case=#<Divide:0x0000 …>, @type=:exception, @value=#<ZeroDivisionError: divided by 0>, @success=false»

Note:

# —- # Any Exception instance which is wrapped by # the Failure() method will receive :exception instead of the :error type. ```

⬆️ Back to Top

How to define custom result types?

Answer: Use a symbol as the argument of Success(), Failure() methods and declare a block to set their values.

```ruby class Multiply < Micro::Case attributes :a, :b

def call! return Success(a * b) if a.is_a?(Numeric) && b.is_a?(Numeric)

Failure(:invalid_data) do
  attributes.reject { |_, input| input.is_a?(Numeric) }
end   end end

Success result

result = Multiply.call(a: 3, b: 2)

result.type # :ok result.value # 6 result.success? # true

Failure result

bad_result = Multiply.call(a: 3, b: ‘2’)

bad_result.type # :invalid_data bad_result.value # “b”=>”2” bad_result.failure? # true ```

⬆️ Back to Top

Is it possible to define a custom result type without a block?

Answer: Yes, it is. But only for failure results!

```ruby class Multiply < Micro::Case attributes :a, :b

def call! return Failure(:invalid_data) unless a.is_a?(Numeric) && b.is_a?(Numeric)

Success(a * b)   end end

result = Multiply.call(a: 2, b: ‘2’)

result.failure? # true result.value # :invalid_data result.type # :invalid_data result.use_case.attributes # “b”=>”2”

Note:

# —- # This feature is handy to handle failures in a flow # (this topic will be covered ahead). ```

⬆️ Back to Top

How to use the result hooks?

As mentioned earlier, the Micro::Case::Result has two methods to improve the flow control. They are: #on_success, on_failure.

The examples below show how to use them:

```ruby class Double < Micro::Case attribute :number

def call! return Failure(:invalid) { ‘the number must be a numeric value’ } unless number.is_a?(Numeric) return Failure(:lte_zero) { ‘the number must be greater than 0’ } if number <= 0

Success(number * 2)   end end

================================

# Printing the output if success # #================================#

Double .call(number: 3) .on_success { |number| p number } .on_failure(:invalid) { |msg| raise TypeError, msg } .on_failure(:lte_zero) { |msg| raise ArgumentError, msg }

The output because it is a success:

# 6

=============================

# Raising an error if failure # #=============================#

Double .call(number: -1) .on_success { |number| p number } .on_failure { |_result, use_case| puts “#use_caseuse_case.classuse_case.class.name was the use case responsible for the failure” } .on_failure(:invalid) { |msg| raise TypeError, msg } .on_failure(:lte_zero) { |msg| raise ArgumentError, msg }

The outputs will be:

# # 1. Prints the message: Double was the use case responsible for the failure # 2. Raises the exception: ArgumentError (the number must be greater than 0)

Note:

# —- # The use case responsible for the failure will be accessible as the second hook argument ```

Why the failure hook (without a type) exposes a different kind of data?

Answer: To allow you to define how to handle the program flow using some conditional statement (like an if, case/when).

```ruby class Double < Micro::Case attribute :number

def call! return Failure(:invalid) unless number.is_a?(Numeric) return Failure(:lte_zero) { number } if number <= 0

Success(number * 2)   end end

=================================

# Using the result type and value # #=================================#

Double .call(-1) .on_failure do |result, use_case| case result.type when :invalid then raise TypeError, ‘the number must be a numeric value’ when :lte_zero then raise ArgumentError, “the number #{result.value} must be greater than 0” else raise NotImplementedError end end

The output will be the exception:

# # ArgumentError (the number -1 must be greater than 0)

=====================================================

# Using decomposition to access result value and type # #=====================================================#

The syntax to decompose an Array can be used in methods, blocks and assigments.

# If you doesn’t know that, check out: # https://ruby-doc.org/core-2.2.0/doc/syntax/assignment_rdoc.html#label-Array+Decomposition # # And the object exposed in the hook failure can be decomposed using this syntax. e.g:

Double .call(-2) .on_failure do |(value, type), use_case| case type when :invalid then raise TypeError, ‘the number must be a numeric value’ when :lte_zero then raise ArgumentError, “the number #{value} must be greater than 0” else raise NotImplementedError end end

The output will be the exception:

# # ArgumentError (the number -2 must be greater than 0) ```

⬆️ Back to Top

What happens if a result hook was declared multiple times?

Answer: The hook always will be triggered if it matches the result type.

```ruby class Double < Micro::Case attributes :number

def call! return Failure(:invalid) { ‘the number must be a numeric value’ } unless number.is_a?(Numeric)

Success(:computed) { number * 2 }   end end

result = Double.call(number: 3) result.value # 6 result.value * 4 # 24

accum = 0

result.on_success { number accum += number }
.on_success { number accum += number }
.on_success(:computed) { number accum += number }
.on_success(:computed) { number accum += number }

accum # 24

result.value * 4 == accum # true ```

⬆️ Back to Top

Micro::Case::Flow - How to compose use cases?

In this case, this will be a flow (Micro::Case::Flow). The main idea of this feature is to use/reuse use cases as steps of a new use case.

```ruby module Steps class ConvertTextToNumbers < Micro::Case attribute :numbers

def call!
  if numbers.all? { |value| String(value) =~ /\d+/ }
    Success(numbers: numbers.map(&:to_i))
  else
    Failure('numbers must contain only numeric types')
  end
end   end

class Add2 < Micro::Case::Strict attribute :numbers

def call!
  Success(numbers: numbers.map { |number| number + 2 })
end   end

class Double < Micro::Case::Strict attribute :numbers

def call!
  Success(numbers: numbers.map { |number| number * 2 })
end   end

class Square < Micro::Case::Strict attribute :numbers

def call!
  Success(numbers: numbers.map { |number| number * number })
end   end end

———————————————

# Creating a flow using the collection syntax # #———————————————#

Add2ToAllNumbers = Micro::Case::Flow([ Steps::ConvertTextToNumbers, Steps::Add2 ])

result = Add2ToAllNumbers.call(numbers: %w[1 1 2 2 3 4])

p result.success? # true p result.value # => [3, 3, 4, 4, 5, 6]

—————————————————

# An alternative way to create a flow using classes # #—————————————————#

class DoubleAllNumbers < Micro::Case flow Steps::ConvertTextToNumbers, Steps::Double end

DoubleAllNumbers .call(numbers: %w[1 1 b 2 3 4]) .on_failure { |message| p message } # “numbers must contain only numeric types”

!———————————— !

# ! Deprecated: Micro::Case::Flow mixin ! # # !————————————-! #

The code below still works, but it will output a warning message:

# Deprecation: Micro::Case::Flow mixin is being deprecated, please use Micro::Case inheritance instead.

class DoubleAllNumbers include Micro::Case::Flow

flow Steps::ConvertTextToNumbers, Steps::Double end

Note: This feature will be removed in the next major release (3.0)

————————————————————-

# Another way to create a flow using the composition operator # #————————————————————-#

SquareAllNumbers = Steps::ConvertTextToNumbers » Steps::Square

SquareAllNumbers .call(numbers: %w[1 1 2 2 3 4]) .on_success { |value| p value[:numbers] } # [1, 1, 4, 4, 9, 16]

Note:

# —- # When happening a failure, the use case responsible # will be accessible in the result

result = SquareAllNumbers.call(numbers: %w[1 1 b 2 3 4])

result.failure? # true result.use_case.is_a?(Steps::ConvertTextToNumbers) # true

result.on_failure do |_message, use_case| puts “#use_caseuse_case.classuse_case.class.name was the use case responsible for the failure” # Steps::ConvertTextToNumbers was the use case responsible for the failure end ```

⬆️ Back to Top

Is it possible to compose a use case flow with other ones?

Answer: Yes, it is.

```ruby module Steps class ConvertTextToNumbers < Micro::Case attribute :numbers

def call!
  if numbers.all? { |value| String(value) =~ /\d+/ }
    Success(numbers: numbers.map(&:to_i))
  else
    Failure('numbers must contain only numeric types')
  end
end   end

class Add2 < Micro::Case::Strict attribute :numbers

def call!
  Success(numbers: numbers.map { |number| number + 2 })
end   end

class Double < Micro::Case::Strict attribute :numbers

def call!
  Success(numbers: numbers.map { |number| number * 2 })
end   end

class Square < Micro::Case::Strict attribute :numbers

def call!
  Success(numbers: numbers.map { |number| number * number })
end   end end

Add2ToAllNumbers = Steps::ConvertTextToNumbers » Steps::Add2 DoubleAllNumbers = Steps::ConvertTextToNumbers » Steps::Double SquareAllNumbers = Steps::ConvertTextToNumbers » Steps::Square

DoubleAllNumbersAndAdd2 = DoubleAllNumbers » Steps::Add2 SquareAllNumbersAndAdd2 = SquareAllNumbers » Steps::Add2

SquareAllNumbersAndDouble = SquareAllNumbersAndAdd2 » DoubleAllNumbers DoubleAllNumbersAndSquareAndAdd2 = DoubleAllNumbers » SquareAllNumbersAndAdd2

SquareAllNumbersAndDouble .call(numbers: %w[1 1 2 2 3 4]) .on_success { |value| p value[:numbers] } # [6, 6, 12, 12, 22, 36]

DoubleAllNumbersAndSquareAndAdd2 .call(numbers: %w[1 1 2 2 3 4]) .on_success { |value| p value[:numbers] } # [6, 6, 18, 18, 38, 66] ```

Note: You can blend any of the available syntaxes/approaches to create use case flows - examples.

⬆️ Back to Top

Is it possible a flow accumulates its input and merges each success result to use as the argument of their use cases?

Answer: Yes, it is! Check out these test examples Micro::Case::Flow and Micro::Case::Safe::Flow to see different use cases sharing their own data.

⬆️ Back to Top

Is it possible to declare a flow using the use case itself?

Answer: Yes, it is! You can use the self.call! macro. e.g:

```ruby class ConvertTextToNumber < Micro::Case attribute :text

def call! Success { { number: text.to_i } } end end

class ConvertNumberToText < Micro::Case attribute :number

def call! Success { { text: number.to_s } } end end

class Double < Micro::Case attribute :number

def call! Success { { number: number * 2 } } end

# NOTE: You need to declare the flow after the definition of the attributes. flow ConvertTextToNumber, self.call!, ConvertNumberToText end

result = Double.call(text: ‘4’)

result.success? # true result.value # “8”

NOTE: This feature can be used with the Micro::Case::Safe.

# Checkout the test: test/micro/case/safe/flow/with_classes/using_itself_test.rb ```

⬆️ Back to Top

Micro::Case::Strict - What is a strict use case?

Answer: Is a use case which will require all the keywords (attributes) on its initialization.

```ruby class Double < Micro::Case::Strict attribute :numbers

def call! Success(numbers.map { |number| number * 2 }) end end

Double.call({})

The output will be the following exception:

# ArgumentError (missing keyword: :numbers) ```

⬆️ Back to Top

Micro::Case::Safe - Is there some feature to auto handle exceptions inside of a use case or flow?

Answer: Yes, there is!

Use cases:

Like Micro::Case::Strict the Micro::Case::Safe is another kind of use case. It has the ability to auto intercept any exception as a failure result. e.g:

```ruby require ‘logger’

AppLogger = Logger.new(STDOUT)

class Divide < Micro::Case::Safe attributes :a, :b

def call! return Success(a / b) if a.is_a?(Integer) && b.is_a?(Integer) Failure(:not_an_integer) end end

result = Divide.call(a: 2, b: 0) result.type == :exception # true result.value.is_a?(ZeroDivisionError) # true

result.on_failure(:exception) do |exception| AppLogger.error(exception.message) # E, [2019-08-21T00:05:44.195506 #9532] ERROR – : divided by 0 end

Note:

# —- # If you need to handle a specific error, # I recommend the usage of a case statement. e,g:

result.on_failure(:exception) do |exception, use_case| case exception when ZeroDivisionError then AppLogger.error(exception.message) else AppLogger.debug(“#use_caseuse_case.classuse_case.class.name was the use case responsible for the exception”) end end

Another note:

# ———— # It is possible to rescue an exception even when is a safe use case. # Examples: https://github.com/serradura/u-case/blob/5a85fc238b63811a32737493dc6c59965f92491d/test/micro/case/safe_test.rb#L95-L123 ```

Flows:

As the safe use cases, safe flows can intercept an exception in any of its steps. These are the ways to define one:

```ruby module Users Create = ProcessParams & ValidateParams & Persist & SendToCRM end

Note:

# The ampersand is based on the safe navigation operator. https://ruby-doc.org/core-2.6/doc/syntax/calling_methods_rdoc.html#label-Safe+navigation+operator

The alternatives to declare a safe flow are:

module Users Create = Micro::Case::Safe::Flow([ ProcessParams, ValidateParams, Persist, SendToCRM ]) end

or within classes

module Users class Create < Micro::Case::Safe flow ProcessParams, ValidateParams, Persist, SendToCRM end end

!—————————————— !

# ! Deprecated: Micro::Case::Safe::Flow mixin ! # # !——————————————-! #

The code below still works, but it will output a warning message:

# Deprecation: Micro::Case::Flow mixin is being deprecated, please use Micro::Case inheritance instead.

module Users class Create include Micro::Case::Safe::Flow

flow ProcessParams, ValidateParams, Persist, SendToCRM   end end

Note: This feature will be removed in the next major release (3.0)

```

⬆️ Back to Top

u-case/with_validation - How to validate use case attributes?

Requirement:

To do this your application must have the activemodel >= 3.2 as a dependency.

```ruby # # By default, if your application has the activemodel as a dependency, # any kind of use case can use it to validate their attributes. # class Multiply < Micro::Case attributes :a, :b

validates :a, :b, presence: true, numericality: true

def call! return Failure(:validation_error) { self.errors } unless valid?

Success(number: a * b)   end end

# But if do you want an automatic way to fail

# your use cases on validation errors, you can use:

In some file. e.g: A Rails initializer

require ‘u-case/with_validation’ # or require ‘micro/case/with_validation’

In the Gemfile

gem ‘u-case’, require: ‘u-case/with_validation’

Using this approach, you can rewrite the previous example with less code. e.g:

class Multiply < Micro::Case attributes :a, :b

validates :a, :b, presence: true, numericality: true

def call! Success(number: a * b) end end

Note:

# —- # After requiring the validation mode, the # Micro::Case::Strict and Micro::Case::Safe classes will inherit this new behavior. ```

If I enabled the auto validation, is it possible to disable it only in specific use case classes?

Answer: Yes, it is. To do this, you only need to use the disable_auto_validation macro. e.g:

```ruby require ‘u-case/with_validation’

class Multiply < Micro::Case disable_auto_validation

attribute :a attribute :b validates :a, :b, presence: true, numericality: true

def call! Success(number: a * b) end end

Multiply.call(a: 2, b: ‘a’)

The output will be the following exception:

# TypeError (String can’t be coerced into Integer) ```

⬆️ Back to Top

Benchmarks

Micro::Case

Best overall

The table below contains the average between the Success results and Failure results benchmarks.

Gem / Abstraction Iterations per second Comparison
Micro::Case 116629.7 The Faster
Dry::Monads 101796.3 1.14x slower
Interactor 21230.5 5.49x slower
Trailblazer::Operation 16466.6 7.08x slower
Dry::Transaction 5069.5 23.00x slower

Success results

Gem / Abstraction Iterations per second Comparison
Dry::Monads 139352.5 The Faster
Micro::Case 124749.4 1.12x slower
Interactor 28974.4 4.81x slower
Trailblazer::Operation 17275.6 8.07x slower
Dry::Transaction 5571.7 25.01x slower
Show the full benchmark/ips results. ```ruby # Warming up -------------------------------------- # Interactor 2.865k i/100ms # Trailblazer::Operation # 1.686k i/100ms # Dry::Monads 13.389k i/100ms # Dry::Transaction 551.000 i/100ms # Micro::Case 11.984k i/100ms # Micro::Case::Strict 9.102k i/100ms # Micro::Case::Safe 11.747k i/100ms # Calculating ------------------------------------- # Interactor 28.974k (± 2.7%) i/s - 146.115k in 5.046703s # Trailblazer::Operation # 17.276k (± 1.8%) i/s - 87.672k in 5.076609s # Dry::Monads 139.353k (± 2.5%) i/s - 709.617k in 5.095599s # Dry::Transaction 5.572k (± 3.6%) i/s - 28.101k in 5.050376s # Micro::Case 124.749k (± 1.9%) i/s - 635.152k in 5.093310s # Micro::Case::Strict 93.417k (± 4.8%) i/s - 473.304k in 5.081341s # Micro::Case::Safe 120.607k (± 3.2%) i/s - 610.844k in 5.070394s # Comparison: # Dry::Monads: 139352.5 i/s # Micro::Case: 124749.4 i/s - 1.12x slower # Micro::Case::Safe: 120607.3 i/s - 1.16x slower # Micro::Case::Strict: 93417.3 i/s - 1.49x slower # Interactor: 28974.4 i/s - 4.81x slower # Trailblazer::Operation: 17275.6 i/s - 8.07x slower # Dry::Transaction: 5571.7 i/s - 25.01x slower ```

https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success_result.rb

Failure results

Gem / Abstraction Iterations per second Comparison
Micro::Case 108510.0 The Faster
Dry::Monads 64240.1 1.69x slower
Trailblazer::Operation 15657.7 6.93x slower
Interactor 13486.7 8.05x slower
Dry::Transaction 4567.3 23.76x slower
Show the full benchmark/ips results. ```ruby # Warming up -------------------------------------- # Interactor 1.331k i/100ms # Trailblazer::Operation # 1.544k i/100ms # Dry::Monads 6.343k i/100ms # Dry::Transaction 456.000 i/100ms # Micro::Case 10.429k i/100ms # Micro::Case::Strict 8.109k i/100ms # Micro::Case::Safe 10.280k i/100ms # Calculating ------------------------------------- # Interactor 13.487k (± 1.9%) i/s - 67.881k in 5.035059s # Trailblazer::Operation # 15.658k (± 1.6%) i/s - 78.744k in 5.030427s # Dry::Monads 64.240k (± 1.8%) i/s - 323.493k in 5.037461s # Dry::Transaction 4.567k (± 1.3%) i/s - 23.256k in 5.092699s # Micro::Case 108.510k (± 2.3%) i/s - 542.308k in 5.000605s # Micro::Case::Strict 83.527k (± 1.4%) i/s - 421.668k in 5.049245s # Micro::Case::Safe 105.641k (± 3.7%) i/s - 534.560k in 5.067836s # Comparison: # Micro::Case: 108510.0 i/s # Micro::Case::Safe: 105640.6 i/s - same-ish: difference falls within error # Micro::Case::Strict: 83526.8 i/s - 1.30x slower # Dry::Monads: 64240.1 i/s - 1.69x slower # Trailblazer::Operation: 15657.7 i/s - 6.93x slower # Interactor: 13486.7 i/s - 8.05x slower # Dry::Transaction: 4567.3 i/s - 23.76x slower ```

https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure_result.rb


Micro::Case::Flow

Gems / Abstraction Success results Failure results
Micro::Case::Flow The Faster The Faster
Micro::Case::Safe::Flow 0x slower 0x slower
Interactor::Organizer 1.47x slower 5.51x slower

* The Dry::Monads, Dry::Transaction, Trailblazer::Operation are out of this analysis because all of them doesn’t have this kind of feature.

Success results - Show the full benchmark/ips results. ```ruby # Warming up -------------------------------------- # Interactor::Organizer 4.880k i/100ms # Micro::Case::Flow 7.035k i/100ms # Micro::Case::Safe::Flow 7.059k i/100ms # Calculating ------------------------------------- # Interactor::Organizer 50.208k (± 1.3%) i/s - 253.760k in 5.055099s # Micro::Case::Flow 73.791k (± 0.9%) i/s - 372.855k in 5.053311s # Micro::Case::Safe::Flow 73.314k (± 1.1%) i/s - 367.068k in 5.007473s # Comparison: # Micro::Case::Flow: 73790.7 i/s # Micro::Case::Safe::Flow: 73313.7 i/s - same-ish: difference falls within error # Interactor::Organizer: 50207.7 i/s - 1.47x slower ```
Failure results - Show the full benchmark/ips results. ```ruby # Warming up -------------------------------------- # Interactor::Organizer 2.372k i/100ms # Micro::Case::Flow 12.802k i/100ms # Micro::Case::Safe::Flow 12.673k i/100ms # Calculating ------------------------------------- # Interactor::Organizer 24.522k (± 2.0%) i/s - 123.344k in 5.032159s # Micro::Case::Flow 135.122k (± 1.7%) i/s - 678.506k in 5.022903s # Micro::Case::Safe::Flow 133.980k (± 1.4%) i/s - 671.669k in 5.014181s # Comparison: # Micro::Case::Flow: 135122.0 i/s # Micro::Case::Safe::Flow: 133979.8 i/s - same-ish: difference falls within error # Interactor::Organizer: 24521.8 i/s - 5.51x slower ```

https://github.com/serradura/u-case/tree/master/benchmarks/flow

Comparisons

Check it out implementations of the same use case with different gems/abstractions.

⬆️ Back to Top

Examples

  1. Rescuing an exception inside of use cases
  2. Users creation

    An example of flow in how to define steps to sanitize, validate, and persist some input data.

  3. CLI calculator

    A more complex example which use rake tasks to demonstrate how to handle user data, and how to use different failures type to control the program flow.

⬆️ Back to Top

Development

After checking out the repo, run bin/setup to install dependencies. Then, run ./test.sh to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/serradura/u-case. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Micro::Case project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.