When Not to Use ActiveRecord (and What to Use Instead)
ActiveRecord is great until you need exact SQL semantics. These are the cases where SQL/Arel/views are safer.
The pragmatic rule: Use ActiveRecord for 80% of queries. When you hit its limits (window functions, complex CTEs, exact LEFT JOIN semantics), don't fight it — use raw SQL or database views.
Use ActiveRecord When…
-
Your query maps to standard Rails patterns: Basic WHERE, JOIN, ORDER BY, GROUP BY, and simple aggregations.
-
You want database portability: ActiveRecord abstracts differences between PostgreSQL, MySQL, SQLite.
-
You're working with associations: Eager loading (
.includes), scopes, and model methods work great. -
Future maintainability matters: Other Rails developers can read and modify ActiveRecord code faster than raw SQL.
Prefer Raw SQL When…
-
You need window functions:
ROW_NUMBER() OVER,RANK(),LAG/LEAD. Rails 7+ has limited support, but raw SQL is clearer. -
Complex reporting queries: Multi-step CTEs, UNION, FULL OUTER JOIN, recursive queries. Don't make ActiveRecord do SQL's job.
-
Database-specific features: PostgreSQL arrays/jsonb, MySQL JSON functions, Postgres full-text search. Use the database's native features.
-
You've tried ActiveRecord and the SQL is ugly: If
.to_sqlshows a mess of subqueries or the query is harder to read than raw SQL, just use SQL.
How to use raw SQL safely:
# Option 1: Execute and map to models
User.find_by_sql("SELECT * FROM users WHERE complex_condition")
# Option 2: Connection.execute for non-model queries
ActiveRecord::Base.connection.execute("SELECT COUNT(*) ...").first['count']
# Option 3: Prepared statements to prevent SQL injection
User.find_by_sql(["SELECT * FROM users WHERE name = ?", params[:name]])
Use Arel When…
Arel is the low-level SQL AST library that ActiveRecord uses internally. You rarely need it directly, but it's useful for:
-
Dynamic query building: Complex OR/AND conditions based on user input. Arel lets you compose conditions programmatically without string concatenation.
-
Functions ActiveRecord doesn't expose:
COALESCE,NULLIF, custom database functions. -
Reusable query fragments: Extract common Arel nodes into methods, compose them in different queries.
Arel example:
users = User.arel_table
query = users[:created_at].gt(1.week.ago)
.and(users[:active].eq(true))
.or(users[:admin].eq(true))
User.where(query)
When NOT to use Arel: If ActiveRecord's .where and .or methods work, use those. Arel is more verbose.
Consider Database Views / Materialized Views When…
Database views let you encapsulate complex SQL and query it like a table. Materialized views (PostgreSQL) cache results for even faster queries.
-
Complex queries used everywhere: Instead of duplicating SQL across models/controllers, define it once as a view.
-
Reporting dashboards: Aggregations that don't need real-time data. Refresh a materialized view every 5 minutes, query it instantly.
-
You want Rails-style model access: Map an ActiveRecord model to a view, get
.where,.order, etc. for free.
Create a view in Rails migration:
class CreateUserStatsView < ActiveRecord::Migration[7.0]
def up
execute <<-SQL
CREATE VIEW user_stats AS
SELECT users.id, users.name,
COUNT(posts.id) as post_count,
MAX(posts.created_at) as latest_post_at
FROM users
LEFT JOIN posts ON users.id = posts.user_id
GROUP BY users.id, users.name
SQL
end
def down
execute "DROP VIEW IF EXISTS user_stats"
end
end
# Map to ActiveRecord
class UserStat < ApplicationRecord
self.primary_key = 'id'
end
# Use like a normal model
UserStat.where('post_count > 10').order(latest_post_at: :desc)
Practical Rule of Thumb + Testing Strategy
Decision Flowchart
-
1
Start with ActiveRecord. Try the simplest approach first:
.where,.joins,.order. -
2
Run
.to_sqland check the output. If it matches your intent, you're done. If not, continue. -
3
Try Arel for dynamic conditions. If you need programmatic query building, Arel might help.
-
4
Switch to raw SQL if ActiveRecord fights you. Don't spend 2 hours making ActiveRecord do what SQL does in 5 lines.
-
5
Consider a view if you'll reuse the query. Encapsulate complex SQL in a view, query it from Rails like a table.
Testing Your Decision
No matter which approach you choose, validate it:
# 1. Compare row counts
original_count = ActiveRecord::Base.connection.execute(sql).count
new_count = YourModel.your_scope.count
raise unless original_count == new_count
# 2. Compare actual IDs
original_ids = ActiveRecord::Base.connection.execute(sql).map { |r| r['id'] }.sort
new_ids = YourModel.your_scope.pluck(:id).sort
raise unless original_ids == new_ids
# 3. Add a regression spec
it "returns correct user stats" do
expect(UserStat.count).to eq(expected_count)
expect(UserStat.first.post_count).to eq(expected_post_count)
end
Complete Tool Comparison
| Tool/Method | Primary Use | Setup Time | Rails Focus | Cost | Team Collab |
|---|---|---|---|---|---|
SQL to ActiveRecordOur Tool |
SQL Conversion | 0 seconds | Native | Free | Individual |
Manual ConversionHand-coding |
Custom Logic | 20-45 min | Variable | Free | Limited |
dbdiagram.ioVisual DB Design |
Schema Design | 2-5 min | Generic | Freemium | Strong |
DrawSQLCollaborative Design |
Team Planning | 5-10 min | Generic | Freemium | Excellent |
Rails GeneratorsBuilt-in Tools |
Model Creation | Instant | Native | Free | Code-based |
Open Source ToolsGitHub Projects |
Various | Setup Required | Limited | Free | Depends |
Detailed Tool Analysis
Visual Database Design Tools
Best For:
- New project planning and design
- Team collaboration on database structure
- Creating database documentation
- Multi-framework projects (not Rails-only)
Popular Options:
dbdiagram.io
Free tier available, clean interface, code generation
DrawSQL
Strong collaboration features, version control
Lucidchart
Enterprise features, broader diagramming tools
Limitations for Rails Developers:
- No direct ActiveRecord code generation
- Time-consuming for existing SQL queries
- Requires manual Rails convention application
- Limited complex query pattern support
💡 Pro Tip:
Use visual tools for initial design, then convert specific queries with our SQL-to-ActiveRecord tool.
Command Line & Open Source Tools
Popular Options:
Rails Built-in Generators
rails generate model, rails generate migration
✓ Native Rails integration
Annotate Gem
Auto-generates model annotations from schema
✓ Keeps models in sync with DB
Rails ERD
Generates entity-relationship diagrams
✓ Visual documentation from code
Strengths & Limitations:
✓ Strengths:
- • Perfect Rails integration
- • No external dependencies
- • Free and open source
- • Command-line workflow
✗ Limitations:
- • No SQL-to-ActiveRecord conversion
- • Limited to basic generation patterns
- • Requires Rails environment setup
- • Not suitable for query conversion
How to Choose the Right Tool
Decision Framework
Ask Yourself:
1. What's your primary goal?
Convert existing SQL vs Design new schema vs Learn Rails patterns
2. Are you working alone or with a team?
Individual development vs Team collaboration requirements
3. How much time do you want to invest?
Instant results vs Setup and learning curve acceptable
4. Is this Rails-specific or multi-framework?
Rails-only project vs Multiple database technologies
Recommended Combinations:
Rails Development Team
Visual tool for planning + Our converter for implementation
Solo Developer
Our SQL-to-ActiveRecord tool for speed + Rails generators for models
Learning Rails
Our tool for examples + Manual practice for understanding
Enterprise Team
Visual collaborative tool + Our converter + Documentation tools
Ready to Convert Your SQL?
When ActiveRecord works, use it. When it doesn't, use our converter to get started, then switch to raw SQL or views as needed.