Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of git@github.com:rails/rails

  • Loading branch information...
commit 248391a7e11e9fa5ffe312cb380034691430acc5 2 parents 86abd68 + a9422cc
@jeremy jeremy authored
View
16 activerecord/lib/active_record/association_preload.rb
@@ -204,9 +204,18 @@ def preload_has_one_association(records, reflection, preload_options={})
unless through_records.empty?
source = reflection.source_reflection.name
through_records.first.class.preload_associations(through_records, source)
- through_records.each do |through_record|
- add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
- reflection.name, through_record.send(source))
+ if through_reflection.macro == :belongs_to
+ rev_id_to_record_map, rev_ids = construct_id_map(records, through_primary_key)
+ rev_primary_key = through_reflection.klass.primary_key
+ through_records.each do |through_record|
+ add_preloaded_record_to_collection(rev_id_to_record_map[through_record[rev_primary_key].to_s],
+ reflection.name, through_record.send(source))
+ end
+ else
+ through_records.each do |through_record|
+ add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
+ reflection.name, through_record.send(source))
+ end
end
end
else
@@ -307,6 +316,7 @@ def preload_belongs_to_association(records, reflection, preload_options={})
klasses_and_ids.each do |klass_and_id|
klass_name, id_map = *klass_and_id
+ next if id_map.empty?
klass = klass_name.constantize
table_name = klass.quoted_table_name
View
43 activerecord/lib/active_record/associations.rb
@@ -1731,6 +1731,11 @@ def construct_finder_sql_for_association_limiting(options, join_dependency)
return sanitize_sql(sql)
end
+ def tables_in_string(string)
+ return [] if string.blank?
+ string.scan(/([\.a-zA-Z_]+).?\./).flatten
+ end
+
def conditions_tables(options)
# look in both sets of conditions
conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
@@ -1741,37 +1746,55 @@ def conditions_tables(options)
else all << cond
end
end
- conditions.join(' ').scan(/([\.a-zA-Z_]+).?\./).flatten
+ tables_in_string(conditions.join(' '))
end
def order_tables(options)
order = [options[:order], scope(:find, :order) ].join(", ")
return [] unless order && order.is_a?(String)
- order.scan(/([\.a-zA-Z_]+).?\./).flatten
+ tables_in_string(order)
end
def selects_tables(options)
select = options[:select]
return [] unless select && select.is_a?(String)
- select.scan(/"?([\.a-zA-Z_]+)"?.?\./).flatten
+ tables_in_string(select)
+ end
+
+ def joined_tables(options)
+ scope = scope(:find)
+ joins = options[:joins]
+ merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
+ [table_name] + case merged_joins
+ when Symbol, Hash, Array
+ if array_of_strings?(merged_joins)
+ tables_in_string(merged_joins.join(' '))
+ else
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
+ join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
+ end
+ else
+ tables_in_string(merged_joins)
+ end
end
# Checks if the conditions reference a table other than the current model table
- def include_eager_conditions?(options, tables = nil)
- ((tables || conditions_tables(options)) - [table_name]).any?
+ def include_eager_conditions?(options, tables = nil, joined_tables = nil)
+ ((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any?
end
# Checks if the query order references a table other than the current model's table.
- def include_eager_order?(options, tables = nil)
- ((tables || order_tables(options)) - [table_name]).any?
+ def include_eager_order?(options, tables = nil, joined_tables = nil)
+ ((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any?
end
- def include_eager_select?(options)
- (selects_tables(options) - [table_name]).any?
+ def include_eager_select?(options, joined_tables = nil)
+ (selects_tables(options) - (joined_tables || joined_tables(options))).any?
end
def references_eager_loaded_tables?(options)
- include_eager_order?(options) || include_eager_conditions?(options) || include_eager_select?(options)
+ joined_tables = joined_tables(options)
+ include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables)
end
def using_limitable_reflections?(reflections)
View
6 activerecord/lib/active_record/associations/association_collection.rb
@@ -83,7 +83,11 @@ def last(*args)
def to_ary
load_target
- @target.to_ary
+ if @target.is_a?(Array)
+ @target.to_ary
+ else
+ Array(@target)
+ end
end
def reset
View
2  activerecord/lib/active_record/base.rb
@@ -1829,7 +1829,7 @@ def self.#{method_id}(*args)
else
find(:#{finder}, options.merge(finder_options))
end
- #{'result || raise(RecordNotFound)' if bang}
+ #{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
end
}, __FILE__, __LINE__
send(method_id, *arguments)
View
17 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -308,6 +308,7 @@ def select_rows(sql, name = nil)
rows
end
+ # Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it.
def execute(sql, name = nil) #:nodoc:
log(sql, name) { @connection.query(sql) }
rescue ActiveRecord::StatementInvalid => exception
@@ -414,7 +415,9 @@ def collation
def tables(name = nil) #:nodoc:
tables = []
- execute("SHOW TABLES", name).each { |field| tables << field[0] }
+ result = execute("SHOW TABLES", name)
+ result.each { |field| tables << field[0] }
+ result.free
tables
end
@@ -425,7 +428,8 @@ def drop_table(table_name, options = {})
def indexes(table_name, name = nil)#:nodoc:
indexes = []
current_index = nil
- execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
+ result.each do |row|
if current_index != row[2]
next if row[2] == "PRIMARY" # skip the primary key
current_index = row[2]
@@ -434,13 +438,16 @@ def indexes(table_name, name = nil)#:nodoc:
indexes.last.columns << row[4]
end
+ result.free
indexes
end
def columns(table_name, name = nil)#:nodoc:
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
columns = []
- execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
+ result = execute(sql, name)
+ result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
+ result.free
columns
end
@@ -521,9 +528,11 @@ def show_variable(name)
# Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table) #:nodoc:
keys = []
- execute("describe #{quote_table_name(table)}").each_hash do |h|
+ result = execute("describe #{quote_table_name(table)}")
+ result.each_hash do |h|
keys << h["Field"]if h["Key"] == "PRI"
end
+ result.free
keys.length == 1 ? [keys.first, nil] : nil
end
View
68 activerecord/test/cases/associations/eager_test.rb
@@ -1,6 +1,7 @@
require "cases/helper"
require 'models/post'
require 'models/tagging'
+require 'models/tag'
require 'models/comment'
require 'models/author'
require 'models/category'
@@ -145,7 +146,7 @@ def test_finding_with_includes_on_belongs_to_association_with_same_include_inclu
def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once
post = posts(:welcome)
post.update_attributes!(:author => nil)
- post = assert_queries(2) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the address
+ post = assert_queries(1) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the author or address
assert_no_queries do
assert_equal nil, post.author_with_address
end
@@ -705,4 +706,69 @@ def test_conditions_on_join_table_with_include_and_limit
def test_order_on_join_table_with_include_and_limit
assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
end
+
+ def test_eager_loading_with_order_on_joined_table_preloads
+ posts = assert_queries(2) do
+ Post.find(:all, :joins => :comments, :include => :author, :order => 'comments.id DESC')
+ end
+ assert_equal posts(:eager_other), posts[0]
+ assert_equal authors(:mary), assert_no_queries { posts[0].author}
+ end
+
+ def test_eager_loading_with_conditions_on_joined_table_preloads
+ posts = assert_queries(2) do
+ Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ end
+ assert_equal [posts(:welcome)], posts
+ assert_equal authors(:david), assert_no_queries { posts[0].author}
+
+ posts = assert_queries(2) do
+ Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ end
+ assert_equal [posts(:welcome)], posts
+ assert_equal authors(:david), assert_no_queries { posts[0].author}
+
+ posts = assert_queries(2) do
+ Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'")
+ end
+ assert_equal posts(:welcome, :thinking), posts
+
+ posts = assert_queries(2) do
+ Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2")
+ end
+ assert_equal posts(:welcome, :thinking), posts
+
+ end
+
+ def test_eager_loading_with_conditions_on_string_joined_table_preloads
+ posts = assert_queries(2) do
+ Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ end
+ assert_equal [posts(:welcome)], posts
+ assert_equal authors(:david), assert_no_queries { posts[0].author}
+
+ posts = assert_queries(2) do
+ Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ end
+ assert_equal [posts(:welcome)], posts
+ assert_equal authors(:david), assert_no_queries { posts[0].author}
+
+ end
+
+ def test_eager_loading_with_select_on_joined_table_preloads
+ posts = assert_queries(2) do
+ Post.find(:all, :select => 'posts.*, authors.name as author_name', :include => :comments, :joins => :author, :order => 'posts.id')
+ end
+ assert_equal 'David', posts[0].author_name
+ assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments}
+ end
+
+ def test_eager_loading_with_conditions_on_join_model_preloads
+ authors = assert_queries(2) do
+ Author.find(:all, :include => :author_address, :joins => :comments, :conditions => "posts.title like 'Welcome%'")
+ end
+ assert_equal authors(:david), authors[0]
+ assert_equal author_addresses(:david_address), authors[0].author_address
+ end
+
end
View
3  activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -3,6 +3,9 @@
require 'models/person'
require 'models/reader'
require 'models/comment'
+require 'models/tag'
+require 'models/tagging'
+require 'models/author'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors
View
17 activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/club'
+require 'models/member_type'
require 'models/member'
require 'models/membership'
require 'models/sponsor'
@@ -7,7 +8,7 @@
require 'models/member_detail'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :members, :clubs, :memberships, :sponsors, :organizations
+ fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations
def setup
@member = members(:groucho)
@@ -158,4 +159,18 @@ def test_reassigning_has_one_through
assert @new_organization.members.include?(@member)
end
+ def test_preloading_has_one_through_on_belongs_to
+ assert_not_nil @member.member_type
+ @organization = organizations(:nsa)
+ @member_detail = MemberDetail.new
+ @member.member_detail = @member_detail
+ @member.organization = @organization
+ @member_details = assert_queries(3) do
+ MemberDetail.find(:all, :include => :member_type)
+ end
+ @new_detail = @member_details[0]
+ assert @new_detail.loaded_member_type?
+ assert_not_nil assert_no_queries { @new_detail.member_type }
+ end
+
end
View
6 activerecord/test/fixtures/member_types.yml
@@ -0,0 +1,6 @@
+founding:
+ id: 1
+ name: Founding
+provisional:
+ id: 2
+ name: Provisional
View
4 activerecord/test/fixtures/members.yml
@@ -1,4 +1,6 @@
groucho:
name: Groucho Marx
+ member_type_id: 1
some_other_guy:
- name: Englebert Humperdink
+ name: Englebert Humperdink
+ member_type_id: 2
View
1  activerecord/test/models/member.rb
@@ -8,4 +8,5 @@ class Member < ActiveRecord::Base
has_one :sponsor_club, :through => :sponsor
has_one :member_detail
has_one :organization, :through => :member_detail
+ belongs_to :member_type
end
View
1  activerecord/test/models/member_detail.rb
@@ -1,4 +1,5 @@
class MemberDetail < ActiveRecord::Base
belongs_to :member
belongs_to :organization
+ has_one :member_type, :through => :member
end
View
3  activerecord/test/models/member_type.rb
@@ -0,0 +1,3 @@
+class MemberType < ActiveRecord::Base
+ has_many :members
+end
View
5 activerecord/test/schema/schema.rb
@@ -195,6 +195,7 @@ def create_table(*args, &block)
create_table :members, :force => true do |t|
t.string :name
+ t.integer :member_type_id
end
create_table :member_details, :force => true do |t|
@@ -210,6 +211,10 @@ def create_table(*args, &block)
t.string :type
end
+ create_table :member_types, :force => true do |t|
+ t.string :name
+ end
+
create_table :references, :force => true do |t|
t.integer :person_id
t.integer :job_id
Please sign in to comment.
Something went wrong with that request. Please try again.