aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorJeremy Kemper <jeremy@bitsweat.net>2009-05-11 11:43:30 -0700
committerJeremy Kemper <jeremy@bitsweat.net>2009-05-11 11:43:30 -0700
commitd45d0a18647e906223b2859d1ce2a83be00095b1 (patch)
treefb7c8afe4cf0c368da13893dfef338e46d988a66 /activerecord
parent4817bf94d135c44ddfae1a30acb15de989e3c86c (diff)
parent4932f7b38f72104819022abca0c952ba6f9888cb (diff)
downloadrails-d45d0a18647e906223b2859d1ce2a83be00095b1.tar.gz
rails-d45d0a18647e906223b2859d1ce2a83be00095b1.tar.bz2
rails-d45d0a18647e906223b2859d1ce2a83be00095b1.zip
Merge branch 'master' of git@github.com:rails/rails
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record/association_preload.rb4
-rwxr-xr-xactiverecord/lib/active_record/associations.rb30
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb11
-rw-r--r--activerecord/test/cases/associations/eager_test.rb12
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb61
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb18
-rw-r--r--activerecord/test/models/pirate.rb2
-rw-r--r--activerecord/test/schema/schema.rb1
8 files changed, 130 insertions, 9 deletions
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index e4ab69aa1b..967fff4d6f 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -126,6 +126,7 @@ module ActiveRecord
association_proxy = parent_record.send(reflection_name)
association_proxy.loaded
association_proxy.target.push(*[associated_record].flatten)
+ association_proxy.__send__(:set_inverse_instance, associated_record, parent_record)
end
end
@@ -152,7 +153,8 @@ module ActiveRecord
seen_keys[associated_record[key].to_s] = true
mapped_records = id_to_record_map[associated_record[key].to_s]
mapped_records.each do |mapped_record|
- mapped_record.send("set_#{reflection_name}_target", associated_record)
+ association_proxy = mapped_record.send("set_#{reflection_name}_target", associated_record)
+ association_proxy.__send__(:set_inverse_instance, associated_record, mapped_record)
end
end
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 0952b087d1..781a0290e8 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1671,17 +1671,29 @@ module ActiveRecord
string.scan(/([\.a-zA-Z_]+).?\./).flatten
end
+ def tables_in_hash(hash)
+ return [] if hash.blank?
+ tables = hash.map do |key, value|
+ if value.is_a?(Hash)
+ key.to_s
+ else
+ tables_in_string(key) if key.is_a?(String)
+ end
+ end
+ tables.flatten.compact
+ end
+
def conditions_tables(options)
# look in both sets of conditions
conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
case cond
when nil then all
- when Array then all << cond.first
- when Hash then all << cond.keys
- else all << cond
+ when Array then all << tables_in_string(cond.first)
+ when Hash then all << tables_in_hash(cond)
+ else all << tables_in_string(cond)
end
end
- tables_in_string(conditions.join(' '))
+ conditions.flatten
end
def order_tables(options)
@@ -1916,21 +1928,27 @@ module ActiveRecord
return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
association = join.instantiate(row)
collection.target.push(association)
+ collection.__send__(:set_inverse_instance, association, record)
when :has_one
return if record.id.to_s != join.parent.record_id(row).to_s
return if record.instance_variable_defined?("@#{join.reflection.name}")
association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
- record.send("set_#{join.reflection.name}_target", association)
+ set_target_and_inverse(join, association, record)
when :belongs_to
return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
association = join.instantiate(row)
- record.send("set_#{join.reflection.name}_target", association)
+ set_target_and_inverse(join, association, record)
else
raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
end
return association
end
+ def set_target_and_inverse(join, association, record)
+ association_proxy = record.send("set_#{join.reflection.name}_target", association)
+ association_proxy.__send__(:set_inverse_instance, association, record)
+ end
+
class JoinBase # :nodoc:
attr_reader :active_record, :table_joins
delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index e3122d195a..dfad2901c5 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -180,10 +180,14 @@ module ActiveRecord
# and the Proc should return either +true+ or +false+. When no Proc
# is specified a record will be built for all attribute hashes that
# do not have a <tt>_delete</tt> that evaluates to true.
+ # Passing <tt>:all_blank</tt> instead of a Proc will create a proc
+ # that will reject a record where all the attributes are blank.
#
# Examples:
# # creates avatar_attributes=
# accepts_nested_attributes_for :avatar, :reject_if => proc { |attributes| attributes['name'].blank? }
+ # # creates avatar_attributes=
+ # accepts_nested_attributes_for :avatar, :reject_if => :all_blank
# # creates avatar_attributes= and posts_attributes=
# accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true
def accepts_nested_attributes_for(*attr_names)
@@ -201,7 +205,12 @@ module ActiveRecord
end
reflection.options[:autosave] = true
- self.reject_new_nested_attributes_procs[association_name.to_sym] = options[:reject_if]
+
+ self.reject_new_nested_attributes_procs[association_name.to_sym] = if options[:reject_if] == :all_blank
+ proc { |attributes| attributes.all? {|k,v| v.blank?} }
+ else
+ options[:reject_if]
+ end
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, false)
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 40723814c5..d23f86b700 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -223,6 +223,18 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_eager_association_loading_with_belongs_to_and_conditions_hash
+ comments = []
+ assert_nothing_raised do
+ comments = Comment.find(:all, :include => :post, :conditions => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id')
+ end
+ assert_equal 3, comments.length
+ assert_equal [5,6,7], comments.collect { |c| c.id }
+ assert_no_queries do
+ comments.first.post
+ end
+ end
+
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name
quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id')
assert_nothing_raised do
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 616f8dfbbe..47f83db112 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -94,6 +94,25 @@ class InverseHasOneTests < ActiveRecord::TestCase
assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
end
+
+ def test_parent_instance_should_be_shared_with_eager_loaded_child_on_find
+ m = Man.find(:first, :include => :face)
+ f = m.face
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
+ f.man.name = 'Mungo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
+
+ m = Man.find(:first, :include => :face, :order => 'faces.id')
+ f = m.face
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
+ f.man.name = 'Mungo'
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
+ end
+
def test_parent_instance_should_be_shared_with_newly_built_child
m = Man.find(:first)
f = m.build_face(:description => 'haunted')
@@ -136,6 +155,29 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
end
+ def test_parent_instance_should_be_shared_with_eager_loaded_children
+ m = Man.find(:first, :include => :interests)
+ is = m.interests
+ is.each do |i|
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
+ i.man.name = 'Mungo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
+ end
+
+ m = Man.find(:first, :include => :interests, :order => 'interests.id')
+ is = m.interests
+ is.each do |i|
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
+ m.name = 'Bongo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
+ i.man.name = 'Mungo'
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
+ end
+
+ end
+
def test_parent_instance_should_be_shared_with_newly_built_child
m = Man.find(:first)
i = m.interests.build(:topic => 'Industrial Revolution Re-enactment')
@@ -188,6 +230,25 @@ class InverseBelongsToTests < ActiveRecord::TestCase
assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
end
+ def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find
+ f = Face.find(:first, :include => :man)
+ m = f.man
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
+ f.description = 'gormless'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
+ m.face.description = 'pleasing'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
+
+
+ f = Face.find(:first, :include => :man, :order => 'men.id')
+ m = f.man
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
+ f.description = 'gormless'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
+ m.face.description = 'pleasing'
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
+ end
+
def test_child_instance_should_be_shared_with_newly_built_parent
f = Face.find(:first)
m = f.build_man(:name => 'Charles')
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index cd6277c24b..f1741ed54d 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -31,11 +31,27 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
end
def test_should_add_a_proc_to_reject_new_nested_attributes_procs
- [:parrots, :birds].each do |name|
+ [:parrots, :birds, :birds_with_reject_all_blank].each do |name|
assert_instance_of Proc, Pirate.reject_new_nested_attributes_procs[name]
end
end
+ def test_should_not_build_a_new_record_if_reject_all_blank_returns_false
+ pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
+ pirate.birds_with_reject_all_blank_attributes = [{:name => '', :color => ''}]
+ pirate.save!
+
+ assert pirate.birds_with_reject_all_blank.empty?
+ end
+
+ def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false
+ pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
+ pirate.birds_with_reject_all_blank_attributes = [{:name => 'Tweetie', :color => ''}]
+ pirate.save!
+
+ assert_equal 1, pirate.birds_with_reject_all_blank.count
+ end
+
def test_should_raise_an_ArgumentError_for_non_existing_associations
assert_raise_with_message ArgumentError, "No association found for name `honesty'. Has it been defined yet?" do
Pirate.accepts_nested_attributes_for :honesty
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 238917bf30..acf53fce8b 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -28,11 +28,13 @@ class Pirate < ActiveRecord::Base
:after_add => proc {|p,b| p.ship_log << "after_adding_proc_bird_#{b.id || '<new>'}"},
:before_remove => proc {|p,b| p.ship_log << "before_removing_proc_bird_#{b.id}"},
:after_remove => proc {|p,b| p.ship_log << "after_removing_proc_bird_#{b.id}"}
+ has_many :birds_with_reject_all_blank, :class_name => "Bird"
accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? }
accepts_nested_attributes_for :parrots_with_method_callbacks, :parrots_with_proc_callbacks,
:birds_with_method_callbacks, :birds_with_proc_callbacks, :allow_destroy => true
+ accepts_nested_attributes_for :birds_with_reject_all_blank, :reject_if => :all_blank
validates_presence_of :catchphrase
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 3b0e17c867..6fb918c60e 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -57,6 +57,7 @@ ActiveRecord::Schema.define do
create_table :birds, :force => true do |t|
t.string :name
+ t.string :color
t.integer :pirate_id
end