DEV Community

Katherine Kelly
Katherine Kelly

Posted on

Active Record find vs. find_by vs. where

Data is power, and being able to query a database is powerful. I’ve been using Rails this past year building web applications, mostly as an API for my backend that gets used by my frontend. Rails is great for my needs and I find Active Record to be mostly intuitive when I need to retrieve objects from my database.

I would describe Active Record as the intermediary between your database and your application; it does all of the heavy lifting with SQL and will perform queries on the database for you. All you have to do is know what you're looking for, which finder method to use, pass in the appropriate arguments, and voila, your data will be served. Okay, this may be an oversimplification.

data gif

Yet, there are times I find myself deciding on which finder method to use, such as find_by, find, or where, and the different tradeoffs/considerations of one over the other.

Retrieving a Single Object

There are several finder methods that return a single instance of the model. One of the most common methods includes find.

find

The find method retrieves an object that matches the specified primary key (generally the id).** If a record is not found, it will raise an ActiveRecord::RecordNotFound exception.

Model.find(id)

# record found
User.find(4) # => #<User id: 4, name: "Batman">

# record not found
User.find(53) # ActiveRecord::RecordNotFound (Couldn't find
# User with 'id'=53)

You can also use find to query for multiple objects by passing in multiple key arguments or an array of primary keys. It will return an array containing all of the matched records for the passed-in keys. However, find will raise an ActiveRecord::RecordNotFound exception if not all of the records are found.

Model.find(id, id*)
Model.find([id, id])

# records found
User.find(1, 4) # => [#<User id: 1, name: "Spongebob">, 
#<User id: 4, name: "Batman">]

# records not found
User.find(2, 5) # ActiveRecord::RecordNotFound 
# (Couldn't find all Users with 'id': (2, 5) 
# (found 1 results, but was looking for 2).)

find_by

Another way to locate a single model instance is find_by. The find_by method returns the first record that matches a given condition. The condition is key value pair argument of what you’re looking for. Optionally, you can pass in several arguments as well. If a record is not found, nil is returned. Say you want to find a user that has a pet cat, your Active Record query would like would:

Model.find_by(key: value)

# record found & single argument
User.find_by(pet: 'cat') # => #<User id: 1, pet: “cat”, location: "SF">

# record found & multiple arguments
User.find_by(pet: 'cat', location: 'NY') # => #<User id: 5, pet: "cat", 
# location: "NY">

# record not found
User.find_by(pet: 'iguana') # => nil

Retrieving Multiple Objects

There are several methods to retrieve multiple objects, but I am only look at using where in this post.

where

where is a finder method that returns a collection of Active Record objects represented as an ActiveRecord::Relation. Using where allows you to specify conditions that will filter the records returned. The conditions can be in the form of a string, array, or hash. If nothing is found, an empty ActiveRecord::Relation object is returned (=> #<ActiveRecord::Relation []>.

Below are the three ways the where conditions can be specified:

Pure String Conditions

# record(s) found
Entry.where("user_id = '1'") # => #<ActiveRecord::Relation [#<Entry id: 1, 
# user_id: 1>, #<Entry id: 2, user_id: 1>]...>

# record not found
Entry.where("user_id = '5'") # => #<ActiveRecord::Relation []>

Be careful when using pure strings, as it can leave you vulnerable to SQL injection attacks. Something like Entry.where("name LIKE '%#{params[:name]%'") is ill-advised because of argument safety. Having the variable directly in the conditions string will pass the variable to the database as-is, potentially putting your database at risk from unscrupulous users who can do very bad things.

Array Conditions

# single argument
Entry.where("user_id = ?", params[:users]) # => #<ActiveRecord::Relation # 
# [#<Entry id: 1, user_id: 1, completed: false>, #<Entry id: 2, 
# user_id: 1, completed: true>...]>

# multiple arguments
Entry.where("user_id = ? AND completed = ?", params[:users], true) # => 
# #<ActiveRecord::Relation [#<Entry id: 2, user_id: 1, completed: true>, 
# #<Entry id: 4, user_id: 1, completed: true>...]>

If the argument will be dynamic and vary, Active Record will take the first argument as the conditions string and additional arguments will replace the question marks (?) in it.

Hash Conditions

# single argument
Entry.where(user_id: 1) # => #<ActiveRecord::Relation #<Entry id: 1, 
# user_id: 1, completed: false>, #<Entry id: 2, user_id: 1, completed: 
# true>...]>

#multiple argument
Entry.where(user_id: 1, completed: true) # => #<ActiveRecord::
# Relation #<Entry id: 2, user_id: 1, completed: true>, #<Entry id: 4, 
# user_id: 1, completed: true>]>

Using hash conditions will generally make your code more readable. You can pass in the keys and values of the attributes you want to query on.

Method Chaining

A feature of using where is the ability to method chain. Method chaining is only allowed in a statement if the previous method returns an ActiveRecord::Relation, like where and all. Methods that only return a single object have to be at the end of the statement.

Entry.where(user_id: 1).find_by(completed: false) # => #<ActiveRecord::
# Relation #<Entry id: 1, user_id: 1, completed: false>, #<Entry id: 3, 
# user_id: 1, completed: false>...]>

This is just a small sampling of the power of Active Record. As you build your Active Record queries and decide which finder method to use, it will vary based on your needs. It also helps to keep in mind what each method returns, how it handles a match not found, and how many records you want. Accessing your data does not have to be a SQL-induced headache, and we have Active Record to thank for that.

Resources
Rails Guides: Active Record Query Interface

Top comments (0)