method

find

find(*args)
public

Find operates with three different retrieval approaches:

  • Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). If no record can be found for all of the listed ids, then RecordNotFound will be raised.
  • Find first: This will return the first record matched by the options used. These options can either be specific conditions or merely an order. If no record can be matched, nil is returned.
  • Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.

All approaches accept an options hash as their last parameter. The options are:

  • :conditions: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
  • :order: An SQL fragment like "created_at DESC, name".
  • :group: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
  • :limit: An integer determining the limit on the number of rows that should be returned.
  • :offset: An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
  • :joins: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed) or named associations in the same form used for the :include option, which will perform an INNER JOIN on the associated table(s). If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table’s columns. Pass :readonly => false to override.
  • :include: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer to already defined associations. See eager loading under Associations.
  • :select: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not include the joined columns.
  • :from: By default, this is the table name of the class, but can be changed to an alternate table name (or even the name of a database view).
  • :readonly: Mark the returned records read-only so they cannot be saved or updated.
  • :lock: An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE". :lock => true gives connection’s default exclusive lock, usually "FOR UPDATE".

Examples for find by id:

  Person.find(1)       # returns the object for ID = 1
  Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
  Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
  Person.find([1])     # returns an array for the object with ID = 1
  Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")

Note that returned records may not be in the same order as the ids you provide since database rows are unordered. Give an explicit :order to ensure the results are sorted.

Examples for find first:

  Person.find(:first) # returns the first object fetched by SELECT * FROM people
  Person.find(:first, :conditions => [ "user_name = ?", user_name])
  Person.find(:first, :order => "created_on DESC", :offset => 5)

Examples for find all:

  Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
  Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
  Person.find(:all, :offset => 10, :limit => 10)
  Person.find(:all, :include => [ :account, :friends ])
  Person.find(:all, :group => "category")

Example for find with a lock. Imagine two concurrent transactions: each will read person.visits == 2, add 1 to it, and save, resulting in two saves of person.visits = 3. By locking the row, the second transaction has to wait until the first is finished; we get the expected person.visits == 4.

  Person.transaction do
    person = Person.find(1, :lock => true)
    person.visits += 1
    person.save!
  end

19Notes

Specialized versions of find with method_missing

Pilaf · Feb 24, 20099 thanks

Check ActiveRecord::Base.method_missing for documentation on the family of "magic" find methods (find_by_x, find_all_by_x, find_or_create_by_x, etc.).

Example: find by associated table

eeby · Aug 4, 20087 thanks

Say you have tables "authors" and "books" and they have a one-to-many association.

You want authors who have written books with "cooking" in the title...

cookbook_authors = Author.find(:all, 
:conditions => ['books.title LIKE ?', '%cooking%'], 
:joins => [:books], 
:order => 'authors.last_name' )

For many-to-many associations, it's a similar pattern. Say you have tables "people" and "organizations" with a many-to-many association through the join table "organization_memberships".

Ski Club members whose first name starts with "a"...

ski_club_members = Person.find(:all, 
:conditions => ['first_name LIKE ? AND organizations.name = ?', 
  'a%', 'Ski Club'], 
:joins => [:organizations], 
:order => 'people.last_name' )

Avoiding duplicate results when you do a join

eeby · Aug 7, 20087 thanks

When you use the :joins option you can get multiple instances of the same item. For example, say you want every User who owns one or more Lists. With the code below, if a user owns 5 lists, the User will show up five times in the results:

users = User.find(:all, 
:conditions => ['users.id = lists.user_id'], 
:joins => [:lists], 
:order => 'users.username')

You can cause each found user to appear only once by using the :select option with "DISTINCT":

users = User.find(:all, 
:conditions => ['users.id = lists.user_id'], 
:joins => [:lists], 
:select => 'DISTINCT users.*'
:order => 'users.username')

Join multiple tables

Ariejan · Aug 7, 20097 thanks

It's easy to join multiple tables too. In this case we have:

class Article
belongs_to :feed
end

class Feed
has_many :articles
belongs_to :source
end

class Source
has_many :feeds
# t.bool :visible
end

You can search articles and specify a condition on the sources table.

Article.find(:all, 
:conditions => { :feeds => { :sources => { :visible => true }}}, 
:joins => [:feed => :source],

Accessing aggregate methods with :group

RISCfuture · Dec 11, 20086 thanks

You can access aggregate methods (such as +SUM+, +COUNT+, etc.) when using a +JOIN+ and GROUP BY query by simply naming the aggregate columns and calling them as methods on the returned objects:

hits_by_page = WebpageHit.all({ :select => "webpages.*, COUNT(webpage_hit.id) AS view_count", :joins => :webpage, :group => "webpages.id" }) homepage_hits = hits_by_page[homepage.id].view_count

The +view_count+ method is added to the +Webpage+ model by this call. Note, however, that this method returns a string, and is not typecasted by Rails.

:conditions examples

mihserf · Jul 22, 20084 thanks

:conditions => {:login => login, :password => password}

:conditions => ['subject LIKE :foo OR body LIKE :foo', {:foo => 'woah'}]

(from the book "The Rails Way")

Include two level has many model example

RobinWu · Aug 23, 20083 thanks

=== class Issue < ActiveRecord::Base has_many :journals end

class Journal < ActiveRecord::Base
belongs_to :issue
has_many :details, :class_name => "JournalDetail", :dependent => :delete_all
end

class JournalDetail < ActiveRecord::Base
belongs_to :journal
end

issue = Issue.find(:first, :include => {:journals => :details}

log record follow:

SELECT * FROM `issues` LIMIT 1
SELECT `journals`.* FROM `journals` WHERE (`journals`.`journalized_id` IN (1) and `journals`.`journalized_type` = 'Issue' AND (dustbin <> 1))
SELECT `journal_details`.* FROM `journal_details` WHERE (`journal_details`.journal_id IN (1,2,876177898,935815637))

when execute follow code, then not build sql sentent:

issue.journals
issue.journals[0].details

Response to created_at/created_on and find(:first).map

etoastw · Aug 22, 20082 thanks

A couple of comments on the comments:

The created_at/created_on thing clearly relates to the columns that have been defined in your model -- it's got nothing to do with Rails 2.1 (although the Rails 2 "timestamp" method adds the created_at column).

And find(:first), find(:last) return model objects, rather than arrays/result sets, which is why you can't do a map on them -- you can't do anything that you would do on an Enumerable, unless the model object itself is Enumerable.

Re: Find random record

RISCfuture · May 1, 20092 thanks

Ordering by RAND() is not a wise idea when you have a large table with lots of rows. Your database will have to calculate a different random value for every row in your database -- O(N) -- then sort the entire table by those values -- O(N log N).

There are a number of better ways to get a random record from your table. Some examples:

  • If your table is not sparse, choose a random ID and get that row (or the nearest row):

rand_id = rand(Model.count) rand_record = Model.first(:conditions => [ "id >= ?", rand_id]) # don't use OFFSET on MySQL; it's very slow

  • If your table is sparse, or does not have a primary key, consider adding an indexed column of random numbers between 0 and N. You can then order by this column quickly and choose a value using a method similar to the above example.

the :order parameter is not sanitized

bradwerth · Feb 5, 20102 thanks

The :order parameter is not sanitized, so doing something like Person.find( :first , :order => params[:order] ) could get you in trouble.

Using .map(&:item)

GavinLaking · Jul 23, 20081 thank

You can only use .map(&:item) with find(:all, not find(:first. For example; the first works, but the second does not.

@person = Person.find(:all, :conditions => {
:id => @person.id}, :select => "name").map(&:name)

@person = Person.find(:first, :conditions => {
:id => @person.id}, :select => "name").map(&:name)

:select multiple fields

csbartus · Nov 16, 20081 thank

it might be obvious or not:

Task.find :all, :select => "name, members"

Find random record

Oleg · Apr 30, 20091 thank

It's as simple as: Things.first(:order => 'RAND()')

Of course depending on your database it could be 'RANDOM()' or something similar.

re: Specifying :include no longer necessarily joins the association

james · Nov 4, 2008

I have seen how :include does not nessisarily perform a join during that SQL query, if you need the join to occur then, rather then tricking AR ("forcing"), use :joins instead of :include to ensure the joins occur.

Re: Find random record

sfusion · May 2, 2009

How about if you wanted to find a random set of records instead of a singular record, what would be the best way?

Thank you

Paginating grouped records

jamesconroyfinn · Jul 29, 2009

If you are grouping similar records and paginating you might need to use :group You'll want to :select only the field you're collapsing on probably.

Model.count(:select => :attribute, :group => :attribute)

This will return an OrderedHash of your attributes with a count for each.

{"Column Content" => 6, "Another Column's Content" => 8}

You'll need a second query to pull all of your records out.

Fetching records when column is set to nil or false

morgoth · Apr 1, 2010

If you want to fetch all records when one column (boolean) is set to nil or false, try this:

Project.all(:conditions => "archived IS NULL OR archived = 'f'")

Find a random *set* of records (without killing the db)

taryneast · Mar 22, 2011

If you want to find any number of records without sorting your entire table randomly every time, try the solution I posted here:

http://rubyglasses.blogspot.com/2010/05/activerecord-find-in-random-order.html

Upgrading to 3.x

lulalala · Oct 18, 2011

http://railscasts.com/episodes/202-active-record-queries-in-rails-3

Since this is deprecated, one can watch the Railcast for upgrading to 3.x

The equivalent is the ActiveRecord finder methods. http://apidock.com/rails/ActiveRecord/Fixture/find