Rails Error Prevention Skill
This skill provides preventative checklists and error patterns for common Rails mistakes. Review relevant sections BEFORE writing code.
When to Use This Skill
-
Before creating ViewComponents
-
Before writing ActiveRecord queries with GROUP BY or joins
-
Before writing view code that calls component methods
-
Before creating controller actions
-
When debugging undefined method errors
-
When debugging template not found errors
-
When debugging ActiveRecord grouping errors
Critical Rule: Method Exposure Verification
This is the #1 cause of runtime errors in Rails applications.
WRONG ASSUMPTION: Service has method → View can call it through component CORRECT RULE: Service has method + Component exposes it = View can call it
Verification Process
Step 1: List all methods view will call on component
grep -oE '@[a-z_]+.[a-z_]+' app/views/{path}/*.erb | sort -u
Step 2: List all public methods in component
grep -E '^\s+def [a-z_]+' app/components/{component}_component.rb
Step 3: Compare - any view call without component method = BUG
Missing methods MUST be added to component BEFORE writing view code
Patterns to Fix Missing Methods
Pattern 1: Delegation
class Metrics::DashboardComponent < ViewComponent::Base delegate :calculate_lifetime_tasks, :calculate_lifetime_success_rate, to: :@service
def initialize(service:) @service = service end end
Pattern 2: Wrapper methods (preferred for transformation)
class Metrics::DashboardComponent < ViewComponent::Base def initialize(service:) @service = service end
def lifetime_tasks @service.calculate_lifetime_tasks end
def lifetime_success_rate @service.calculate_lifetime_success_rate end end
Pattern 3: Expose service directly (use sparingly)
class Metrics::DashboardComponent < ViewComponent::Base attr_reader :service
def initialize(service:) @service = service end end
View then calls: dashboard.service.calculate_lifetime_tasks
ViewComponent Errors
Template Not Found
Error Pattern:
Couldn't find a template file or inline render method for {Component}
Root Causes:
-
Missing template file (component.html.erb)
-
Template in wrong location
-
Class name doesn't match file path
-
Using render without inline template defined
Prevention Checklist:
Before creating component:
ls app/components/ # Check structure head -50 app/components/*_component.rb | head -1 # Review existing pattern
Verify naming: ComponentName -> component_name/
Required Files:
app/components/{namespace}/{component_name}_component.rb app/components/{namespace}/{component_name}_component.html.erb
Correct Patterns:
Option A: Separate template file
app/components/metrics/kpi_card_component.rb
class Metrics::KpiCardComponent < ViewComponent::Base def initialize(title:, value:) @title = title @value = value end end
app/components/metrics/kpi_card_component.html.erb
<div class="kpi-card">
<h3><%= @title %></h3>
<span><%= @value %></span>
</div>
Option B: Inline template (call method)
class Metrics::KpiCardComponent < ViewComponent::Base def initialize(title:, value:) @title = title @value = value end
def call content_tag :div, class: "kpi-card" do safe_join([ content_tag(:h3, @title), content_tag(:span, @value) ]) end end end
Helper Method Errors
Error Pattern:
undefined local variable or method '{method}' for an instance of {Component}
Did you mean helpers.{method}?
Root Cause: Calling view helper directly without helpers. prefix
Prevention Rules:
-
ViewComponents do NOT have direct access to view helpers
-
Must use helpers.method_name for any Rails helper
Helpers Requiring Prefix:
WRONG # CORRECT
link_to(...) helpers.link_to(...) image_tag(...) helpers.image_tag(...) url_for(...) helpers.url_for(...) form_with(...) helpers.form_with(...) number_to_currency(...) helpers.number_to_currency(...) time_ago_in_words(...) helpers.time_ago_in_words(...) truncate(...) helpers.truncate(...) pluralize(...) helpers.pluralize(...) content_tag(...) helpers.content_tag(...) tag(...) helpers.tag(...) content_for(...) helpers.content_for(...)
Alternative - Delegate Helpers:
class Metrics::KpiCardComponent < ViewComponent::Base delegate :number_to_currency, :link_to, to: :helpers
def formatted_value number_to_currency(@value) # Now works without prefix end end
Content Block Errors
Error Pattern:
content is not defined
Prevention: Always check content? before using content
<% if content? %> <%= content %> <% end %>
ActiveRecord Errors
Grouping Error (PostgreSQL)
Error Pattern:
PG::GroupingError: ERROR: column "{table}.{column}" must appear in the GROUP BY clause
Root Causes:
-
SELECT includes columns not in GROUP BY or aggregate functions
-
Using .select with .group but including non-grouped columns
-
Eager loading (includes /preload ) with GROUP BY
-
Using .pluck or .select with associations and GROUP BY
Prevention Rules:
-
Every non-aggregated column in SELECT must be in GROUP BY
-
NEVER combine includes /preload /eager_load with GROUP BY
-
Use .select only for grouped columns and aggregates
-
If you need associated data with grouped results, query separately
Examples:
WRONG - selecting all columns with group
Task.includes(:user).group(:task_type).count
This tries to select tasks.* which fails
WRONG - selecting id with group
Task.select(:task_type, :id).group(:task_type).count
id is not grouped or aggregated
CORRECT - only select grouped columns and aggregates
Task.group(:task_type).count
=> { "type_a" => 5, "type_b" => 3 }
CORRECT - explicit select with only valid columns
Task.select(:task_type, 'COUNT(*) as count').group(:task_type)
CORRECT - if you need associated data, query separately
type_counts = Task.group(:task_type).count tasks_by_type = type_counts.keys.each_with_object({}) do |type, hash| hash[type] = Task.where(task_type: type).includes(:user).limit(5) end
CORRECT - using pluck for simple aggregations
Task.group(:task_type).pluck(:task_type, 'COUNT(*)')
=> [["type_a", 5], ["type_b", 3]]
Before Writing GROUP BY Query:
Detection command
grep -r '.group(' app/ --include='.rb' -A3 -B3 grep -r '.includes..group|.group..includes' app/ --include='.rb'
N+1 Queries
Detection: Multiple queries for same association in logs
Prevention:
WRONG - N+1
tasks.each { |t| puts t.user.name }
CORRECT
tasks.includes(:user).each { |t| puts t.user.name }
Ambiguous Column
Error Pattern:
PG::AmbiguousColumn: ERROR: column reference "{column}" is ambiguous
Prevention: Always qualify columns when joining
WRONG
User.joins(:tasks).where(status: 'active') # Both have status?
CORRECT
User.joins(:tasks).where(users: { status: 'active' })
OR
User.joins(:tasks).where('users.status = ?', 'active')
Missing Attribute
Error Pattern:
ActiveModel::MissingAttributeError: missing attribute: {attribute}
Prevention: Include all attributes needed downstream
WRONG - later code needs 'email'
users = User.select(:id, :name) users.each { |u| puts u.email } # ERROR!
CORRECT
users = User.select(:id, :name, :email)
Controller Errors
Missing Template
Error Pattern:
ActionView::MissingTemplate
Prevention:
-
Every non-redirect action needs a view OR explicit render
-
View path must match controller namespace
Before creating controller action:
ls app/views/{controller}/
Unknown Action
Error Pattern:
AbstractController::ActionNotFound
Root Causes:
-
Route points to non-existent action
-
Action is private/protected (must be public)
Verification
rails routes | grep {controller} grep -n 'def ' app/controllers/{controller}_controller.rb
Nil Errors
Error Pattern:
undefined method '{method}' for nil:NilClass
Prevention Patterns:
Safe navigation
user&.profile&.settings&.theme
Guard clause
return unless user&.profile
With default
user&.profile&.settings&.theme || 'default'
Early return
def process_user(user) return if user.nil?
... rest of method
end
Argument Errors
Error Pattern:
ArgumentError: wrong number of arguments (given X, expected Y)
Prevention:
Before modifying method signature
grep -r 'method_name' app/ --include='*.rb'
Update all call sites
Rules:
-
Prefer keyword arguments for methods with 2+ params
-
Use default values for optional params
Prevention Checklists
Before Creating ViewComponent
[ ] Check app/components/ structure [ ] Review existing component for template pattern (inline vs file) [ ] Verify naming: Namespace::ComponentNameComponent [ ] Create both .rb AND .html.erb files (unless using inline) [ ] List ALL methods view will need [ ] Implement ALL needed methods in component [ ] Prefix ALL Rails helpers with 'helpers.' or delegate them
Before Writing View Code
[ ] List ALL methods view will call on component [ ] For EACH method, verify it exists in component class [ ] If method missing, ADD to component BEFORE view code [ ] Verify underlying service/model has implementation
Before Writing ActiveRecord Query with GROUP BY
[ ] List ALL columns in SELECT [ ] Verify each is in GROUP BY or aggregate function [ ] Remove includes/preload/eager_load [ ] Test query in rails console first
Before Writing ActiveRecord Query with Joins
[ ] Qualify ambiguous columns with table name [ ] Consider if includes is better for the use case
Before Creating Controller Action
[ ] View exists for non-redirect actions? [ ] Routes point to public methods? [ ] All @variables view needs are set?
Before Any Code
[ ] Inspect existing similar implementations [ ] Check naming conventions in codebase [ ] Verify all dependencies exist (gems, files, routes) [ ] Verify method exposure across all layers (view→component→service)
Quick Debugging Commands
Find where method is called
grep -r 'method_name' app/ --include='*.rb'
Find method definition
grep -rn 'def method_name' app/ --include='*.rb'
Check method visibility
grep -B5 'def method_name' app/path/to/file.rb
List component methods
grep -E '^\s+def [a-z_]+' app/components/path_component.rb
List service methods
grep -E '^\s+def [a-z_]+' app/services/path.rb
Compare view calls vs component methods
grep -oE '@\w+.\w+' app/views/path/*.erb | sort -u