serradura/u-case

DRAFT: Plans for the next major version (v5)

serradura opened this issue · 0 comments

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
SumPositiveNumbers = Micro::Cases.flow([
  FilterPositiveNumbers,
  SumAllNumbers
])
class SumPositiveNumbers < Micro::Case
  def call!
    call(FilterPositiveNumbers)
      .then(SumAllNumbers)
  end
end

# or

FilterPositiveNumbers
  .call(numbers: numbers)
  .then(SumAllNumbers)
Removed Alternative
class SumPositiveNumbers < Micro::Case
  flow([
    FilterPositiveNumbers,
    SumAllNumbers
  ])
end
class SumPositiveNumbers < Micro::Case
  def call!
    call(FilterPositiveNumbers)
      .then(SumAllNumbers)
  end
end

# or

FilterPositiveNumbers
  .call(numbers: numbers)
  .then(SumAllNumbers)
Removed Alternative
class SumPositiveNumbers < Micro::Case
  flow([
    FilterPositiveNumbers,
    self.call!
  ])

  attributes :numbers

  def call!
    Success result: { number: numbers.sum }
  end
end
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
SumPositiveNumbers = Micro::Cases.safe_flow([
  FilterPositiveNumbers,
  SumAllNumbers
])
class SumPositiveNumbers < Micro::Case::Safe
  def call!
    call(FilterPositiveNumbers)
      .then(SumAllNumbers)
  end
end

# or

FilterPositiveNumbers
  .call(numbers: numbers)
  .then(SumAllNumbers)
Removed Alternative
class SumPositiveNumbers < Micro::Case::Safe
  flow([
    FilterPositiveNumbers,
    SumAllNumbers
  ])
end
class SumPositiveNumbers < Micro::Case::Safe
  def call!
    call(FilterPositiveNumbers)
      .then(SumAllNumbers)
  end
end

# or

FilterPositiveNumbers
  .call(numbers: numbers)
  .then(SumAllNumbers)
Removed Alternative
class SumPositiveNumbers < Micro::Case::Safe
  flow([
    FilterPositiveNumbers,
    self.call!
  ])

  attributes :numbers

  def call!
    Success result: { number: numbers.sum }
  end
end
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
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)
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
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
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
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
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