TL;DR

I have no time to read tons of docs. Just show me an example use case and I'll quickly craft something similar 😎.

— A lazy and productive developer.

A complete working example of a Convenient Service usage - from setup to executing a service.

Using .result - handle all three statuses explicitly:

# Any source code in the project.
def read_file_content(path)
  result = ReadFileContent.result(path: path)

  if result.success?
    ##
    # Service tried to read the file content and completed it.
    #
    result.data[:content] # File content.
  elsif result.failure?
    ##
    # Service tried to read file content, but did NOT complete it due to some expected reason.
    #
    App.logger.warn { result.message.to_s }

    "" # Fallback value or any other reasonable fallback behavior.
  else # result.error?
    ##
    # Service NOT even tried to complete reading of file content due to a validation issue or exception.
    #
    App.logger.error { result.message.to_s }

    raise VerboseException, result.message.to_s # Self-explanatory exception or any other reasonable exception handling behavior.
  end
end

Using .call - returns data.to_h on success, nil on failure, raises on error:

# Any source code in the project.
def read_file_content(path)
  ReadFileContent.call(path: path)&.fetch(:content) || ""
end

Installation.

gem "convenient_service", "1.0.0"

Setup.

require "convenient_service"

Config.

module ApplicationService
  module Config
    include ConvenientService::Config

    included do
      include ConvenientService::Standard::Config
    end
  end
end

Regular service example.

class AssertFileExists
  include ApplicationService::Config

  attr_reader :path

  def initialize(path:)
    @path = path
  end

  def result
    return error("Path is `nil`") if path.nil?
    return error("Path is empty") if path.empty?

    return failure("File does not exist at path `#{path}`") unless File.exist?(path)

    success
  end
end

One more regular service example.

class AssertFileNotEmpty
  include ApplicationService::Config

  attr_reader :path

  def initialize(path:)
    @path = path
  end

  def result
    return error("Path is `nil`") if path.nil?
    return error("Path is empty") if path.empty?

    return failure("File is empty at path `#{path}`") if File.zero?(path)

    success
  end
end

Organizer service example (has steps).

class ReadFileContent
  include ApplicationService::Config

  attr_reader :path

  step :validate_path
  step AssertFileExists, in: :path
  step AssertFileNotEmpty, in: :path
  step :result, in: :path, out: :content

  def initialize(path:)
    @path = path
  end

  def result
    success(content: File.read(path))
  end

  private

  def validate_path
    return error("Path is `nil`") if path.nil?
    return error("Path is empty") if path.empty?

    success
  end
end

result = ReadFileContent.result(path: "Gemfile")

See more examples with specs in the Convenient Service repo.