serradura / u-case

Represent use cases in a simple and powerful way while writing modular, expressive and sequentially logical code.
https://rubygems.org/gems/u-case
MIT License
527 stars 33 forks source link

DRAFT: Plans for the next major version (v5) #131

Open serradura opened 2 years ago

serradura commented 2 years ago

Table of contents:

[Change] Drop support for older Ruby versions

Ruby version: >= 2.5.0

[Change] Drop support for older Rails versions

Rails version: >= 5.2.0

[Change] Allow only two ways to declare flows

Until v4, there were different ways to declare flows, but in the v5, there will be only two:

Micro::Case

class SumPositiveNumbers < Micro::Case
  def call!
    call(FilterPositiveNumbers)
      .then(SumAllNumbers)
  end
end

# or

FilterPositiveNumbers
  .call(numbers: numbers)
  .then(SumAllNumbers)
Removed Alternative
```ruby SumPositiveNumbers = Micro::Cases.flow([ FilterPositiveNumbers, SumAllNumbers ]) ``` ```ruby class SumPositiveNumbers < Micro::Case def call! call(FilterPositiveNumbers) .then(SumAllNumbers) end end # or FilterPositiveNumbers .call(numbers: numbers) .then(SumAllNumbers) ```
Removed Alternative
```ruby class SumPositiveNumbers < Micro::Case flow([ FilterPositiveNumbers, SumAllNumbers ]) end ``` ```ruby class SumPositiveNumbers < Micro::Case def call! call(FilterPositiveNumbers) .then(SumAllNumbers) end end # or FilterPositiveNumbers .call(numbers: numbers) .then(SumAllNumbers) ```
Removed Alternative
```ruby class SumPositiveNumbers < Micro::Case flow([ FilterPositiveNumbers, self.call! ]) attributes :numbers def call! Success result: { number: numbers.sum } end end ``` ```ruby class SumPositiveNumbers < Micro::Case def call! call(FilterPositiveNumbers) .then(:sum_all_numbers) end private def sum_all_numbers(numbers:, **) Success result: { number: numbers.sum } end end ```

Micro::Case::Safe

class SumPositiveNumbers < Micro::Case::Safe
  def call!
    call(FilterPositiveNumbers)
      .then(SumAllNumbers)
  end
end

# or

FilterPositiveNumbers
  .call(numbers: numbers)
  .then(SumAllNumbers)
Removed Alternative
```ruby SumPositiveNumbers = Micro::Cases.safe_flow([ FilterPositiveNumbers, SumAllNumbers ]) ``` ```ruby class SumPositiveNumbers < Micro::Case::Safe def call! call(FilterPositiveNumbers) .then(SumAllNumbers) end end # or FilterPositiveNumbers .call(numbers: numbers) .then(SumAllNumbers) ```
Removed Alternative
```ruby class SumPositiveNumbers < Micro::Case::Safe flow([ FilterPositiveNumbers, SumAllNumbers ]) end ``` ```ruby class SumPositiveNumbers < Micro::Case::Safe def call! call(FilterPositiveNumbers) .then(SumAllNumbers) end end # or FilterPositiveNumbers .call(numbers: numbers) .then(SumAllNumbers) ```
Removed Alternative
```ruby class SumPositiveNumbers < Micro::Case::Safe flow([ FilterPositiveNumbers, self.call! ]) attributes :numbers def call! Success result: { number: numbers.sum } end end ``` ```ruby class SumPositiveNumbers < Micro::Case::Safe def call! call(FilterPositiveNumbers) .then(:sum_all_numbers) end private def sum_all_numbers(numbers:, **) Success result: { number: numbers.sum } end end ```

[REMOVED] Micro::Case::Strict

Use the required: true option in all of the attributes.

Removed Alternative
```ruby class Double < Micro::Case::Strict attribute :numbers def call! doubled = numbers.map { _1 * 2 } Success result: { numbers: doubled } end end Double.call({}) # The output will be: # ArgumentError (missing keyword: :numbers) ``` ```ruby class Double < Micro::Case attribute :numbers, required: true def call! doubled = numbers.map { _1 * 2 } Success result: { numbers: doubled } end end Double.call({}) # The output will be: # ArgumentError (missing keyword: :numbers) ```

[NEW] Allow instantiation with dependencies

class Divide < Micro::Case
  dependency :logger, kind: { respond_to: :error }

  attribute :a, kind: Numeric
  attribute :b, kind: Numeric

  def call!
    number = a / b

    Success result: { number: number }
  rescue ZeroDivisionError => exception
    logger.error(exception.message)

    Failure(:zero_division)
  end
end

divide = Divide.new(logger: Logger.new(STDOUT))

divide.call(a: 2, b: 0)

############################################
# Dependencies will be required by default #
############################################

Divide.new
# The output will be:
# ArgumentError (missing keyword: :logger)

# The definition of an attribute default will avoid this error.

[NEW] This change will allow the mock of internal steps:

module User::Register
  class Flow < Micro::Case
    def call!
      call(Step::ValidateAttributes)
        .then(Step::CreateRecord)
    end
  end
end

# Rspec

result = Micro::Case::Result::Success.new(data: {user: User.new})

register_user = User::Register::Flow.new

expect(register_user)
  .to receive(:then).with(Step::CreateRecord)
  .and_return(result)

Mocks will also work with the u-case call method.

result = Micro::Case::Result::Failure.new(type: :invalid_attributes)

register_user = User::Register::Flow.new

expect(register_user)
  .to receive(call).with(Step::ValidateAttributes)
  .and_return(result)

[NEW] Add support for pattern matching (Ruby >= 2.7) in the Micro::Case::Result

case result
in {success: _, data: {number: number}}
  # ...
in {failure: :invalid_attributes}
  # ...
end

[DEPRECATED] Use the method method and it's alias apply in the step's declaration.

Deprecated Alternative
```ruby class SumPositiveNumbers < Micro::Case attribute :numbers def call! filter_positive_numbers .then(method(:sum_all_numbers)) end private def filter_positive_numbers # .. end def sum_all_numbers(numbers: **) # ... end end ``` ```ruby class SumPositiveNumbers < Micro::Case attribute :numbers def call! filter_positive_numbers .then(:sum_all_numbers) end private def filter_positive_numbers # .. end def sum_all_numbers(numbers: **) # ... end end ```
Deprecated Alternative
```ruby class SumPositiveNumbers < Micro::Case attribute :numbers def call! filter_positive_numbers .then(apply(:sum_all_numbers)) end private def filter_positive_numbers # .. end def sum_all_numbers(numbers: **) # ... end end ``` ```ruby class SumPositiveNumbers < Micro::Case attribute :numbers def call! filter_positive_numbers .then(:sum_all_numbers) end private def filter_positive_numbers # .. end def sum_all_numbers(numbers: **) # ... end end ```