Skip to content

Instantly share code, notes, and snippets.

@mauricioszabo
Created February 20, 2014 15:24
Show Gist options
  • Save mauricioszabo/9116205 to your computer and use it in GitHub Desktop.
Save mauricioszabo/9116205 to your computer and use it in GitHub Desktop.
ActiveRecord's query methods
# When you do this:
class Person < ActiveRecord::Base
end
people = Person.where(name: "Foo")
people.each do
# Do something with each row
end
# You're instantiating a relation, which includes this module:
module ActiveRecord
module Delegation
# Set up common delegations for performance (avoids method_missing)
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
#...
end
end
# to_a implementation of Relation:
logging_query_plan do
exec_queries
end
def exec_queries
return @records if loaded?
#... some scope magic...
eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
#... don't care about the rest
end
# Which falls into Querying#find_by_sql:
# collect! == 1 iteration into each record.
def find_by_sql(sql, binds = [])
logging_query_plan do
connection.select_all(sanitize_sql(sql), "#{name} Load", binds).collect! { |record| instantiate(record) }
end
end
def select_all(arel, name = nil, binds = [])
select(to_sql(arel, binds), name, binds)
end
# select is defined on database adapters, so we go there
def select(sql, name = nil, binds = [])
exec_query(sql, name, binds).to_a
end
def exec_query(sql, name = 'SQL', binds = [])
log(sql, name, binds) do
# caching logic..
result = binds.empty? ? exec_no_cache(sql, binds) :
exec_cache(sql, binds)
ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
result.clear
return ret
end
# converting results to array
# each == 1 iteration into each record.
def result_as_array(res) #:nodoc:
#...fields conversion...
rows.each do |row|
#...more conversion work...
end
end
# We must remember that #select called exec_query(...).to_a
# and exec_query returned an ActiveRecord::Result. So:
module ActiveRecord
class Result
include Enumerable
def each
hash_rows.each { |row| yield row }
end
private
def hash_rows
@hash_rows ||=
begin
columns = @columns.map { |c| c.dup.freeze }
@rows.map { |row|
Hash[columns.zip(row)]
}
end
end
end
end
# 1 another iteration into each record.
# counting with the #each we've done in the beginning: 4 iterations
# So, down to UP: we query the database
result = exec_no_cache(sql, binds)
# Transform this result into an Array:
rows.each #...
# Creating a structure like: [ [1, 'Foo', 'Bar'], [2, 'Foo', 'Baz'] ]
# then we transform this array into a hash:
hash_rows.each { |row| yield row }
# Creating a structure like: [ {id: 1, name: 'Foo', surname: 'Bar'}, {id: 2, ...} ]
# as we're calling "to_a", we're not iterating into it: in fact, we are creating
# this object in memory. After that, select_all iterates into each object:
connection.select_all(sanitize_sql(sql), "#{name} Load", binds).collect! { |record| instantiate(record) }
# iterating into it a third time, this time instantiating a bunch of
# Person into an array. After that, we iterate a fourth time:
people.each do
#...whathever...
end
# to really process our data.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment