Class: FService::Base Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/f_service/base.rb

Overview

This class is abstract.

Abstract base class for services. It provides the basic interface to return and handle results.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.call(*args) ⇒ Result::Success, Result::Failure

Note:

this method shouldn't be overridden in the subclasses

Initializes and runs a new service.

Examples:

User::UpdateName.(user: user, new_name: new_name)
# or
User::UpdateName.call(user: user, new_name: new_name)

Returns:

Raises:



23
24
25
26
27
28
# File 'lib/f_service/base.rb', line 23

def call(*args)
  result = new(*args).run
  raise(FService::Error, 'Services must return a Result') unless result.is_a? Result::Base

  result
end

.to_procProc

Allows running a service without explicit giving params. This is useful when chaining services or mapping inputs to be processed.

Examples:

# Assuming all classes here subclass FService::Base:

User::Create
  .and_then(&User::Login)
  .and_then(&SendWelcomeEmail)

# Mapping inputs:

[{ n:1 }, { n: 2 }].map(&DoubleNumber).map(&:value)
# => [2, 4]

Returns:

  • (Proc)


48
49
50
# File 'lib/f_service/base.rb', line 48

def to_proc
  proc { |args| call(**args) }
end

Instance Method Details

#Check(*types, data: nil) ⇒ Result::Success, Result::Failure

Converts a boolean to a Result. Truthy values map to Success, and falsey values map to Failures. You can optionally provide a types for the result. The result value defaults as the evaluated value of the given block. If you want another value you can pass it through the data: argument.

Examples:

class CheckMathWorks < FService::Base
  def run
    Check(:math_works) { 1 < 2 }
    # => #<Success @value=true, @types=[:math_works]>

    Check(:math_works) { 1 > 2 }
    # => #<Failure @error=false, @types=[:math_works]>

    Check(:math_works, data: 1 + 2) { 1 > 2 }
    # => #<Failure @types=:math_works, @error=3>
  end

    Check(:math_works, data: 1 + 2) { 1 < 2 }
    # => #<Success @types=[:math_works], @value=3>
  end
end

Parameters:

  • types

    the Result types

Returns:



161
162
163
164
165
166
167
# File 'lib/f_service/base.rb', line 161

def Check(*types, data: nil)
  res = yield

  final_data = data || res

  res ? Success(*types, data: final_data) : Failure(*types, data: final_data)
end

#Failure(*types, data: nil) ⇒ Result::Failure

Returns a failed result. You can optionally specify types and a value for your result. You'll probably want to return this inside #run.

Examples:

def run
  Failure()
  # => #<Failure @error=nil, @types=[]>

  Failure(:not_a_number)
  # => #<Failure @error=nil, @types=[:not_a_number]>

  Failure(data: "10")
  # => #<Failure @error="10", @types=[]>

  Failure(:not_a_number, data: "10")
  # => #<Failure @error="10", @types=[:not_a_number]>
end

Parameters:

  • types

    the Result types

  • data (defaults to: nil)

    the result value

Returns:



131
132
133
# File 'lib/f_service/base.rb', line 131

def Failure(*types, data: nil)
  Result::Failure.new(data, types)
end

#runResult::Success, Result::Failure

Note:

this method SHOULD be overridden in the subclasses

This method is where the main work of your service must be. It is called after initilizing the service and should return an FService::Result.

Examples:

class User::UpdateName < FService::Base
  def initialize(user:, new_name:)
    @user = user
    @new_name = new_name
  end

  def run
    return Failure(:missing_user) if user.nil?

    if @user.update(name: @new_name)
      Success(:created, data: user)
    else
      Failure(:creation_failed, data: user.errors)
    end
  end
end

Returns:

Raises:

  • (NotImplementedError)


77
78
79
# File 'lib/f_service/base.rb', line 77

def run
  raise NotImplementedError, 'Services must implement #run'
end

#Success(*types, data: nil) ⇒ Result::Success

Returns a successful result. You can optionally specify a list of types and a value for your result. You'll probably want to return this inside #run.

Examples:

def run
  Success()
  # => #<Success @value=nil, @types=[]>

  Success(:ok)
  # => #<Success @value=nil, @types=[:ok]>

  Success(data: 10)
  # => #<Success @value=10, @types=[]>

  Success(:ok, data: 10)
  # => #<Success @value=10, @types=[:ok]>
end

Parameters:

  • types

    the Result types

  • data (defaults to: nil)

    the result value

Returns:



104
105
106
# File 'lib/f_service/base.rb', line 104

def Success(*types, data: nil)
  Result::Success.new(data, types)
end

#Try(*types, catch: StandardError) ⇒ Result::Success, Result::Failure

If the given block raises an exception, it wraps it in a Failure. Otherwise, maps the block value in a Success object. You can specify which exceptions to watch for. It's possible to provide a types for the result too.

Examples:

class IHateEvenNumbers < FService::Base
  def run
    Try(:rand_int) do
      n = rand(1..10)
      raise "Yuck! It's a #{n}" if n.even?

      n
    end
  end
end

IHateEvenNumbers.call
# => #<Success @value=9, @types=[:rand_int]>

IHateEvenNumbers.call
# => #<Failure @error=#<RuntimeError: Yuck! It's a 4>, @types=[:rand_int]>

Parameters:

  • types

    the Result types

  • catch (defaults to: StandardError)

    the exception list to catch

Returns:



195
196
197
198
199
200
201
# File 'lib/f_service/base.rb', line 195

def Try(*types, catch: StandardError)
  res = yield

  Success(*types, data: res)
rescue *catch => e
  Failure(*types, data: e)
end