Skip to main content

Convert SQL JOIN to Rails ActiveRecord

Transform SQL JOIN queries into Rails ActiveRecord associations instantly

Supports INNER JOIN, LEFT JOIN, RIGHT JOIN with proper Rails associations • 100% Free • No registration required

Try it now:

Common JOIN to ActiveRecord Conversions

INNER JOIN

SQL JOIN:

SELECT users.*, posts.* FROM users INNER JOIN posts ON users.id = posts.user_id

ActiveRecord:

User.joins(:posts).includes(:posts)

LEFT JOIN

SQL JOIN:

SELECT users.*, posts.title FROM users LEFT JOIN posts ON users.id = posts.user_id

ActiveRecord:

User.left_joins(:posts).select('users.*, posts.title')

JOIN with WHERE Conditions

SQL JOIN:

SELECT users.name FROM users JOIN posts ON users.id = posts.user_id WHERE posts.published = true

ActiveRecord:

User.joins(:posts).where(posts: { published: true }).select('users.name')

Multiple JOINs

SQL JOIN:

SELECT u.name, p.title, c.content FROM users u JOIN posts p ON u.id = p.user_id JOIN comments c ON p.id = c.post_id

ActiveRecord:

User.joins(posts: :comments).select('users.name, posts.title, comments.content')

Common JOIN Pitfalls When Converting to ActiveRecord

LEFT JOIN silently becomes INNER JOIN when you add a WHERE on the right table

User.left_joins(:posts).where(posts: { published: true }) looks like a LEFT JOIN but behaves as INNER JOIN — users without posts are excluded because the WHERE filters out NULL rows. The fix is to move the condition into the JOIN clause using a string: User.joins("LEFT JOIN posts ON posts.user_id = users.id AND posts.published = true"), or use .or to include NULLs explicitly.

joins does not load associations — includes does

User.joins(:posts) produces the JOIN SQL but does not populate user.posts in memory. Accessing user.posts after a joins call fires a separate query per user — the N+1 you were trying to avoid. Use includes(:posts) when you need to read association data, and joins(:posts) only when filtering.

N+1 queries hide behind correct-looking JOIN code

A common pattern: User.joins(:posts).each { |u| u.posts.count } — the JOIN is there, but each u.posts.count fires another query. Use includes with a counter cache, or select("users.*, COUNT(posts.id) AS posts_count").group("users.id") to aggregate in the JOIN itself. The Bullet gem detects these during development.

Use left_joins (Rails 5+) instead of a raw SQL string for simple cases

Before Rails 5, LEFT JOIN required raw SQL strings. Since Rails 5, User.left_joins(:posts) generates a clean LEFT OUTER JOIN using the association definition — no manual ON clause needed. Prefer this over string-based joins unless you need non-standard join conditions, since it respects association scopes and is composable with other ActiveRecord methods.

Ready to Convert Your JOIN Queries?

Transform complex SQL JOINs into clean ActiveRecord code