Introduction
The where method is the backbone of Active Record querying. Beyond simple equality checks, it supports ranges, pattern matching, NULL handling, OR/AND composition, and raw SQL fragments. Mastering these techniques lets you express almost any SQL condition without leaving Ruby.
Key Concepts
where.not: Negates conditions, producingNOT INor!=SQL..or: Combines two relations with SQLORlogic.- Range conditions: Ruby ranges map to SQL
BETWEENor comparison operators. sanitize_sql_like: Escapes user input for LIKE queries to prevent injection of%and_wildcards.
Real World Context
Every Rails application needs filtering: dashboards with date ranges, admin panels with multi-criteria search, APIs with complex query parameters. Knowing the full where API means you can express these filters cleanly without falling back to raw SQL strings.
Deep Dive
Comparison Operators with Ranges
ruby# Using ranges (more readable than raw SQL) Product.where(price: 10..100) # BETWEEN 10 AND 100 Product.where(price: 10...100) # >= 10 AND < 100 (exclusive end) # Using beginless/endless ranges (Ruby 2.7+) Product.where(price: ..100) # price <= 100 Product.where(price: 100..) # price >= 100
Ranges are the idiomatic way to express comparison operators in Active Record. They generate efficient SQL and are easier to read than string conditions.
NOT Conditions
rubyArticle.where.not(published: false) Article.where.not(author_id: nil) # WHERE author_id IS NOT NULL Article.where.not(status: ["draft", "archived"]) # NOT IN
OR Conditions
rubyArticle.where(published: true).or(Article.where(featured: true)) # WHERE published = true OR featured = true
Both sides of .or must be relations on the same model. You cannot mix different models.
Pattern Matching (LIKE)
ruby# Using sanitize_sql_like for user input query = Article.sanitize_sql_like(params[:search]) Article.where("title LIKE ?", "%#{query}%") # Case-insensitive (PostgreSQL) Article.where("title ILIKE ?", "%rails%")
Always use sanitize_sql_like when the search term comes from user input. Without it, a user could inject % or _ wildcards.
NULL Handling
rubyArticle.where(published_at: nil) # IS NULL Article.where.not(published_at: nil) # IS NOT NULL # Rails 7+ missing/associated methods Article.where.missing(:author) # No associated author Article.where.associated(:author) # Has associated author
Date and Time Queries
rubyArticle.where(created_at: Date.today.all_day) Article.where(created_at: 1.week.ago..) Article.where(published_at: start_date..end_date)
Common Pitfalls
- SQL injection with string interpolation -- Never write
where("name = '#{params[:name]}'"). Always use placeholders:where("name = ?", params[:name]). - Confusing
.orscope -- Both sides of.ormust be on the same model. Trying to OR across different models raises an error. - Hash conditions use AND --
where(a: 1, b: 2)meansa = 1 AND b = 2, not OR. Use.orfor OR logic.
Best Practices
- Prefer hash syntax --
where(status: "active")is safer and more readable thanwhere("status = ?", "active"). - Use ranges for comparisons --
where(price: 10..100)is cleaner thanwhere("price >= ? AND price <= ?", 10, 100). - Sanitize LIKE inputs -- Always use
sanitize_sql_likefor user-provided search terms.
Summary
wheresupports hashes, arrays, ranges, strings with placeholders, and raw SQL.- Use
where.notfor negation,.orfor OR conditions. - Ranges map to
BETWEENand comparison operators. - Always sanitize user input in LIKE queries.
- Rails 7+ adds
where.missingandwhere.associatedfor association-based filtering.
Code Examples
# Range queries
Product.where(price: 10..100) # BETWEEN 10 AND 100
Product.where(price: 100..) # price >= 100
# OR conditions
Article.where(published: true).or(Article.where(featured: true))
# NOT conditions
User.where.not(role: ["guest", "banned"])
# NULL handling
Article.where.associated(:author) # Has an author