Insights | NextLink Labs

Setting Up Your Ruby on Rails Monolith for AI Development

Written by Colin Soleim | Feb 19, 2026 10:58:23 PM

Introduction

The AI revolution has arrived, and it's transforming how developers work with codebases. For teams maintaining large Rails monoliths with millions of lines of code, the promise of AI-assisted development is especially appealing. In our experience, these massive codebases can be nearly impossible for AI tools to understand and work with effectively without spending millions of tokens per session.

As software consultants who regularly work with enterprise Rails applications, we've spent the last year experimenting with ways to make large Rails monoliths more accessible to AI development tools like Claude Code, Cursor, and Windsurf. Through trial and error, we've discovered that preparing your codebase for AI requires deliberate architectural choices and workflow adjustments.

Here's our roadmap for successfully setting up your Rails monolith to take full advantage of AI-assisted development.

 

1. Create Contextual README Files for Major Namespaces

The Problem with Monolithic Documentation

In a massive Rails monolith with millions of lines of code, a single top-level README is essentially useless for AI tools. When an AI assistant tries to understand your billing system, it doesn't need to know about your authentication logic, email processing, or any of the other dozen subsystems in your application. Context windows are limited, and every irrelevant piece of information reduces the AI's ability to provide useful suggestions.

Namespace-Level Context Files

The solution is to create focused documentation files in each major namespace of your application. We recommend creating .context.md or README.md files in key directories throughout your codebase:


```
app/
 services/
   billing/
     .context.md          # Explains billing service architecture
   notifications/
     .context.md          # Explains notification system
   analytics/
     .context.md          # Explains analytics pipeline
 models/
   concerns/
     auditable/
       .context.md        # Explains auditing concern usage
```

These context files should be concise (200-500 lines maximum) and include:

  1. Purpose: What this namespace is responsible for
  2. Key Classes: The 5-10 most important classes and what they do
  3. Data Flow: How data moves through this subsystem
  4. Dependencies: What external services or other namespaces this code depends on
  5. Common Patterns: Any namespace-specific patterns or conventions
  6. Gotchas: Known issues or surprising behavior

By keeping these files focused and co-located with the code they describe, AI tools can quickly load the relevant context when working in a specific area of your codebase. We've found this dramatically reduces token usage and cost while also improving AI suggestion quality by 3-4x compared to relying on a single README.

 

2. Implement Guard with Fast, Focused Test Suites

The Iteration Speed Problem

AI tools are most effective when developers can rapidly test their suggestions. If your test suite takes 15 minutes to run, you'll find yourself accepting AI-generated code without properly verifying it works or without taking full advantage of how fast code generation can be.

The standard advice is to write faster tests, but with a monolith containing millions of lines, even a well-optimized test suite might take 30+ minutes for a full run. Instead, we need a way to run only relevant tests instantly.

Using Guard for Instant Feedback

Guard is a gem that watches your file system and automatically runs tests when files change. Combined with intelligent test mapping, it creates an instant feedback loop that's perfect for AI-assisted development.

Here's our recommended Guard setup for large Rails monoliths:


# Gemfile
group :development do
 gem 'guard'
 gem 'guard-rspec'
 gem 'spring-commands-rspec'
end


# Guardfile
guard :rspec, cmd: "bundle exec rspec", all_on_start: false do
 watch(%r{^spec/.+_spec\.rb$})
 watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
 watch(%r{^app/services/(.+)\.rb$}) { |m| "spec/services/#{m[1]}_spec.rb" }
 watch(%r{^app/models/(.+)\.rb$}) { |m| "spec/models/#{m[1]}_spec.rb" }
  # For controllers, run both controller and request specs
 watch(%r{^app/controllers/(.+)\.rb$}) do |m|
   [
     "spec/controllers/#{m[1]}_spec.rb",
     "spec/requests/#{m[1]}_spec.rb"
   ].compact
 end
end

With Guard running in a terminal window, every time you save a file (or accept an AI suggestion), the relevant tests run automatically within 1-3 seconds. This creates a tight feedback loop where the agent can immediately detect if it made a breaking change through a verifiable method that is fast and deterministic.

Optimizing Test Speed

To make Guard truly effective, your individual test files need to run in under 5 seconds. Here are our key strategies:

  1. Use dependency injection to avoid loading the entire Rails environment for service object tests
  2. Stub external API calls aggressively using tools like WebMock
  3. Use build_stubbed instead of create in FactoryBot to avoid database hits when possible
  4. Break up god objects that require complex test setup
  5. Keep feature test count low (5-10 for core flows) and rely on faster unit and controller tests

We've found that when individual test files run in under 5 seconds, developers actually trust the AI more because they can verify suggestions instantly.

 

3. Implement Typed ActiveRecord Models with Sorbet or RBS

Why Types Matter for AI

AI coding assistants are dramatically more effective when working with typed code. In a dynamically-typed Ruby codebase, the AI has to infer types from method names and context, which leads to hallucinations and incorrect suggestions. With explicit types, the AI can provide accurate autocompletions and catch type errors before you even run tests.

Adopting Sorbet Incrementally

Sorbet is a gradual type checker for Ruby, so you don't need to type your entire multi-million line codebase at once. We’ve found a lot of success applying it incrementally, starting with your most critical models and services.


# Before: AI has to guess what fields exist
class Invoice < ApplicationRecord
 belongs_to :customer
 has_many :line_items
end


# After: AI knows exactly what fields and methods exist
# typed: true
class Invoice < ApplicationRecord
 extend T::Sig
  sig { returns(Customer) }
 attr_accessor :customer
  sig { returns(T::Array[LineItem]) }
 attr_accessor :line_items
  sig { returns(T.nilable(String)) }
 attr_accessor :stripe_invoice_id
  sig { returns(BigDecimal) }
 def total_amount
   line_items.sum(&:amount)
 end
end

With Sorbet types in place, AI tools can:

  1. Autocomplete method calls with correct parameters
  2. Catch type mismatches before you run code
  3. Understand the data flow through your application
  4. Generate new methods that match your existing type signatures

Starting Small with Typed Models

We recommend starting by adding Sorbet signatures to:

  1. Your 10-20 most frequently modified models
  2. All service objects (since these change often and benefit most from type safety)
  3. Any complex business logic that AI tools frequently work with

You can use the typed: false comment to opt files out of type checking, allowing you to adopt Sorbet gradually without blocking progress.

 

4. Set Up Replit Agents for Non-Technical Stakeholders

The Design Iteration Bottleneck

One of the most time-consuming aspects of Rails development is the back-and-forth with designers and product managers. They describe a UI change, you implement it, they realize it's not quite right, and the cycle repeats. This process wastes days of developer time on what should be quick iterations.

Replit Agents for Rapid Prototyping

Replit Agent is an AI-powered development environment that can build functional prototypes from natural language descriptions. For Rails teams, this creates an opportunity: let non-technical stakeholders create their own mockups with hardcoded data, and only bring developers in once the design is validated.

Here's our workflow:

Step 1: Create a Sandboxed Rails Template

Set up a minimal Rails app in Replit with:

  1. Your company's CSS framework (Tailwind, Bootstrap, etc.)
  2. Sample models with hardcoded seed data
  3. Your authentication UI (for consistent navigation)
  4. No access to production or staging databases

# db/seeds.rb - Hardcoded sandbox data
User.create!(email: "demo@example.com", name: "Demo User")
Invoice.create!(
 number: "INV-001",
 amount: 1000.00,
 status: "paid",
 customer: Customer.first
)

Step 2: Product Manager Creates Mockup with AI

The product manager describes what they want to Replit Agent:

"Create an invoice list page showing invoice number, customer name,

amount, and status. Add filters for status (paid/unpaid/overdue) and

a date range picker. Use Tailwind CSS to match our design system."

Replit Agent generates a functional page in minutes. The product manager can iterate on the design, try different layouts, and get feedback from stakeholders—all without developer involvement.

Step 3: Developer Implements Production Version

Once the design is validated, a developer extracts the relevant view code and implements it properly with:

  1. Real database queries instead of hardcoded data
  2. Proper authorization checks
  3. Pagination and performance optimizations
  4. Tests

This approach reduces back-and-forth by 70% because all the design iteration happens before developer time is involved.

Security Considerations

Make sure your Replit sandbox:

  1. Uses completely separate, fake data (never production database credentials)
  2. Has no API keys or secrets that could access real systems
  3. Is clearly labeled as a "Design Sandbox" to avoid confusion
  4. Gets recreated from the template regularly to prevent drift

We've found this particularly valuable for customer-facing UI work where design aesthetics matter most, and where non-technical stakeholders have strong opinions that are hard to communicate through mockups or tickets.

 

5. Build an AI-Friendly Service Layer Architecture

Why Controllers Confuse AI Tools

AI assistants struggle with Rails controllers because they mix multiple concerns: params parsing, authorization, business logic, and rendering. When an AI tries to understand or modify a controller, it has to track all these concerns simultaneously, leading to suggestions that miss authorization checks or break rendering logic.

The Service Object Pattern

Moving business logic into service objects makes your code dramatically more AI-friendly. Service objects are plain Ruby classes with a single responsibility and an explicit interface—exactly what AI tools work best with.

Before: Complex Controller Logic

class InvoicesController < ApplicationController
 def create
   authorize! :create, Invoice
  
   customer = Customer.find(params[:customer_id])
   invoice = customer.invoices.build(invoice_params)
  
   if invoice.save
     InvoiceMailer.invoice_created(invoice).deliver_later
     Analytics.track(current_user, 'invoice_created', invoice.id)
    
     if params[:send_to_stripe]
       stripe_invoice = StripeService.create_invoice(invoice)
       invoice.update(stripe_invoice_id: stripe_invoice.id)
     end
    
     render json: invoice, status: :created
   else
     render json: invoice.errors, status: :unprocessable_entity
   end
 end
end

After: Service Object with Clear Interface


# app/services/billing/invoice_creator.rb
module Billing
 class InvoiceCreator
   attr_reader :customer, :invoice_params, :send_to_stripe
  
   def initialize(customer:, invoice_params:, send_to_stripe: false)
     @customer = customer
     @invoice_params = invoice_params
     @send_to_stripe = send_to_stripe
   end
  
   def call
     invoice = customer.invoices.create!(invoice_params)
     send_notifications(invoice)
     sync_to_stripe(invoice) if send_to_stripe
     invoice
   end
  
   private
  
   def send_notifications(invoice)
     InvoiceMailer.invoice_created(invoice).deliver_later
     Analytics.track(customer.user, 'invoice_created', invoice.id)
   end
  
   def sync_to_stripe(invoice)
     stripe_invoice = StripeService.create_invoice(invoice)
     invoice.update(stripe_invoice_id: stripe_invoice.id)
   end
 end
end


# app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
 def create
   authorize! :create, Invoice
   customer = Customer.find(params[:customer_id])
  
   invoice = Billing::InvoiceCreator.new(
     customer: customer,
     invoice_params: invoice_params,
     send_to_stripe: params[:send_to_stripe]
   ).call
  
   render json: invoice, status: :created
 rescue ActiveRecord::RecordInvalid => e
   render json: e.record.errors, status: :unprocessable_entity
 end
end

With this structure, AI tools can:

  1. Understand the InvoiceCreator service in isolation without controller concerns
  2. Generate accurate tests for the service without mocking Rails dependencies
  3. Suggest modifications to business logic without breaking authorization or rendering
  4. Work with the .context.md file in app/services/billing/ to understand the full billing flow

Service Object Conventions for AI

To maximize AI effectiveness with service objects, we follow these conventions:

  1. Always use keyword arguments so AI tools understand parameter names
  2. Keep service objects under 100 lines to fit in AI context windows
  3. Use explicit return values instead of instance variables
  4. Namespace by domain (Billing, Auth, Notifications) for better context files
  5. Follow the Command pattern with a #call method as the main entry point

These conventions create predictable patterns that AI tools can learn and replicate accurately.

Conclusion

Preparing a massive Rails monolith for AI-assisted development isn't about adopting the fanciest AI tools from social media, it's about making your codebase more understandable and modular.

We've seen teams reduce time-to-feature by an order of magnitude after implementing these practices. The initial investment in restructuring and documentation pays dividends within weeks as AI tools begin providing genuinely useful suggestions instead of plausible-sounding nonsense without requiring millions of tokens per session.

The future of Rails development is a partnership between human expertise and AI assistance. By following these five tips, you'll ensure your monolith is ready to take full advantage of that partnership.

Have you tried any of these techniques in your Rails monolith? We'd love to hear about your experiences with AI-assisted development. Reach out to share your story or if you need help preparing your codebase for the AI era.