166 | Chapter 6: Performance
The problem withfind_by_sqlis that it instantiates every object that is returned.
This is usually fine, but sometimes it can be too much overhead. To avoid this, you
may need to bypass ActiveRecord and talk directly with the connection adapter. This
is fairly easy, but you can make it easier by bolting some convenience methods onto
ActiveRecord to sanitize the query automatically:
class <<ActiveRecord::Base
def select_values(sql)
connection.select_values(sanitize_sql(sql))
end
end
sql = %(SELECT id FROM people WHERE last_name = ?)
last_name = %(O'Reilly)
Person.select_values [sql, last_name] # => ["12", "42"]
Because ActiveRecord is not doing any of the work here, the values come across
without any type conversion (as strings here). The complete list of methods available
through the connection adapter (theActiveRecord::Base.connectionobject) is listed
in the RDoc forActiveRecord::ConnectionAdapters::DatabaseStatements.
1+N Problem
The so-called1+N problemis characteristic of the problems that you can run into if
you are not aware of your tools. It is best illustrated with an example.
Assume that we are using the following model, which splits off a person’s login into
a separateUser model:*
class Person < ActiveRecord::Base
has_one :user
end
class User < ActiveRecord::Base
belongs_to :person
end
If you want to find the usernames of all people from Illinois, you might write this code:
logins_from_illinois = []
Person.find_all_by_state('IL').each do |person|
logins_from_illinois << person.user.login
end
Not only is this iterative code bad style in Ruby, it can be inefficient. Even the func-
tional version suffers from the same problem:
logins_from_illinois = Person.find_all_by_state('IL').
map{|p| p.user.login}
- This style can be very useful, especially when you need to keep track of many people who may or may not
actually be able to log in to a Rails application.