Created
February 20, 2014 15:24
-
-
Save mauricioszabo/9116205 to your computer and use it in GitHub Desktop.
ActiveRecord's query methods
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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