Files
claude-engineering-plugin/plugins/compound-engineering/skills/andrew-kane-gem-writer/references/rails-integration.md
Kieran Klaassen 6c5b3e40db [2.9.0] Rename plugin to compound-engineering
BREAKING: Plugin renamed from compounding-engineering to compound-engineering.
Users will need to reinstall with the new name:

  claude /plugin install compound-engineering

Changes:
- Renamed plugin directory and all references
- Updated documentation counts (24 agents, 19 commands)
- Added julik-frontend-races-reviewer to docs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 17:32:16 -08:00

3.7 KiB

Rails Integration Patterns

The Golden Rule

Never require Rails gems directly. This causes loading order issues.

# WRONG - causes premature loading
require "active_record"
ActiveRecord::Base.include(MyGem::Model)

# CORRECT - lazy loading
ActiveSupport.on_load(:active_record) do
  extend MyGem::Model
end

ActiveSupport.on_load Hooks

Common hooks and their uses:

# Models
ActiveSupport.on_load(:active_record) do
  extend GemName::Model        # Add class methods (searchkick, has_encrypted)
  include GemName::Callbacks   # Add instance methods
end

# Controllers
ActiveSupport.on_load(:action_controller) do
  include Ahoy::Controller
end

# Jobs
ActiveSupport.on_load(:active_job) do
  include GemName::JobExtensions
end

# Mailers
ActiveSupport.on_load(:action_mailer) do
  include GemName::MailerExtensions
end

Prepend for Behavior Modification

When overriding existing Rails methods:

ActiveSupport.on_load(:active_record) do
  ActiveRecord::Migration.prepend(StrongMigrations::Migration)
  ActiveRecord::Migrator.prepend(StrongMigrations::Migrator)
end

Railtie Pattern

Minimal Railtie for non-mountable gems:

# lib/gemname/railtie.rb
module GemName
  class Railtie < Rails::Railtie
    initializer "gemname.configure" do
      ActiveSupport.on_load(:active_record) do
        extend GemName::Model
      end
    end

    # Optional: Add to controller runtime logging
    initializer "gemname.log_runtime" do
      require_relative "controller_runtime"
      ActiveSupport.on_load(:action_controller) do
        include GemName::ControllerRuntime
      end
    end

    # Optional: Rake tasks
    rake_tasks do
      load "tasks/gemname.rake"
    end
  end
end

Engine Pattern (Mountable Gems)

For gems with web interfaces (PgHero, Blazer, Ahoy):

# lib/pghero/engine.rb
module PgHero
  class Engine < ::Rails::Engine
    isolate_namespace PgHero

    initializer "pghero.assets", group: :all do |app|
      if app.config.respond_to?(:assets) && defined?(Sprockets)
        app.config.assets.precompile << "pghero/application.js"
        app.config.assets.precompile << "pghero/application.css"
      end
    end

    initializer "pghero.config" do
      PgHero.config = Rails.application.config_for(:pghero) rescue {}
    end
  end
end

Routes for Engines

# config/routes.rb (in engine)
PgHero::Engine.routes.draw do
  root to: "home#index"
  resources :databases, only: [:show]
end

Mount in app:

# config/routes.rb (in app)
mount PgHero::Engine, at: "pghero"

YAML Configuration with ERB

For complex gems needing config files:

def self.settings
  @settings ||= begin
    path = Rails.root.join("config", "blazer.yml")
    if path.exist?
      YAML.safe_load(ERB.new(File.read(path)).result, aliases: true)
    else
      {}
    end
  end
end

Generator Pattern

# lib/generators/gemname/install_generator.rb
module GemName
  module Generators
    class InstallGenerator < Rails::Generators::Base
      source_root File.expand_path("templates", __dir__)

      def copy_initializer
        template "initializer.rb", "config/initializers/gemname.rb"
      end

      def copy_migration
        migration_template "migration.rb", "db/migrate/create_gemname_tables.rb"
      end
    end
  end
end

Conditional Feature Detection

# Check for specific Rails versions
if ActiveRecord.version >= Gem::Version.new("7.0")
  # Rails 7+ specific code
end

# Check for optional dependencies
def self.client
  @client ||= if defined?(OpenSearch::Client)
    OpenSearch::Client.new
  elsif defined?(Elasticsearch::Client)
    Elasticsearch::Client.new
  else
    raise Error, "Install elasticsearch or opensearch-ruby"
  end
end