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
91%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
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.
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.| Layer | Responsibility | File |
|---|---|---|
| Auth | OAuth/token management, caching | auth.rb |
| Client | HTTP requests, response parsing, error wrapping | client.rb |
| Fetcher | Query orchestration, polling, pagination | fetcher.rb |
| Builder | Response -> structured data transformation | builder.rb |
| Domain Entity | Domain-specific config, query definitions | entity.rb |
Auth → Client → Fetcher → Builder → Domain Entityauth.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
endclient.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
endfetcher.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
endKey patterns: constructor DI, delegates HTTP to Client, delegates transformation to Builder, retries with exponential backoff.
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
endanimal.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
endATTRIBUTES, DEFAULT_QUERY, and optionally SEARCH_QUERY constants.fetcher class method wiring Builder and Fetcher.find or .search class methods with sanitize_sqlspec/factories/module_name/spec/services/module_name/ covering .fetcher, .find/.searchapp/services/Auth with self.default and token cachingClient with self.default, Error class, and error wrappingFetcher with polling/pagination if neededBuilder with attribute filtering.fetcherREADME.md with usage examples and error handling docs| Mistake | Reality |
|---|---|
| Skipping the Auth layer | Token management scattered across services. Centralize in Auth. |
Client without Error class | Callers can't distinguish API errors from other exceptions |
| No retry logic in Fetcher | Transient failures kill the pipeline. Add exponential backoff. |
| Builder that returns all fields | Whitelist with ATTRIBUTES. Don't leak internal API structure. |
| Hardcoded credentials | Use self.default from encrypted credentials, never hardcode |
| No FactoryBot hash factories | Tests become brittle fixtures. Use factories for API responses. |
| Skill | When to chain |
|---|---|
| yard-documentation | When writing or reviewing inline docs for API client layers |
| ruby-service-objects | Base conventions (.call, responses, transactions, README) |
| rspec-service-testing | For testing all layers with instance_double and hash factories |
| rspec-best-practices | For general RSpec structure |
| rails-security-review | When auditing credential handling and input validation |
api-rest-collection
create-prd
ddd-boundaries-review
ddd-rails-modeling
ddd-ubiquitous-language
generate-tasks
rails-agent-skills
rails-architecture-review
rails-background-jobs
rails-bug-triage
rails-code-conventions
rails-code-review
rails-engine-compatibility
rails-engine-docs
rails-engine-extraction
rails-engine-installers
rails-engine-release
rails-engine-reviewer
rails-engine-testing
rails-graphql-best-practices
rails-migration-safety
rails-review-response
rails-security-review
rails-stack-conventions
rails-tdd-slices
refactor-safely
rspec-best-practices
rspec-service-testing
ruby-api-client-integration
ruby-service-objects
strategy-factory-null-calculator
ticket-planning
yard-documentation