Project

yabi

0.0
No release in over 3 years
A small dry-rb-based base service/interactor with optional contract validation.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 3.12
~> 1.66

Runtime

>= 1.6, < 2
 Project Readme

YABI — Yet Another Base Interactor

A tiny base class for service objects/interactors built on dry-monads, dry-initializer, and dry-validation, plus a minimal base contract.

Installation

Add to your Gemfile:

gem 'yabi', git: 'https://example.com/yabi.git'

Require the gem (Rails autoloading works too):

require 'yabi'

By default the gem exposes Yabi::BaseInteractor plus shims BaseInteractor and BaseService (only defined if missing) to ease migration of existing code.

Usage

class Users::Questions::Create < BaseInteractor
  option :question_text

  class ValidationContract < BaseContract
    params { required(:question_text).filled(:string) }
  end

  def call
    Success(question_text.upcase)
  end
end

Users::Questions::Create.call(question_text: 'hello')
# => Success(\"HELLO\")

Contracts

Pass a contract via the contract: keyword when calling, or define an inner ValidationContract constant. Validation runs before call; failures return Failure(errors).

YABI ships with Yabi::BaseContract (also available as BaseContract) which is a light wrapper around Dry::Validation::Contract. It uses the :i18n messages backend by default and loads the built‑in dry-validation translations. Customize it in your app if you want different load paths or locales:

class ApplicationContract < Yabi::BaseContract
  config.messages.default_locale = :es
  config.messages.load_paths << Rails.root.join('config/locales/es.yml')
end

Error messages & I18n

The gem ships an English locale file at config/locales/en.yml and loads it automatically. Errors raised inside YABI (e.g., for unexpected positional arguments) are translated via I18n.t('yabi.errors.*'). Override translations by adding your own locale files earlier in I18n.load_path or by setting I18n.locale.

Helpers

  • safe_call { ... } wraps a block into Try and returns a Result.
  • in_transaction { ... } delegates to ActiveRecord::Base.transaction when ActiveRecord is available.

Attribute capture

Yabi::BaseInteractor uses dry_initializer.attributes(self) to collect declared options/params for validation instead of scraping every instance variable. This avoids leaking internal state while keeping compatibility with dry-initializer defaults. A fallback to the previous instance-variable scan remains for non-dry objects.

Example: HTTP request interactor (not included in the gem)

The gem no longer ships an HTTP interactor to avoid adding Faraday as a runtime dependency. If you want one, you can copy/paste or adapt the example below. Remember to add Faraday (or your preferred adapter) to your own Gemfile.

require 'faraday'
require 'uri'

class Integrations::Http::Requests::Make < BaseInteractor
  option :http_method
  option :url
  option :request_params, default: -> { {} }
  option :request_headers, default: -> { {} }
  option :options, default: -> { {} }

  class ValidationContract < BaseContract
    params do
      required(:http_method).filled(:string)
      required(:url).filled(:string)
      optional(:request_params)
      optional(:request_headers)
      optional(:options)
    end

    rule(:http_method) do
      key.failure('is not a supported HTTP method') unless %w[get post put patch delete].include?(value.to_s.downcase)
    end

    rule(:url) do
      key.failure('is not a valid URL') unless value.to_s.match?(URI::DEFAULT_PARSER.make_regexp)
    end
  end

  def call
    response = yield safe_call { faraday_client.public_send(http_method.to_sym, '', prepared_request_params) }
    Success(response)
  end

  private

  def faraday_client
    Faraday.new(url:, headers: request_headers, **options) do |faraday|
      faraday.request :url_encoded
      faraday.adapter Faraday.default_adapter
    end
  end

  def prepared_request_params
    request_params.respond_to?(:to_h) ? request_params.to_h : request_params
  end
end

Integrations::Http::Requests::Make.call(
  http_method: 'get',
  url: 'https://jsonplaceholder.typicode.com/posts/1'
).either(
  ->(response) { puts \"Success: #{response.status}\" },
  ->(error)    { puts \"Error: #{error}\" }
)

This interactor is only an example; include it in your own app if desired and add Faraday (or another HTTP client) to your dependencies.

Development

bundle install
bundle exec rspec

License

MIT