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
Implements a variant-based calculator system with a single entry point, concrete strategies, and a no-op fallback (Null Object).
Core principle: One API for the client: Calculator::Factory.for(entity).calculate. The factory picks the strategy; NullService handles unknown variants safely.
EVERY component (Factory, BaseService, NullService, concrete services) MUST have
its test written and validated BEFORE implementation.
1. Write the spec for the component (contexts per variant)
2. Run the spec — verify it fails because the component does not exist yet
3. ONLY THEN write the component implementation
4. Repeat for each component: Factory → BaseService → NullService → Concrete
See rspec-best-practices for the full gate cycle.| Component | Responsibility |
|---|---|
| Factory | Choose class from entity variant; return instance or NullService |
| BaseService | Common #calculate flow, guards, call to compute_result |
| NullService | Never compute; return nil safely |
| Concrete | Variant condition in should_calculate? and logic in compute_result |
nil or default without raising).SomethingCalculator::Factory.for(entity).calculate.app/services/<calculator_name>/
├── factory.rb
├── base_service.rb
├── null_service.rb
├── standard_service.rb
├── premium_service.rb
└── README.md# frozen_string_literal: true
module EligibilityDateCalculator
class Factory
SERVICE_MAP = {
'standard' => StandardEligibilityService,
'premium' => PremiumEligibilityService
}.freeze
def self.for(animal)
shelter = animal.shelter
return NullService.new(animal) unless shelter&.participates_in_eligibility_program?
program_names = shelter.shelter_programs.pluck(:name)
service_class = SERVICE_MAP.find { |name, _| program_names.include?(name) }&.last || NullService
service_class.new(animal)
end
end
endFactory rules:
NullServiceSERVICE_MAP -> NullServiceSERVICE_MAP)# frozen_string_literal: true
module EligibilityDateCalculator
class BaseService
attr_reader :animal, :shelter
def initialize(animal)
@animal = animal
@shelter = animal.shelter
end
def calculate
return nil unless should_calculate?
intake_date = animal.intake_date
return nil if intake_date.blank?
compute_result(intake_date)
end
private
def should_calculate?
shelter&.participates_in_eligibility_program?
end
def compute_result(_intake_date)
nil
end
end
endSubclasses override should_calculate? and compute_result.
# frozen_string_literal: true
module EligibilityDateCalculator
class NullService < BaseService
private
def should_calculate?
false
end
end
endBaseServiceshould_calculate?: call super and add variant conditioncompute_result: implement the formulaeligibility_date = EligibilityDateCalculator::Factory.for(animal).calculate.for with contexts for each branch (nil shelter, no program, each variant, multiple variants)compute_result returns nil#calculate always nilshould_calculate? true only when variant applies; compute_result returns expected valuesUse FactoryBot for entity setup. Use travel_to for time-dependent calculations.
| Mistake | Reality |
|---|---|
| No NullService — raising on unknown variant | Use NullService for safe no-op. Raising breaks the client. |
| Factory logic scattered across callers | Centralize in Factory.for(entity). One entry point. |
BaseService without should_calculate? guard | Subclasses forget the guard. Put it in the base class. |
| SERVICE_MAP with string keys that don't match DB values | Verify key names match exactly what's stored in the database |
| No tests per variant | Each variant must have its own spec context |
case/when instead of Factory (the whole point is to avoid conditionals)#calculate entirely (should only override should_calculate? and compute_result).freeze)| Skill | When to chain |
|---|---|
| ruby-service-objects | Base conventions (YARD, constants, frozen_string_literal, response style) |
| rspec-service-testing | For testing Factory, BaseService, NullService, and concrete strategies |
| rspec-best-practices | For general RSpec structure |
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