AI Code Review for Ruby on Rails

Rails applications reward speed early and punish it later. Convention-over-configuration hides complexity behind magic, and a mature Rails codebase accumulates N+1 queries, tangled callback chains, and ActiveRecord anti-patterns that no linter can trace. VibeRails reads your entire Rails application and finds the framework-specific debt that compounds across models, controllers, and services.

Where Rails convention becomes Rails confusion

Convention-over-configuration is the organising principle of Rails, and it works remarkably well for small applications. Routes map to controllers, controllers delegate to models, models define the domain. But as an application grows, these conventions start to fight each other. The fat model pattern encourages putting business logic in ActiveRecord classes, which leads to models with hundreds of methods, dozens of callbacks, and validation rules that depend on context the model was never designed to carry.

Service objects were introduced to solve the fat model problem, but without enforced conventions they create their own category of drift. Some teams put service objects in app/services, others use app/interactors or app/operations. Some services return booleans, others return result objects, and a few raise exceptions on failure. Over time, the service layer becomes as inconsistent as the models it was meant to simplify. A developer reading an unfamiliar part of the codebase cannot predict how a service object will behave without reading its entire implementation.

Single Table Inheritance is one of the most persistent sources of hidden complexity in Rails applications. STI starts as a clean abstraction – a Vehicle base class with Car, Truck, and Motorcycle subclasses sharing one database table. But as the subclasses diverge in behaviour and data requirements, the shared table accumulates nullable columns, the base class fills with conditional logic, and queries against the base type return unexpected mixtures of subtypes. Migrating away from STI once the application depends on it is a major undertaking that most teams defer indefinitely.

N+1 queries and the eager loading maze

N+1 queries are the defining performance anti-pattern in Rails applications. ActiveRecord makes it trivially easy to traverse associations – order.line_items.each reads naturally, but generates one database query per line item rather than a single query for all of them. The bullet gem catches some N+1 queries during development, but only on code paths that are exercised during manual testing. Code paths that are triggered by background jobs, API endpoints, or admin interfaces often go unexamined.

The fix is includes, preload, or eager_load, but knowing which to use requires understanding the query strategy. includes lets Rails decide whether to use a separate query or a LEFT OUTER JOIN. preload always uses separate queries. eager_load always uses a JOIN. Choosing the wrong strategy for a deeply nested association can actually worsen performance by loading a Cartesian product of rows. Teams that apply includes everywhere as a blanket fix often discover that their memory consumption has increased without understanding why.

Scopes and custom finders add another layer of complexity. A scope that filters by status might defeat the eager loading that was applied in the controller. A default_scope that adds an ORDER BY clause can cause unexpected behaviour when composed with other scopes. These interactions are invisible at the individual file level and only become apparent when tracing a request from controller through model queries to view rendering.

Mass assignment, callbacks, and silent side effects

Rails introduced strong parameters to mitigate mass assignment vulnerabilities, replacing the older attr_accessible approach. But strong parameters are only as secure as the developer writing them. A permit! call that allows all parameters, a nested attributes declaration that permits :id and :_destroy without access control checks, or a params.merge that reintroduces unpermitted keys all create the same category of risk. These patterns are scattered across controllers and are easy to miss during manual code review.

Callback chains remain one of the most dangerous aspects of Rails applications. A before_save that sets a normalised field, an after_create that enqueues a background job, and an after_commit that sends a notification can all fire as the result of a single update! call in a controller. When callbacks call methods that modify other records, those records trigger their own callbacks, creating cascading side effects that are nearly impossible to predict from reading any single file.

Concerns compound the problem further. A model that includes five concerns inherits methods and callbacks from each of them, but the interaction between those concerns is not defined anywhere. A before_validation callback in one concern might depend on a field that is set by an after_initialize callback in another. The execution order depends on the order of include statements, which most developers treat as arbitrary. These ordering dependencies create subtle bugs that only surface under specific data conditions.

How VibeRails reviews Rails applications

VibeRails performs a full-codebase scan using frontier large language models. Every Ruby file, ERB and HAML template, migration, initialiser, and configuration file is analysed – not just recent diffs, but the complete application including tests, rake tasks, and gem configuration.

For Rails codebases specifically, the AI reasons about:

  • N+1 query paths – association traversals in views and serialisers that bypass eager loading, scopes that defeat includes, and default_scope interactions that produce unexpected query plans
  • ActiveRecord anti-patterns – STI tables with excessive nullable columns, polymorphic associations without type constraints, find_by_sql that bypasses the query interface, and callbacks that modify unrelated records
  • Service object inconsistency – services with heterogeneous return types, missing error handling contracts, services that call other services without clear dependency boundaries, and God services that orchestrate entire workflows
  • Mass assignment risks – controllers using permit!, nested attributes without access control, parameter merging that bypasses strong parameters, and update_columns calls that skip validations entirely
  • Callback cascadesbefore_save, after_commit, and after_create chains that trigger cross-model side effects, concern interactions with implicit ordering dependencies, and callbacks that silently swallow exceptions
  • Convention violations – business logic in controllers, query logic in views, configuration scattered across initialisers without documentation, and routes that bypass RESTful conventions without justification

Each finding includes the file path, line range, severity, category, and a detailed description explaining why the pattern is problematic and how to address it. Findings are organised into 17 categories so teams can filter and prioritise by area of concern.

Dual-model verification for Rails idioms

Rails applications contain genuine ambiguity that makes automated review challenging. A fat model might be a deliberate architectural choice for a domain concept that truly belongs in one place. A callback chain might be the correct approach for maintaining data consistency in a system where eventual consistency is unacceptable. A default_scope might be appropriate when soft-delete behaviour must be enforced globally.

VibeRails supports running reviews with two different AI backends – Claude Code and Codex CLI – in sequence. The first pass identifies potential issues, the second verifies them using a different model architecture. When both models independently flag the same N+1 query path or callback cascade, confidence is high. Disagreements highlight areas where the pattern may be intentional, giving your team the context to triage efficiently rather than reviewing every file manually.

From findings to fixes in your Rails application

After triaging findings, VibeRails can dispatch AI agents to implement fixes directly in your local repository. For Rails projects, this typically means adding includes or preload to N+1 query paths, extracting callbacks into explicit service objects, replacing permit! with specific parameter allowlists, decomposing STI hierarchies into separate tables, and standardising service object return contracts.

Each fix is generated as a local code change you can inspect, test, and commit or discard. The AI works within the conventions of your existing codebase, matching your application's naming patterns, test framework, and directory structure – whether you use RSpec or Minitest, Slim or ERB, Sidekiq or ActiveJob.

VibeRails runs as a desktop app with a BYOK model – it orchestrates Claude Code or Codex CLI installations you already have. No code is uploaded to VibeRails servers. AI analysis is sent directly to the provider you configured, billed to your existing subscription. Each licence covers one developer: $19/month (cancel anytime) or $299 lifetime. The free tier includes 5 issues per session to evaluate the workflow.

Download Free See Pricing