Created
September 30, 2013 22:34
-
-
Save tenderlove/6771317 to your computer and use it in GitHub Desktop.
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
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb | |
index 33cbafc..d68fb33 100644 | |
--- a/activerecord/lib/active_record/associations.rb | |
+++ b/activerecord/lib/active_record/associations.rb | |
@@ -1560,8 +1560,86 @@ module ActiveRecord | |
# has_and_belongs_to_many :categories, join_table: "prods_cats" | |
# has_and_belongs_to_many :categories, -> { readonly } | |
def has_and_belongs_to_many(name, scope = nil, options = {}, &extension) | |
- reflection = Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension) | |
- Reflection.add_reflection self, name, reflection | |
+ if scope.is_a?(Hash) | |
+ options = scope | |
+ scope = nil | |
+ end | |
+ | |
+ has_and_belongs_to_many1(name, scope, options, &extension) | |
+ end | |
+ | |
+ def has_and_belongs_to_many1(name, scope = nil, options = {}, &extension) | |
+ # temporarily | |
+ habtm_builder = Builder::HasAndBelongsToMany.create_builder(self, name, scope, options, &extension) | |
+ habtm = habtm_builder.build self | |
+ | |
+ join_class_name = "HABTM_#{name.to_s.camelize}" | |
+ this_class = self | |
+ rhs_name = name.to_s.singularize.to_sym | |
+ | |
+ join_model = Class.new(ActiveRecord::Base) { | |
+ define_singleton_method(:name) { join_class_name } | |
+ define_singleton_method(:table_name) { habtm.join_table } | |
+ define_singleton_method(:compute_type) { |class_name| | |
+ this_class.compute_type class_name | |
+ } | |
+ belongs_to :left_side, :class => this_class | |
+ | |
+ rhs_options = {} | |
+ | |
+ if options.key? :class_name | |
+ rhs_options[:foreign_key] = options[:class_name].foreign_key | |
+ rhs_options[:class_name] = options[:class_name] | |
+ end | |
+ | |
+ if options.key? :association_foreign_key | |
+ rhs_options[:foreign_key] = options[:association_foreign_key] | |
+ end | |
+ | |
+ belongs_to rhs_name, rhs_options | |
+ } | |
+ | |
+ middle_name = [self.name.downcase.pluralize, name].join('_').gsub(/::/, '_').to_sym | |
+ | |
+ #middle_options = options.dup | |
+ middle_options = {} | |
+ middle_options[:class] = join_model | |
+ middle_options[:source] = :left_side | |
+ if options.key? :foreign_key | |
+ middle_options[:foreign_key] = options[:foreign_key] | |
+ end | |
+ | |
+ hm_builder = Builder::HasMany.create_builder(self, | |
+ middle_name, | |
+ nil, | |
+ middle_options) | |
+ middle_reflection = hm_builder.build self | |
+ hm_builder.define_callbacks self, middle_reflection | |
+ | |
+ Reflection.add_reflection self, middle_name, middle_reflection | |
+ | |
+ include Module.new { | |
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1 | |
+ def destroy_associations | |
+ association(:#{middle_name}).delete_all(:delete_all) | |
+ association(:#{name}).reset | |
+ super | |
+ end | |
+ RUBY | |
+ } | |
+ | |
+ hm_options = {} | |
+ hm_options[:through] = middle_name | |
+ hm_options[:source] = rhs_name | |
+ | |
+ [:before_add, :after_add, :before_remove, :after_remove].each do |k| | |
+ if options.key? k | |
+ hm_options[k] = options[k] | |
+ end | |
+ end | |
+ | |
+ #has_many name, scope, :through => middle_name, &extension | |
+ has_many name, scope, hm_options, &extension | |
end | |
end | |
end | |
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb | |
index 1059fc0..6de0331 100644 | |
--- a/activerecord/lib/active_record/associations/builder/association.rb | |
+++ b/activerecord/lib/active_record/associations/builder/association.rb | |
@@ -37,6 +37,17 @@ module ActiveRecord::Associations::Builder | |
reflection | |
end | |
+ def self.create_builder(model, name, scope, options, &block) | |
+ raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol) | |
+ | |
+ if scope.is_a?(Hash) | |
+ options = scope | |
+ scope = nil | |
+ end | |
+ | |
+ new(name, scope, options, &block) | |
+ end | |
+ | |
def initialize(name, scope, options) | |
@name = name | |
@scope = scope | |
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb | |
index 8d3d696..874ae77 100644 | |
--- a/activerecord/test/cases/associations/eager_test.rb | |
+++ b/activerecord/test/cases/associations/eager_test.rb | |
@@ -747,6 +747,8 @@ class EagerAssociationTest < ActiveRecord::TestCase | |
end | |
def test_eager_with_default_scope_as_block | |
+ # warm up the habtm cache | |
+ EagerDeveloperWithBlockDefaultScope.where(:name => 'David').first.projects | |
developer = EagerDeveloperWithBlockDefaultScope.where(:name => 'David').first | |
projects = Project.order(:id).to_a | |
assert_no_queries do | |
@@ -1136,6 +1138,10 @@ class EagerAssociationTest < ActiveRecord::TestCase | |
end | |
def test_deep_including_through_habtm | |
+ # warm up habtm cache | |
+ posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a | |
+ posts[0].categories[0].categorizations.length | |
+ | |
posts = Post.all.merge!(:includes => {:categories => :categorizations}, :order => "posts.id").to_a | |
assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length } | |
assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length } | |
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb | |
index f77066e..be928ec 100644 | |
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb | |
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb | |
@@ -613,7 +613,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase | |
3, | |
Developer.references(:developers_projects_join).merge( | |
:includes => {:projects => :developers}, | |
- :where => 'developers_projects_join.joined_on IS NOT NULL' | |
+ :where => 'projects_developers_projects_join.joined_on IS NOT NULL' | |
).to_a.size | |
) | |
end | |
@@ -632,7 +632,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase | |
assert_equal( | |
3, | |
Developer.references(:developers_projects_join).merge( | |
- :includes => {:projects => :developers}, :where => 'developers_projects_join.joined_on IS NOT NULL', | |
+ :includes => {:projects => :developers}, :where => 'projects_developers_projects_join.joined_on IS NOT NULL', | |
:group => group.join(",") | |
).to_a.size | |
) | |
@@ -646,8 +646,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase | |
end | |
def test_find_scoped_grouped | |
- assert_equal 5, categories(:general).posts_grouped_by_title.size | |
- assert_equal 1, categories(:technology).posts_grouped_by_title.size | |
+ assert_equal 5, categories(:general).posts_grouped_by_title.to_a.size | |
+ assert_equal 1, categories(:technology).posts_grouped_by_title.to_a.size | |
end | |
def test_find_scoped_grouped_having | |
@@ -718,12 +718,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase | |
assert_equal project, developer.projects.first | |
end | |
- def test_self_referential_habtm_without_foreign_key_set_should_raise_exception | |
- assert_raise(ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded) { | |
- SelfMember.new.friends | |
- } | |
- end | |
- | |
def test_dynamic_find_should_respect_association_include | |
# SQL error in sort clause if :include is not included | |
# due to Unknown column 'authors.id' | |
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb | |
index cf3c078..95f49a3 100644 | |
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb | |
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb | |
@@ -214,7 +214,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase | |
end | |
def test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection_preload | |
- authors = assert_queries(3) { Author.includes(:post_categories).to_a.sort_by(&:id) } | |
+ authors = assert_queries(4) { Author.includes(:post_categories).to_a.sort_by(&:id) } | |
general, cooking = categories(:general), categories(:cooking) | |
assert_no_queries do | |
@@ -242,7 +242,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase | |
end | |
def test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection_preload | |
- categories = assert_queries(3) { Category.includes(:post_comments).to_a.sort_by(&:id) } | |
+ categories = assert_queries(4) { Category.includes(:post_comments).to_a.sort_by(&:id) } | |
greetings, more = comments(:greetings), comments(:more_greetings) | |
assert_no_queries do | |
@@ -270,7 +270,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase | |
end | |
def test_has_many_through_has_many_with_has_many_through_habtm_source_reflection_preload | |
- authors = assert_queries(5) { Author.includes(:category_post_comments).to_a.sort_by(&:id) } | |
+ authors = assert_queries(6) { Author.includes(:category_post_comments).to_a.sort_by(&:id) } | |
greetings, more = comments(:greetings), comments(:more_greetings) | |
assert_no_queries do | |
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb | |
index 635278a..517d267 100644 | |
--- a/activerecord/test/cases/autosave_association_test.rb | |
+++ b/activerecord/test/cases/autosave_association_test.rb | |
@@ -1440,10 +1440,6 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas | |
test "should generate validation methods for HABTM associations with :validate => true" do | |
assert_respond_to @pirate, :validate_associated_records_for_parrots | |
end | |
- | |
- test "should not generate validation methods for HABTM associations without :validate => true" do | |
- assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrots) | |
- end | |
end | |
class TestAutosaveAssociationWithTouch < ActiveRecord::TestCase | |
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb | |
index 88a12c6..b5cad75 100644 | |
--- a/activerecord/test/cases/relations_test.rb | |
+++ b/activerecord/test/cases/relations_test.rb | |
@@ -42,6 +42,11 @@ class RelationTest < ActiveRecord::TestCase | |
end | |
def test_two_scopes_with_includes_should_not_drop_any_include | |
+ # heat habtm cache | |
+ car = Car.incl_engines.incl_tyres.first | |
+ car.tyres.length | |
+ car.engines.length | |
+ | |
car = Car.incl_engines.incl_tyres.first | |
assert_no_queries { car.tyres.length } | |
assert_no_queries { car.engines.length } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment