From 428e77bf0fcee4369cb8d94011141f791b8e2ba9 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Wed, 17 Dec 2008 23:37:55 +0000 Subject: Make exceptions raise from find_by_foo! have a more helpful message [#1567 state:resolved] --- activerecord/lib/active_record/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 87c0002463..dfead0b94f 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1829,7 +1829,7 @@ module ActiveRecord #:nodoc: 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) -- cgit v1.2.3 From 707d0dd3e1e8df7771073670e4257d933d2818f9 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Wed, 17 Dec 2008 23:39:09 +0000 Subject: Fix preloading of belongs_to with null foreign key generating useless query [#1027 state:resolved] --- activerecord/lib/active_record/association_preload.rb | 1 + activerecord/test/cases/associations/eager_test.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index 99c3ce5e62..d8aa1051bd 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -307,6 +307,7 @@ module ActiveRecord 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 diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 3c8408d14b..2fd51ef03a 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -145,7 +145,7 @@ class EagerAssociationTest < ActiveRecord::TestCase 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 +705,5 @@ class EagerAssociationTest < ActiveRecord::TestCase 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 + end -- cgit v1.2.3 From 9cf6b1b15e6335134a5af5d9d6296f8d9242e005 Mon Sep 17 00:00:00 2001 From: Emilio Tagua Date: Tue, 2 Dec 2008 13:47:53 -0300 Subject: Add missing model files so tests can run isolated [#1506 state:resolved] Signed-off-by: Frederick Cheung --- .../test/cases/associations/has_many_through_associations_test.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index a07f4bcbdd..0bfda337b2 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -3,6 +3,9 @@ require 'models/post' 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 -- cgit v1.2.3 From c9ab7098be7bdd748c0f4a49c8ef015b4aad3108 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Thu, 18 Dec 2008 19:07:55 +0000 Subject: Ensure :include checks joins when determining if it can preload [#528 state:resolved] --- activerecord/lib/active_record/associations.rb | 43 ++++++++++---- activerecord/test/cases/associations/eager_test.rb | 65 ++++++++++++++++++++++ 2 files changed, 98 insertions(+), 10 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 3fbbea43ed..bc2a88ef7e 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1731,6 +1731,11 @@ module ActiveRecord 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 @@ module ActiveRecord 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) + [table_name] + 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) diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 2fd51ef03a..42063d18a3 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/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' @@ -706,4 +707,68 @@ class EagerAssociationTest < ActiveRecord::TestCase 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 -- cgit v1.2.3 From 9c7fe7c6729bbdde4a22f58e7279463092390330 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Thu, 18 Dec 2008 10:41:45 +0000 Subject: Don't include table_name twice --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index bc2a88ef7e..3165015f3e 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1771,7 +1771,7 @@ module ActiveRecord tables_in_string(merged_joins.join(' ')) else join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil) - [table_name] + join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact + 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) -- cgit v1.2.3 From 8326b95169ae6af3b81f5596107fef9db4bcbbb0 Mon Sep 17 00:00:00 2001 From: Manfred Stienstra Date: Wed, 19 Nov 2008 15:17:40 +0100 Subject: Free MySQL::Result objects after a call to execute [#1416 state:resolved] No freeing Result objects causes the MySQL driver to free result sets at undefined times, this can lead to erratic performance in your application. Signed-off-by: Frederick Cheung --- .../active_record/connection_adapters/mysql_adapter.rb | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c3cbdc8c2f..46d4b6c89c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -308,6 +308,7 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 @@ module ActiveRecord # 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 -- cgit v1.2.3 From a9422cc1db9501a80ecf2c25a5d3b0c4f4f32763 Mon Sep 17 00:00:00 2001 From: Matt Jones Date: Tue, 2 Dec 2008 16:21:21 -0500 Subject: Fix preloading of has_one :through associations on belongs_to [#1507 state:resolved] Signed-off-by: Frederick Cheung --- activerecord/lib/active_record/association_preload.rb | 15 ++++++++++++--- .../associations/association_collection.rb | 6 +++++- .../associations/has_one_through_associations_test.rb | 17 ++++++++++++++++- activerecord/test/fixtures/member_types.yml | 6 ++++++ activerecord/test/fixtures/members.yml | 4 +++- activerecord/test/models/member.rb | 1 + activerecord/test/models/member_detail.rb | 1 + activerecord/test/models/member_type.rb | 3 +++ activerecord/test/schema/schema.rb | 5 +++++ 9 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 activerecord/test/fixtures/member_types.yml create mode 100644 activerecord/test/models/member_type.rb diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index d8aa1051bd..7b1b2d9ad9 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -204,9 +204,18 @@ module ActiveRecord 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 diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 0ff91fbdf8..0fefec1216 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -83,7 +83,11 @@ module ActiveRecord def to_ary load_target - @target.to_ary + if @target.is_a?(Array) + @target.to_ary + else + Array(@target) + end end def reset diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 7d418de965..f65d76e2ce 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/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/organization' 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 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase 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 diff --git a/activerecord/test/fixtures/member_types.yml b/activerecord/test/fixtures/member_types.yml new file mode 100644 index 0000000000..797a57430c --- /dev/null +++ b/activerecord/test/fixtures/member_types.yml @@ -0,0 +1,6 @@ +founding: + id: 1 + name: Founding +provisional: + id: 2 + name: Provisional diff --git a/activerecord/test/fixtures/members.yml b/activerecord/test/fixtures/members.yml index 67a6cc459a..6db945e61d 100644 --- a/activerecord/test/fixtures/members.yml +++ b/activerecord/test/fixtures/members.yml @@ -1,4 +1,6 @@ groucho: name: Groucho Marx + member_type_id: 1 some_other_guy: - name: Englebert Humperdink \ No newline at end of file + name: Englebert Humperdink + member_type_id: 2 diff --git a/activerecord/test/models/member.rb b/activerecord/test/models/member.rb index 77a37abb38..255fb569d7 100644 --- a/activerecord/test/models/member.rb +++ b/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 \ No newline at end of file diff --git a/activerecord/test/models/member_detail.rb b/activerecord/test/models/member_detail.rb index e731454556..94f59e5794 100644 --- a/activerecord/test/models/member_detail.rb +++ b/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 diff --git a/activerecord/test/models/member_type.rb b/activerecord/test/models/member_type.rb new file mode 100644 index 0000000000..a13561c72a --- /dev/null +++ b/activerecord/test/models/member_type.rb @@ -0,0 +1,3 @@ +class MemberType < ActiveRecord::Base + has_many :members +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 6217e3bc1c..fbacc692b4 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -195,6 +195,7 @@ ActiveRecord::Schema.define do 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 @@ ActiveRecord::Schema.define do 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 -- cgit v1.2.3