activerecord 2.3.5 → 2.3.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (90) hide show
  1. data/CHANGELOG +33 -0
  2. data/Rakefile +1 -1
  3. data/examples/performance.sql +85 -0
  4. data/lib/active_record.rb +1 -2
  5. data/lib/active_record/association_preload.rb +9 -2
  6. data/lib/active_record/associations.rb +48 -38
  7. data/lib/active_record/associations/association_collection.rb +15 -11
  8. data/lib/active_record/associations/association_proxy.rb +16 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +11 -1
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -10
  11. data/lib/active_record/associations/has_many_association.rb +5 -0
  12. data/lib/active_record/associations/has_many_through_association.rb +5 -5
  13. data/lib/active_record/associations/has_one_association.rb +10 -1
  14. data/lib/active_record/attribute_methods.rb +5 -1
  15. data/lib/active_record/autosave_association.rb +66 -35
  16. data/lib/active_record/base.rb +77 -36
  17. data/lib/active_record/batches.rb +13 -9
  18. data/lib/active_record/calculations.rb +6 -3
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -3
  20. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -7
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +64 -10
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +2 -0
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +31 -1
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +31 -66
  28. data/lib/active_record/connection_adapters/sqlite_adapter.rb +2 -2
  29. data/lib/active_record/dirty.rb +2 -2
  30. data/lib/active_record/fixtures.rb +1 -0
  31. data/lib/active_record/locking/optimistic.rb +34 -1
  32. data/lib/active_record/migration.rb +5 -0
  33. data/lib/active_record/nested_attributes.rb +64 -52
  34. data/lib/active_record/reflection.rb +66 -1
  35. data/lib/active_record/schema.rb +5 -1
  36. data/lib/active_record/schema_dumper.rb +3 -0
  37. data/lib/active_record/serializers/json_serializer.rb +1 -1
  38. data/lib/active_record/validations.rb +13 -1
  39. data/lib/active_record/version.rb +1 -1
  40. data/test/cases/active_schema_test_mysql.rb +22 -0
  41. data/test/cases/associations/belongs_to_associations_test.rb +13 -0
  42. data/test/cases/associations/eager_load_nested_include_test.rb +8 -7
  43. data/test/cases/associations/eager_test.rb +7 -1
  44. data/test/cases/associations/has_many_associations_test.rb +26 -0
  45. data/test/cases/associations/inverse_associations_test.rb +566 -0
  46. data/test/cases/associations_test.rb +10 -0
  47. data/test/cases/autosave_association_test.rb +86 -10
  48. data/test/cases/base_test.rb +29 -0
  49. data/test/cases/batches_test.rb +20 -0
  50. data/test/cases/calculations_test.rb +2 -3
  51. data/test/cases/encoding_test.rb +6 -0
  52. data/test/cases/finder_test.rb +19 -3
  53. data/test/cases/fixtures_test.rb +5 -0
  54. data/test/cases/json_serialization_test.rb +14 -0
  55. data/test/cases/locking_test.rb +48 -3
  56. data/test/cases/migration_test.rb +115 -0
  57. data/test/cases/modules_test.rb +28 -0
  58. data/test/cases/named_scope_test.rb +1 -1
  59. data/test/cases/nested_attributes_test.rb +239 -7
  60. data/test/cases/query_cache_test.rb +7 -1
  61. data/test/cases/reflection_test.rb +47 -7
  62. data/test/cases/schema_test_postgresql.rb +2 -2
  63. data/test/cases/validations_i18n_test.rb +6 -36
  64. data/test/cases/validations_test.rb +33 -1
  65. data/test/cases/yaml_serialization_test.rb +11 -0
  66. data/test/fixtures/faces.yml +11 -0
  67. data/test/fixtures/fixture_database.sqlite +0 -0
  68. data/test/fixtures/fixture_database.sqlite3 +0 -0
  69. data/test/fixtures/fixture_database_2.sqlite +0 -0
  70. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  71. data/test/fixtures/interests.yml +33 -0
  72. data/test/fixtures/men.yml +5 -0
  73. data/test/fixtures/zines.yml +5 -0
  74. data/test/models/author.rb +3 -0
  75. data/test/models/bird.rb +6 -0
  76. data/test/models/company_in_module.rb +17 -0
  77. data/test/models/event_author.rb +5 -0
  78. data/test/models/face.rb +7 -0
  79. data/test/models/interest.rb +5 -0
  80. data/test/models/invoice.rb +4 -0
  81. data/test/models/line_item.rb +3 -0
  82. data/test/models/man.rb +9 -0
  83. data/test/models/parrot.rb +6 -0
  84. data/test/models/pirate.rb +10 -0
  85. data/test/models/ship.rb +10 -1
  86. data/test/models/ship_part.rb +3 -1
  87. data/test/models/zine.rb +3 -0
  88. data/test/schema/schema.rb +41 -0
  89. metadata +37 -11
  90. data/lib/active_record/i18n_interpolation_deprecation.rb +0 -26
@@ -158,7 +158,7 @@ module ActiveRecord
158
158
  # If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
159
159
  #
160
160
  def add(attribute, message = nil, options = {})
161
- options[:message] = options.delete(:default) if options.has_key?(:default)
161
+ options[:message] = options.delete(:default) if options[:default].is_a?(Symbol)
162
162
  error, message = message, nil if message.is_a?(Error)
163
163
 
164
164
  @errors[attribute.to_s] ||= []
@@ -239,6 +239,18 @@ module ActiveRecord
239
239
  @errors.each_key { |attr| @errors[attr].each { |error| yield attr, error.message } }
240
240
  end
241
241
 
242
+ # Yields each attribute and associated error per error added.
243
+ #
244
+ # class Company < ActiveRecord::Base
245
+ # validates_presence_of :name, :address, :email
246
+ # validates_length_of :name, :in => 5..30
247
+ # end
248
+ #
249
+ # company = Company.create(:address => '123 First St.')
250
+ # company.errors.each_error{|attr,err| puts "#{attr} - #{err.type}" }
251
+ # # => name - :too_short
252
+ # # name - :blank
253
+ # # address - :blank
242
254
  def each_error
243
255
  @errors.each_key { |attr| @errors[attr].each { |error| yield attr, error } }
244
256
  end
@@ -2,7 +2,7 @@ module ActiveRecord
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 2
4
4
  MINOR = 3
5
- TINY = 5
5
+ TINY = 6
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -15,6 +15,28 @@ class ActiveSchemaTest < ActiveRecord::TestCase
15
15
  end
16
16
  end
17
17
 
18
+ def test_add_index
19
+ # add_index calls index_exists? which can't work since execute is stubbed
20
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_exists?) do |*|
21
+ false
22
+ end
23
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
24
+ assert_equal expected, add_index(:people, :last_name, :length => nil)
25
+
26
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
27
+ assert_equal expected, add_index(:people, :last_name, :length => 10)
28
+
29
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
30
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
31
+
32
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
33
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
34
+
35
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
36
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
37
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_exists?)
38
+ end
39
+
18
40
  def test_drop_table
19
41
  assert_equal "DROP TABLE `people`", drop_table(:people)
20
42
  end
@@ -31,6 +31,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
31
31
  assert_equal companies(:first_firm).name, client.firm_with_primary_key.name
32
32
  end
33
33
 
34
+ def test_belongs_to_with_primary_key_joins_on_correct_column
35
+ sql = Client.send(:construct_finder_sql, :joins => :firm_with_primary_key)
36
+ assert sql !~ /\.id/
37
+ assert sql =~ /\.name/
38
+ end
39
+
34
40
  def test_proxy_assignment
35
41
  account = Account.find(1)
36
42
  assert_nothing_raised { account.firm = account.firm }
@@ -60,6 +66,13 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
60
66
  assert_equal apple.name, citibank.firm_name
61
67
  end
62
68
 
69
+ def test_eager_loading_with_primary_key
70
+ apple = Firm.create("name" => "Apple")
71
+ citibank = Client.create("name" => "Citibank", :firm_name => "Apple")
72
+ citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key)
73
+ assert_not_nil citibank_result.instance_variable_get("@firm_with_primary_key")
74
+ end
75
+
63
76
  def test_no_unexpected_aliasing
64
77
  first_firm = companies(:first_firm)
65
78
  another_firm = companies(:another_firm)
@@ -4,6 +4,7 @@ require 'models/author'
4
4
  require 'models/comment'
5
5
  require 'models/category'
6
6
  require 'models/categorization'
7
+ require 'active_support/core_ext/array/random_access'
7
8
 
8
9
  module Remembered
9
10
  def self.included(base)
@@ -17,7 +18,7 @@ module Remembered
17
18
 
18
19
  module ClassMethods
19
20
  def remembered; @@remembered ||= []; end
20
- def rand; @@remembered.rand; end
21
+ def random_element; @@remembered.random_element; end
21
22
  end
22
23
  end
23
24
 
@@ -81,14 +82,14 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
81
82
  [Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!)
82
83
  end
83
84
  1.upto(NUM_SIMPLE_OBJS) do
84
- PaintColor.create!(:non_poly_one_id => NonPolyOne.rand.id)
85
- PaintTexture.create!(:non_poly_two_id => NonPolyTwo.rand.id)
85
+ PaintColor.create!(:non_poly_one_id => NonPolyOne.random_element.id)
86
+ PaintTexture.create!(:non_poly_two_id => NonPolyTwo.random_element.id)
86
87
  end
87
88
  1.upto(NUM_SHAPE_EXPRESSIONS) do
88
- shape_type = [Circle, Square, Triangle].rand
89
- paint_type = [PaintColor, PaintTexture].rand
90
- ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.rand.id,
91
- :paint_type => paint_type.to_s, :paint_id => paint_type.rand.id)
89
+ shape_type = [Circle, Square, Triangle].random_element
90
+ paint_type = [PaintColor, PaintTexture].random_element
91
+ ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.random_element.id,
92
+ :paint_type => paint_type.to_s, :paint_id => paint_type.random_element.id)
92
93
  end
93
94
  end
94
95
 
@@ -830,5 +830,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
830
830
  assert_equal expected, firm.account_using_primary_key
831
831
  end
832
832
  end
833
-
833
+
834
+ def test_preloading_empty_polymorphic_parent
835
+ t = Tagging.create!(:taggable_type => 'Post', :taggable_id => Post.maximum(:id) + 1, :tag => tags(:general))
836
+
837
+ assert_queries(2) { @tagging = Tagging.find(t.id, :include => :taggable) }
838
+ assert_no_queries { assert ! @tagging.taggable }
839
+ end
834
840
  end
@@ -1130,5 +1130,31 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
1130
1130
  client = firm.clients_using_primary_key.create!(:name => 'test')
1131
1131
  assert_equal firm.name, client.firm_name
1132
1132
  end
1133
+
1134
+ def test_normal_method_call_in_association_proxy
1135
+ assert_equal 'Welcome to the weblog', Comment.all.map { |comment| comment.post }.sort_by(&:id).first.title
1136
+ end
1137
+
1138
+ def test_instance_eval_in_association_proxy
1139
+ assert_equal 'Welcome to the weblog', Comment.all.map { |comment| comment.post }.sort_by(&:id).first.instance_eval{title}
1140
+ end
1141
+
1142
+ def test_defining_has_many_association_with_delete_all_dependency_lazily_evaluates_target_class
1143
+ ActiveRecord::Reflection::AssociationReflection.any_instance.expects(:class_name).never
1144
+ class_eval <<-EOF
1145
+ class DeleteAllModel < ActiveRecord::Base
1146
+ has_many :nonentities, :dependent => :delete_all
1147
+ end
1148
+ EOF
1149
+ end
1150
+
1151
+ def test_defining_has_many_association_with_nullify_dependency_lazily_evaluates_target_class
1152
+ ActiveRecord::Reflection::AssociationReflection.any_instance.expects(:class_name).never
1153
+ class_eval <<-EOF
1154
+ class NullifyModel < ActiveRecord::Base
1155
+ has_many :nonentities, :dependent => :nullify
1156
+ end
1157
+ EOF
1158
+ end
1133
1159
  end
1134
1160
 
@@ -0,0 +1,566 @@
1
+ require "cases/helper"
2
+ require 'models/man'
3
+ require 'models/face'
4
+ require 'models/interest'
5
+ require 'models/zine'
6
+ require 'models/club'
7
+ require 'models/sponsor'
8
+
9
+ class InverseAssociationTests < ActiveRecord::TestCase
10
+ def test_should_allow_for_inverse_of_options_in_associations
11
+ assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_many') do
12
+ Class.new(ActiveRecord::Base).has_many(:wheels, :inverse_of => :car)
13
+ end
14
+
15
+ assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on has_one') do
16
+ Class.new(ActiveRecord::Base).has_one(:engine, :inverse_of => :car)
17
+ end
18
+
19
+ assert_nothing_raised(ArgumentError, 'ActiveRecord should allow the inverse_of options on belongs_to') do
20
+ Class.new(ActiveRecord::Base).belongs_to(:car, :inverse_of => :driver)
21
+ end
22
+ end
23
+
24
+ def test_should_be_able_to_ask_a_reflection_if_it_has_an_inverse
25
+ has_one_with_inverse_ref = Man.reflect_on_association(:face)
26
+ assert has_one_with_inverse_ref.respond_to?(:has_inverse?)
27
+ assert has_one_with_inverse_ref.has_inverse?
28
+
29
+ has_many_with_inverse_ref = Man.reflect_on_association(:interests)
30
+ assert has_many_with_inverse_ref.respond_to?(:has_inverse?)
31
+ assert has_many_with_inverse_ref.has_inverse?
32
+
33
+ belongs_to_with_inverse_ref = Face.reflect_on_association(:man)
34
+ assert belongs_to_with_inverse_ref.respond_to?(:has_inverse?)
35
+ assert belongs_to_with_inverse_ref.has_inverse?
36
+
37
+ has_one_without_inverse_ref = Club.reflect_on_association(:sponsor)
38
+ assert has_one_without_inverse_ref.respond_to?(:has_inverse?)
39
+ assert !has_one_without_inverse_ref.has_inverse?
40
+
41
+ has_many_without_inverse_ref = Club.reflect_on_association(:memberships)
42
+ assert has_many_without_inverse_ref.respond_to?(:has_inverse?)
43
+ assert !has_many_without_inverse_ref.has_inverse?
44
+
45
+ belongs_to_without_inverse_ref = Sponsor.reflect_on_association(:sponsor_club)
46
+ assert belongs_to_without_inverse_ref.respond_to?(:has_inverse?)
47
+ assert !belongs_to_without_inverse_ref.has_inverse?
48
+ end
49
+
50
+ def test_should_be_able_to_ask_a_reflection_what_it_is_the_inverse_of
51
+ has_one_ref = Man.reflect_on_association(:face)
52
+ assert has_one_ref.respond_to?(:inverse_of)
53
+
54
+ has_many_ref = Man.reflect_on_association(:interests)
55
+ assert has_many_ref.respond_to?(:inverse_of)
56
+
57
+ belongs_to_ref = Face.reflect_on_association(:man)
58
+ assert belongs_to_ref.respond_to?(:inverse_of)
59
+ end
60
+
61
+ def test_inverse_of_method_should_supply_the_actual_reflection_instance_it_is_the_inverse_of
62
+ has_one_ref = Man.reflect_on_association(:face)
63
+ assert_equal Face.reflect_on_association(:man), has_one_ref.inverse_of
64
+
65
+ has_many_ref = Man.reflect_on_association(:interests)
66
+ assert_equal Interest.reflect_on_association(:man), has_many_ref.inverse_of
67
+
68
+ belongs_to_ref = Face.reflect_on_association(:man)
69
+ assert_equal Man.reflect_on_association(:face), belongs_to_ref.inverse_of
70
+ end
71
+
72
+ def test_associations_with_no_inverse_of_should_return_nil
73
+ has_one_ref = Club.reflect_on_association(:sponsor)
74
+ assert_nil has_one_ref.inverse_of
75
+
76
+ has_many_ref = Club.reflect_on_association(:memberships)
77
+ assert_nil has_many_ref.inverse_of
78
+
79
+ belongs_to_ref = Sponsor.reflect_on_association(:sponsor_club)
80
+ assert_nil belongs_to_ref.inverse_of
81
+ end
82
+ end
83
+
84
+ class InverseHasOneTests < ActiveRecord::TestCase
85
+ fixtures :men, :faces
86
+
87
+ def test_parent_instance_should_be_shared_with_child_on_find
88
+ m = men(:gordon)
89
+ f = m.face
90
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
91
+ m.name = 'Bongo'
92
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
93
+ f.man.name = 'Mungo'
94
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
95
+ end
96
+
97
+ def test_parent_instance_should_be_shared_with_eager_loaded_child_on_find
98
+ m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face)
99
+ f = m.face
100
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
101
+ m.name = 'Bongo'
102
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
103
+ f.man.name = 'Mungo'
104
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
105
+
106
+ m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face, :order => 'faces.id')
107
+ f = m.face
108
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
109
+ m.name = 'Bongo'
110
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
111
+ f.man.name = 'Mungo'
112
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
113
+ end
114
+
115
+ def test_parent_instance_should_be_shared_with_newly_built_child
116
+ m = men(:gordon)
117
+ f = m.build_face(:description => 'haunted')
118
+ assert_not_nil f.man
119
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
120
+ m.name = 'Bongo'
121
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
122
+ f.man.name = 'Mungo'
123
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
124
+ end
125
+
126
+ def test_parent_instance_should_be_shared_with_newly_created_child
127
+ m = men(:gordon)
128
+ f = m.create_face(:description => 'haunted')
129
+ assert_not_nil f.man
130
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
131
+ m.name = 'Bongo'
132
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
133
+ f.man.name = 'Mungo'
134
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
135
+ end
136
+
137
+ def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method
138
+ m = Man.find(:first)
139
+ f = m.face.create!(:description => 'haunted')
140
+ assert_not_nil f.man
141
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
142
+ m.name = 'Bongo'
143
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
144
+ f.man.name = 'Mungo'
145
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
146
+ end
147
+
148
+ def test_parent_instance_should_be_shared_with_newly_built_child_when_we_dont_replace_existing
149
+ m = Man.find(:first)
150
+ f = m.build_face({:description => 'haunted'}, false)
151
+ assert_not_nil f.man
152
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
153
+ m.name = 'Bongo'
154
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
155
+ f.man.name = 'Mungo'
156
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
157
+ end
158
+
159
+ def test_parent_instance_should_be_shared_with_newly_created_child_when_we_dont_replace_existing
160
+ m = Man.find(:first)
161
+ f = m.create_face({:description => 'haunted'}, false)
162
+ assert_not_nil f.man
163
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
164
+ m.name = 'Bongo'
165
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
166
+ f.man.name = 'Mungo'
167
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
168
+ end
169
+
170
+ def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method_when_we_dont_replace_existing
171
+ m = Man.find(:first)
172
+ f = m.face.create!({:description => 'haunted'}, false)
173
+ assert_not_nil f.man
174
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
175
+ m.name = 'Bongo'
176
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
177
+ f.man.name = 'Mungo'
178
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
179
+ end
180
+
181
+ def test_parent_instance_should_be_shared_with_replaced_via_accessor_child
182
+ m = Man.find(:first)
183
+ f = Face.new(:description => 'haunted')
184
+ m.face = f
185
+ assert_not_nil f.man
186
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
187
+ m.name = 'Bongo'
188
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
189
+ f.man.name = 'Mungo'
190
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
191
+ end
192
+
193
+ def test_parent_instance_should_be_shared_with_replaced_via_method_child
194
+ m = Man.find(:first)
195
+ f = Face.new(:description => 'haunted')
196
+ m.face.replace(f)
197
+ assert_not_nil f.man
198
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
199
+ m.name = 'Bongo'
200
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
201
+ f.man.name = 'Mungo'
202
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
203
+ end
204
+
205
+ def test_parent_instance_should_be_shared_with_replaced_via_method_child_when_we_dont_replace_existing
206
+ m = Man.find(:first)
207
+ f = Face.new(:description => 'haunted')
208
+ m.face.replace(f, false)
209
+ assert_not_nil f.man
210
+ assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
211
+ m.name = 'Bongo'
212
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
213
+ f.man.name = 'Mungo'
214
+ assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
215
+ end
216
+
217
+ def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
218
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).dirty_face }
219
+ end
220
+ end
221
+
222
+ class InverseHasManyTests < ActiveRecord::TestCase
223
+ fixtures :men, :interests
224
+
225
+ def test_parent_instance_should_be_shared_with_every_child_on_find
226
+ m = men(:gordon)
227
+ is = m.interests
228
+ is.each do |i|
229
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
230
+ m.name = 'Bongo'
231
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
232
+ i.man.name = 'Mungo'
233
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
234
+ end
235
+ end
236
+
237
+ def test_parent_instance_should_be_shared_with_eager_loaded_children
238
+ m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests)
239
+ is = m.interests
240
+ is.each do |i|
241
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
242
+ m.name = 'Bongo'
243
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
244
+ i.man.name = 'Mungo'
245
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
246
+ end
247
+
248
+ m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests, :order => 'interests.id')
249
+ is = m.interests
250
+ is.each do |i|
251
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
252
+ m.name = 'Bongo'
253
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
254
+ i.man.name = 'Mungo'
255
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
256
+ end
257
+ end
258
+
259
+ def test_parent_instance_should_be_shared_with_newly_built_child
260
+ m = men(:gordon)
261
+ i = m.interests.build(:topic => 'Industrial Revolution Re-enactment')
262
+ assert_not_nil i.man
263
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
264
+ m.name = 'Bongo'
265
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
266
+ i.man.name = 'Mungo'
267
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
268
+ end
269
+
270
+ def test_parent_instance_should_be_shared_with_newly_block_style_built_child
271
+ m = Man.find(:first)
272
+ i = m.interests.build {|ii| ii.topic = 'Industrial Revolution Re-enactment'}
273
+ assert_not_nil i.topic, "Child attributes supplied to build via blocks should be populated"
274
+ assert_not_nil i.man
275
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
276
+ m.name = 'Bongo'
277
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
278
+ i.man.name = 'Mungo'
279
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
280
+ end
281
+
282
+ def test_parent_instance_should_be_shared_with_newly_created_child
283
+ m = men(:gordon)
284
+ i = m.interests.create(:topic => 'Industrial Revolution Re-enactment')
285
+ assert_not_nil i.man
286
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
287
+ m.name = 'Bongo'
288
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
289
+ i.man.name = 'Mungo'
290
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
291
+ end
292
+
293
+ def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child
294
+ m = Man.find(:first)
295
+ i = m.interests.create!(:topic => 'Industrial Revolution Re-enactment')
296
+ assert_not_nil i.man
297
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
298
+ m.name = 'Bongo'
299
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
300
+ i.man.name = 'Mungo'
301
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
302
+ end
303
+
304
+ def test_parent_instance_should_be_shared_with_newly_block_style_created_child
305
+ m = Man.find(:first)
306
+ i = m.interests.create {|ii| ii.topic = 'Industrial Revolution Re-enactment'}
307
+ assert_not_nil i.topic, "Child attributes supplied to create via blocks should be populated"
308
+ assert_not_nil i.man
309
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
310
+ m.name = 'Bongo'
311
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
312
+ i.man.name = 'Mungo'
313
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
314
+ end
315
+
316
+ def test_parent_instance_should_be_shared_with_poked_in_child
317
+ m = men(:gordon)
318
+ i = Interest.create(:topic => 'Industrial Revolution Re-enactment')
319
+ m.interests << i
320
+ assert_not_nil i.man
321
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
322
+ m.name = 'Bongo'
323
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
324
+ i.man.name = 'Mungo'
325
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
326
+ end
327
+
328
+ def test_parent_instance_should_be_shared_with_replaced_via_accessor_children
329
+ m = Man.find(:first)
330
+ i = Interest.new(:topic => 'Industrial Revolution Re-enactment')
331
+ m.interests = [i]
332
+ assert_not_nil i.man
333
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
334
+ m.name = 'Bongo'
335
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
336
+ i.man.name = 'Mungo'
337
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
338
+ end
339
+
340
+ def test_parent_instance_should_be_shared_with_replaced_via_method_children
341
+ m = Man.find(:first)
342
+ i = Interest.new(:topic => 'Industrial Revolution Re-enactment')
343
+ m.interests.replace([i])
344
+ assert_not_nil i.man
345
+ assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
346
+ m.name = 'Bongo'
347
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
348
+ i.man.name = 'Mungo'
349
+ assert_equal m.name, i.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
350
+ end
351
+
352
+ def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
353
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).secret_interests }
354
+ end
355
+ end
356
+
357
+ class InverseBelongsToTests < ActiveRecord::TestCase
358
+ fixtures :men, :faces, :interests
359
+
360
+ def test_child_instance_should_be_shared_with_parent_on_find
361
+ f = faces(:trusting)
362
+ m = f.man
363
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
364
+ f.description = 'gormless'
365
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
366
+ m.face.description = 'pleasing'
367
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
368
+ end
369
+
370
+ def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find
371
+ f = Face.find(:first, :include => :man, :conditions => {:description => 'trusting'})
372
+ m = f.man
373
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
374
+ f.description = 'gormless'
375
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
376
+ m.face.description = 'pleasing'
377
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
378
+
379
+ f = Face.find(:first, :include => :man, :order => 'men.id', :conditions => {:description => 'trusting'})
380
+ m = f.man
381
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
382
+ f.description = 'gormless'
383
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
384
+ m.face.description = 'pleasing'
385
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
386
+ end
387
+
388
+ def test_child_instance_should_be_shared_with_newly_built_parent
389
+ f = faces(:trusting)
390
+ m = f.build_man(:name => 'Charles')
391
+ assert_not_nil m.face
392
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
393
+ f.description = 'gormless'
394
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
395
+ m.face.description = 'pleasing'
396
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to just-built-parent-owned instance"
397
+ end
398
+
399
+ def test_child_instance_should_be_shared_with_newly_created_parent
400
+ f = faces(:trusting)
401
+ m = f.create_man(:name => 'Charles')
402
+ assert_not_nil m.face
403
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
404
+ f.description = 'gormless'
405
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
406
+ m.face.description = 'pleasing'
407
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to newly-created-parent-owned instance"
408
+ end
409
+
410
+ def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
411
+ i = interests(:trainspotting)
412
+ m = i.man
413
+ assert_not_nil m.interests
414
+ iz = m.interests.detect {|iz| iz.id == i.id}
415
+ assert_not_nil iz
416
+ assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
417
+ i.topic = 'Eating cheese with a spoon'
418
+ assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to child"
419
+ iz.topic = 'Cow tipping'
420
+ assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to parent-owned instance"
421
+ end
422
+
423
+ def test_child_instance_should_be_shared_with_replaced_via_accessor_parent
424
+ f = Face.find(:first)
425
+ m = Man.new(:name => 'Charles')
426
+ f.man = m
427
+ assert_not_nil m.face
428
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
429
+ f.description = 'gormless'
430
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
431
+ m.face.description = 'pleasing'
432
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
433
+ end
434
+
435
+ def test_child_instance_should_be_shared_with_replaced_via_method_parent
436
+ f = faces(:trusting)
437
+ assert_not_nil f.man
438
+ m = Man.new(:name => 'Charles')
439
+ f.man.replace(m)
440
+ assert_not_nil m.face
441
+ assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
442
+ f.description = 'gormless'
443
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to child instance"
444
+ m.face.description = 'pleasing'
445
+ assert_equal f.description, m.face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
446
+ end
447
+
448
+ def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
449
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_man }
450
+ end
451
+ end
452
+
453
+ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
454
+ fixtures :men, :faces, :interests
455
+
456
+ def test_child_instance_should_be_shared_with_parent_on_find
457
+ f = Face.find(:first, :conditions => {:description => 'confused'})
458
+ m = f.polymorphic_man
459
+ assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
460
+ f.description = 'gormless'
461
+ assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to child instance"
462
+ m.polymorphic_face.description = 'pleasing'
463
+ assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance"
464
+ end
465
+
466
+ def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find
467
+ f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man)
468
+ m = f.polymorphic_man
469
+ assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
470
+ f.description = 'gormless'
471
+ assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to child instance"
472
+ m.polymorphic_face.description = 'pleasing'
473
+ assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance"
474
+
475
+ f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man, :order => 'men.id')
476
+ m = f.polymorphic_man
477
+ assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
478
+ f.description = 'gormless'
479
+ assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to child instance"
480
+ m.polymorphic_face.description = 'pleasing'
481
+ assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance"
482
+ end
483
+
484
+ def test_child_instance_should_be_shared_with_replaced_via_accessor_parent
485
+ face = faces(:confused)
486
+ old_man = face.polymorphic_man
487
+ new_man = Man.new
488
+
489
+ assert_not_nil face.polymorphic_man
490
+ face.polymorphic_man = new_man
491
+
492
+ assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance"
493
+ face.description = 'Bongo'
494
+ assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance"
495
+ new_man.polymorphic_face.description = 'Mungo'
496
+ assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
497
+ end
498
+
499
+ def test_child_instance_should_be_shared_with_replaced_via_method_parent
500
+ face = faces(:confused)
501
+ old_man = face.polymorphic_man
502
+ new_man = Man.new
503
+
504
+ assert_not_nil face.polymorphic_man
505
+ face.polymorphic_man.replace(new_man)
506
+
507
+ assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same before changes to parent instance"
508
+ face.description = 'Bongo'
509
+ assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to parent instance"
510
+ new_man.polymorphic_face.description = 'Mungo'
511
+ assert_equal face.description, new_man.polymorphic_face.description, "Description of face should be the same after changes to replaced-parent-owned instance"
512
+ end
513
+
514
+ def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
515
+ i = interests(:llama_wrangling)
516
+ m = i.polymorphic_man
517
+ assert_not_nil m.polymorphic_interests
518
+ iz = m.polymorphic_interests.detect {|iz| iz.id == i.id}
519
+ assert_not_nil iz
520
+ assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
521
+ i.topic = 'Eating cheese with a spoon'
522
+ assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to child"
523
+ iz.topic = 'Cow tipping'
524
+ assert_not_equal i.topic, iz.topic, "Interest topics should not be the same after changes to parent-owned instance"
525
+ end
526
+
527
+ def test_trying_to_access_inverses_that_dont_exist_shouldnt_raise_an_error
528
+ # Ideally this would, if only for symmetry's sake with other association types
529
+ assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_polymorphic_man }
530
+ end
531
+
532
+ def test_trying_to_set_polymorphic_inverses_that_dont_exist_at_all_should_raise_an_error
533
+ # fails because no class has the correct inverse_of for horrible_polymorphic_man
534
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_polymorphic_man = Man.first }
535
+ end
536
+
537
+ def test_trying_to_set_polymorphic_inverses_that_dont_exist_on_the_instance_being_set_should_raise_an_error
538
+ # passes because Man does have the correct inverse_of
539
+ assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).polymorphic_man = Man.first }
540
+ # fails because Interest does have the correct inverse_of
541
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).polymorphic_man = Interest.first }
542
+ end
543
+ end
544
+
545
+ # NOTE - these tests might not be meaningful, ripped as they were from the parental_control plugin
546
+ # which would guess the inverse rather than look for an explicit configuration option.
547
+ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
548
+ fixtures :men, :interests, :zines
549
+
550
+ def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models
551
+ assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
552
+ i = Interest.find(:first)
553
+ z = i.zine
554
+ m = i.man
555
+ end
556
+ end
557
+
558
+ def test_that_we_can_create_associations_that_have_the_same_reciprocal_name_from_different_models
559
+ assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
560
+ i = Interest.find(:first)
561
+ i.build_zine(:title => 'Get Some in Winter! 2008')
562
+ i.build_man(:name => 'Gordon')
563
+ i.save!
564
+ end
565
+ end
566
+ end