CtrlK
BlogDocsLog inGet started
Tessl Logo

igmarin/rails-agent-skills

Curated library of AI agent skills for Ruby on Rails development. Covers code review, architecture, security, testing (RSpec), engines, service objects, DDD patterns, and workflow automation.

73

Quality

91%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

SKILL.mdruby-api-client-integration/

name:
ruby-api-client-integration
description:
Use when integrating with external APIs in Ruby, creating HTTP clients, or building data pipelines. Covers the layered Auth, Client, Fetcher, Builder, and Domain Entity pattern with token caching, retry logic, and FactoryBot hash factories for test data.

Ruby API Client Integration

Follow ruby-service-objects for shared conventions (YARD via yard-documentation, constants, response format, app/services/ layout). This skill adds the layered Auth -> Client -> Fetcher -> Builder -> Domain Entity pattern for external APIs.

HARD-GATE: Tests Gate Implementation

EVERY layer (Auth, Client, Fetcher, Builder, Entity) MUST have its test
written and validated BEFORE implementation.
  1. Write the spec for the layer (instance_double for unit, hash factories for API responses)
  2. Run the spec — verify it fails because the layer does not exist yet
  3. ONLY THEN write the layer implementation
  4. Repeat for each layer in order: Auth → Client → Fetcher → Builder → Entity
See rspec-best-practices for the full gate cycle.

Quick Reference

LayerResponsibilityFile
AuthOAuth/token management, cachingauth.rb
ClientHTTP requests, response parsing, error wrappingclient.rb
FetcherQuery orchestration, polling, paginationfetcher.rb
BuilderResponse -> structured data transformationbuilder.rb
Domain EntityDomain-specific config, query definitionsentity.rb

Architecture

Auth → Client → Fetcher → Builder → Domain Entity

Layer Details

1. Auth (auth.rb)

Handles authentication. Caches tokens. Provides self.default from env vars.

module ServiceName
  class Auth
    include HTTParty

    DEFAULT_TIMEOUT = 30

    def self.default
      new(
        client_id: Rails.configuration.secrets[:service_client_id],
        client_secret: Rails.configuration.secrets[:service_client_secret],
        account_id: Rails.configuration.secrets[:service_account_id]
      )
    end

    def initialize(client_id:, client_secret:, account_id:, timeout: DEFAULT_TIMEOUT)
      raise 'Missing required credentials' if [client_id, client_secret, account_id].any?(&:blank?)
      @token = nil
    end

    def token
      return @token if @token
      # fetch and cache token
    end
  end
end

2. Client (client.rb)

Wraps HTTP calls. Validates inputs. Parses responses. Raises Client::Error.

module ServiceName
  class Client
    include HTTParty

    MISSING_CONFIGURATION_ERROR = 'Missing required configuration'
    DEFAULT_TIMEOUT = 30
    DEFAULT_RETRIES = 3

    class Error < StandardError; end

    def self.default
      token = Auth.default.token
      host = Rails.configuration.secrets[:service_host]
      new(token:, host:)
    end

    def initialize(token:, host:, timeout: DEFAULT_TIMEOUT, max_retries: DEFAULT_RETRIES)
      raise Error, MISSING_CONFIGURATION_ERROR if [token, host].any?(&:blank?)
    end

    def execute_query(payload)
      # POST, parse JSON, raise on failure
    rescue JSON::ParserError, HTTParty::Error => e
      raise Error, "Request failed: #{e.message}"
    end
  end
end

3. Fetcher (fetcher.rb)

Orchestrates query execution. Handles polling and pagination.

module ServiceName
  class Fetcher
    MAX_RETRIES = 3
    RETRY_DELAY_IN_SECONDS = 2

    def initialize(client, data_builder:, default_query:)
      @client = client
      @data_builder = data_builder
      @default_query = default_query
    end

    def execute_query(query = @default_query)
      raw_response = @client.execute_query(query)
      @data_builder.build(complete_response)
    end
    alias query execute_query
  end
end

Key patterns: constructor DI, delegates HTTP to Client, delegates transformation to Builder, retries with exponential backoff.

4. Builder (builder.rb)

Transforms raw API response into attribute-filtered hashes.

module ServiceName
  class Builder
    def initialize(attributes:)
      @attributes = attributes
    end

    def build(response)
      schema = response['manifest']['schema']['columns']
      data_array = response['result']['data_array'] || []
      data_array.map { |row| build_hash(schema, row).slice(*@attributes) }
    end
  end
end

5. Domain Entity (e.g., animal.rb)

Defines domain-specific constants and wires up the layers.

module ServiceName
  class Animal
    ATTRIBUTES = %w[tag_number name species_id shelter_id].freeze
    DEFAULT_QUERY = 'SELECT * FROM schema.animals;'
    SEARCH_QUERY = 'SELECT * FROM schema.animals WHERE tag_number = ?;'

    def self.fetcher(client: Client.default)
      data_builder = Builder.new(attributes: ATTRIBUTES)
      Fetcher.new(client, data_builder:, default_query: DEFAULT_QUERY)
    end

    def self.find(tag_number:)
      query = ActiveRecord::Base.sanitize_sql([SEARCH_QUERY, tag_number])
      fetcher.execute_query(query)
    end
  end
end

Adding a New Domain Entity

  1. Define ATTRIBUTES, DEFAULT_QUERY, and optionally SEARCH_QUERY constants
  2. Implement .fetcher class method wiring Builder and Fetcher
  3. Add .find or .search class methods with sanitize_sql
  4. Create a FactoryBot hash factory in spec/factories/module_name/
  5. Write spec in spec/services/module_name/ covering .fetcher, .find/.search

Checklist for New API Integration

  • Create module directory under app/services/
  • Implement Auth with self.default and token caching
  • Implement Client with self.default, Error class, and error wrapping
  • Implement Fetcher with polling/pagination if needed
  • Implement Builder with attribute filtering
  • Create domain entities with constants and .fetcher
  • Add README.md with usage examples and error handling docs
  • Write comprehensive specs for all layers
  • Create FactoryBot hash factories for API responses

Common Mistakes

MistakeReality
Skipping the Auth layerToken management scattered across services. Centralize in Auth.
Client without Error classCallers can't distinguish API errors from other exceptions
No retry logic in FetcherTransient failures kill the pipeline. Add exponential backoff.
Builder that returns all fieldsWhitelist with ATTRIBUTES. Don't leak internal API structure.
Hardcoded credentialsUse self.default from encrypted credentials, never hardcode
No FactoryBot hash factoriesTests become brittle fixtures. Use factories for API responses.

Red Flags

  • Credentials or tokens hardcoded or committed to git
  • HTTP calls without timeout configuration
  • No error wrapping — raw HTTParty exceptions bubble up
  • Builder that doesn't filter attributes (leaks full API response)
  • Missing specs for error scenarios (network failure, invalid JSON, 4xx/5xx)
  • Fetcher without pagination support when API returns paginated results

Integration

SkillWhen to chain
yard-documentationWhen writing or reviewing inline docs for API client layers
ruby-service-objectsBase conventions (.call, responses, transactions, README)
rspec-service-testingFor testing all layers with instance_double and hash factories
rspec-best-practicesFor general RSpec structure
rails-security-reviewWhen auditing credential handling and input validation

ruby-api-client-integration

README.md

tile.json