CtrlK
BlogDocsLog inGet started
Tessl Logo

igmarin/rails-agent-skills

Curated library of 42 public AI agent skills for Ruby on Rails development, plus 5 callable workflow skills. Organized by category: planning, testing, code-quality, ddd, engines, infrastructure, api, patterns, context, orchestration, and workflows. Covers code review, architecture, security, testing (RSpec), engines, service objects, DDD patterns, and TDD automation.

97

Quality

97%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

SKILL.mdskills/patterns/strategy-factory-null-calculator/

name:
strategy-factory-null-calculator
license:
MIT
description:
Use when building variant-based calculators with a single entry point that picks the right implementation (Strategy + Factory), or when adding a no-op fallback (Null Object). Generates variant-based calculator classes, implements SERVICE_MAP routing, and scaffolds RSpec tests per variant. Trigger words: design pattern, Ruby, dispatch table, polymorphism, no-op default, variant calculator, strategy pattern, factory pattern, null object pattern.

Strategy + Factory + Null Object Calculator Pattern

One API for the client: Calculator::Factory.for(entity).calculate. The factory picks the strategy; NullService handles unknown variants safely.

HARD-GATE: Tests Gate Implementation

For each component (Factory → BaseService → NullService → Concrete):

  1. Write the spec — contexts per variant, plus the NullService path
  2. Run it — verify it fails because the component does not exist yet
  3. Implement the component — minimum code to make the spec pass
  4. Run again — confirm green, then proceed to the next component

Quick Reference

ComponentResponsibility
FactoryDispatch to correct service class via SERVICE_MAP; fall back to NullService
BaseServiceGuard with should_calculate?; delegate to compute_result
NullServiceAlways returns nil safely — never raises
ConcreteOverride should_calculate? (add variant check on top of super) and compute_result

File Structure

app/services/<calculator_name>/
├── factory.rb
├── base_service.rb
├── null_service.rb
├── standard_service.rb
├── premium_service.rb

1. Factory

# frozen_string_literal: true

module PricingCalculator
  class Factory
    SERVICE_MAP = {
      'standard' => StandardPricingService,
      'premium'  => PremiumPricingService
    }.freeze

    def self.for(order)
      plan = order.plan
      return NullService.new(order) unless plan&.active?

      service_class = SERVICE_MAP[plan.name] || NullService
      service_class.new(order)
    end
  end
end

No qualifying context or unknown variant → NullService.

2. BaseService

# frozen_string_literal: true

module PricingCalculator
  class BaseService
    def initialize(order)
      @order = order
    end

    def calculate
      return nil unless should_calculate?

      compute_result
    end

    private

    def should_calculate?
      @order.present?
    end

    def compute_result
      raise NotImplementedError, "#{self.class}#compute_result must be implemented"
    end
  end
end

3. NullService

# frozen_string_literal: true

module PricingCalculator
  class NullService < BaseService
    private

    def should_calculate?
      false
    end

    def compute_result
      nil
    end
  end
end

4. Concrete Service Example

# frozen_string_literal: true

module PricingCalculator
  class StandardPricingService < BaseService
    private

    def should_calculate?
      super && @order.plan.name == 'standard'
    end

    def compute_result
      @order.base_price * 1.0
    end
  end
end

Always call super in should_calculate? to preserve the base guard.

5. Usage

price = PricingCalculator::Factory.for(order).calculate

Single entry point rule: Factory.for(entity) is the only permitted access path. Clients never instantiate service classes directly. If you see StandardPricingService.new(order) outside of Factory, that is a bug — route through the factory.

6. Tests (RSpec)

See assets/examples.md for complete, copy-paste-ready RSpec examples for the Factory, NullService, and each concrete service.

Each spec suite must cover: inactive plan, nil plan, each named variant, and unknown variant. Mirror the same context structure across all concrete services.

Pitfalls

PitfallFix
SERVICE_MAP key mismatchVerify keys match exactly what is stored in the database — typos cause silent NullService fallbacks
Missing NullService specAlways add a spec context for unknown/nil variants or tests will never catch the fallback regression
Direct service instantiation (ServiceClass.new(entity))Route through Factory.for(entity) — it is the sole public entry point; direct instantiation bypasses the NullService safety net
Forgetting super in concrete should_calculate?Always call super — skipping it removes the base nil/presence guard

Integration

SkillWhen to chain
rspec-service-testingFor complete Factory, BaseService, NullService, and concrete strategy specs
ruby-service-objectsFor naming conventions, YARD docs, and frozen_string_literal baseline

Assets

skills

README.md

tile.json