activerecord 2.0.5 → 2.1.0

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 (289) hide show
  1. data/CHANGELOG +168 -6
  2. data/README +27 -22
  3. data/RUNNING_UNIT_TESTS +7 -4
  4. data/Rakefile +22 -25
  5. data/lib/active_record.rb +8 -2
  6. data/lib/active_record/aggregations.rb +21 -12
  7. data/lib/active_record/association_preload.rb +277 -0
  8. data/lib/active_record/associations.rb +481 -295
  9. data/lib/active_record/associations/association_collection.rb +162 -37
  10. data/lib/active_record/associations/association_proxy.rb +71 -7
  11. data/lib/active_record/associations/belongs_to_association.rb +5 -3
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -6
  13. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -64
  14. data/lib/active_record/associations/has_many_association.rb +8 -73
  15. data/lib/active_record/associations/has_many_through_association.rb +68 -117
  16. data/lib/active_record/associations/has_one_association.rb +7 -5
  17. data/lib/active_record/associations/has_one_through_association.rb +28 -0
  18. data/lib/active_record/attribute_methods.rb +69 -19
  19. data/lib/active_record/base.rb +496 -275
  20. data/lib/active_record/calculations.rb +28 -21
  21. data/lib/active_record/callbacks.rb +9 -38
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
  24. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +232 -45
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +141 -27
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +9 -13
  28. data/lib/active_record/connection_adapters/mysql_adapter.rb +57 -24
  29. data/lib/active_record/connection_adapters/postgresql_adapter.rb +143 -42
  30. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  31. data/lib/active_record/connection_adapters/sqlite_adapter.rb +18 -10
  32. data/lib/active_record/dirty.rb +158 -0
  33. data/lib/active_record/fixtures.rb +121 -156
  34. data/lib/active_record/locking/optimistic.rb +14 -11
  35. data/lib/active_record/locking/pessimistic.rb +2 -2
  36. data/lib/active_record/migration.rb +157 -77
  37. data/lib/active_record/named_scope.rb +163 -0
  38. data/lib/active_record/observer.rb +19 -5
  39. data/lib/active_record/reflection.rb +34 -14
  40. data/lib/active_record/schema.rb +7 -14
  41. data/lib/active_record/schema_dumper.rb +4 -4
  42. data/lib/active_record/serialization.rb +5 -5
  43. data/lib/active_record/serializers/json_serializer.rb +37 -28
  44. data/lib/active_record/serializers/xml_serializer.rb +52 -29
  45. data/lib/active_record/test_case.rb +36 -0
  46. data/lib/active_record/timestamp.rb +4 -4
  47. data/lib/active_record/transactions.rb +3 -3
  48. data/lib/active_record/validations.rb +182 -248
  49. data/lib/active_record/version.rb +2 -2
  50. data/test/{fixtures → assets}/example.log +0 -0
  51. data/test/{fixtures → assets}/flowers.jpg +0 -0
  52. data/test/cases/aaa_create_tables_test.rb +24 -0
  53. data/test/cases/active_schema_test_mysql.rb +95 -0
  54. data/test/cases/active_schema_test_postgresql.rb +24 -0
  55. data/test/{adapter_test.rb → cases/adapter_test.rb} +15 -14
  56. data/test/{adapter_test_sqlserver.rb → cases/adapter_test_sqlserver.rb} +95 -95
  57. data/test/{aggregations_test.rb → cases/aggregations_test.rb} +20 -20
  58. data/test/{ar_schema_test.rb → cases/ar_schema_test.rb} +6 -6
  59. data/test/cases/associations/belongs_to_associations_test.rb +412 -0
  60. data/test/{associations → cases/associations}/callbacks_test.rb +24 -10
  61. data/test/{associations → cases/associations}/cascaded_eager_loading_test.rb +18 -17
  62. data/test/cases/associations/eager_load_nested_include_test.rb +83 -0
  63. data/test/{associations → cases/associations}/eager_singularization_test.rb +5 -5
  64. data/test/{associations → cases/associations}/eager_test.rb +216 -51
  65. data/test/{associations → cases/associations}/extension_test.rb +8 -8
  66. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +684 -0
  67. data/test/cases/associations/has_many_associations_test.rb +932 -0
  68. data/test/cases/associations/has_many_through_associations_test.rb +190 -0
  69. data/test/cases/associations/has_one_associations_test.rb +323 -0
  70. data/test/cases/associations/has_one_through_associations_test.rb +74 -0
  71. data/test/{associations → cases/associations}/inner_join_association_test.rb +20 -20
  72. data/test/{associations → cases/associations}/join_model_test.rb +175 -35
  73. data/test/cases/associations_test.rb +262 -0
  74. data/test/{attribute_methods_test.rb → cases/attribute_methods_test.rb} +103 -11
  75. data/test/{base_test.rb → cases/base_test.rb} +338 -191
  76. data/test/{binary_test.rb → cases/binary_test.rb} +6 -4
  77. data/test/{calculations_test.rb → cases/calculations_test.rb} +35 -23
  78. data/test/{callbacks_test.rb → cases/callbacks_test.rb} +7 -7
  79. data/test/{class_inheritable_attributes_test.rb → cases/class_inheritable_attributes_test.rb} +3 -3
  80. data/test/{column_alias_test.rb → cases/column_alias_test.rb} +3 -3
  81. data/test/{connection_test_firebird.rb → cases/connection_test_firebird.rb} +2 -2
  82. data/test/{connection_test_mysql.rb → cases/connection_test_mysql.rb} +2 -2
  83. data/test/{copy_table_test_sqlite.rb → cases/copy_table_test_sqlite.rb} +13 -13
  84. data/test/{datatype_test_postgresql.rb → cases/datatype_test_postgresql.rb} +8 -8
  85. data/test/{date_time_test.rb → cases/date_time_test.rb} +5 -5
  86. data/test/{default_test_firebird.rb → cases/default_test_firebird.rb} +3 -3
  87. data/test/{defaults_test.rb → cases/defaults_test.rb} +8 -6
  88. data/test/{deprecated_finder_test.rb → cases/deprecated_finder_test.rb} +3 -3
  89. data/test/cases/dirty_test.rb +163 -0
  90. data/test/cases/finder_respond_to_test.rb +76 -0
  91. data/test/{finder_test.rb → cases/finder_test.rb} +266 -33
  92. data/test/{fixtures_test.rb → cases/fixtures_test.rb} +88 -72
  93. data/test/cases/helper.rb +47 -0
  94. data/test/{inheritance_test.rb → cases/inheritance_test.rb} +61 -17
  95. data/test/cases/invalid_date_test.rb +24 -0
  96. data/test/{json_serialization_test.rb → cases/json_serialization_test.rb} +36 -11
  97. data/test/{lifecycle_test.rb → cases/lifecycle_test.rb} +16 -13
  98. data/test/{locking_test.rb → cases/locking_test.rb} +17 -10
  99. data/test/{method_scoping_test.rb → cases/method_scoping_test.rb} +75 -39
  100. data/test/{migration_test.rb → cases/migration_test.rb} +420 -80
  101. data/test/{migration_test_firebird.rb → cases/migration_test_firebird.rb} +3 -3
  102. data/test/{mixin_test.rb → cases/mixin_test.rb} +7 -6
  103. data/test/{modules_test.rb → cases/modules_test.rb} +11 -6
  104. data/test/{multiple_db_test.rb → cases/multiple_db_test.rb} +5 -5
  105. data/test/cases/named_scope_test.rb +157 -0
  106. data/test/{pk_test.rb → cases/pk_test.rb} +10 -10
  107. data/test/{query_cache_test.rb → cases/query_cache_test.rb} +12 -10
  108. data/test/{readonly_test.rb → cases/readonly_test.rb} +11 -11
  109. data/test/{reflection_test.rb → cases/reflection_test.rb} +15 -14
  110. data/test/{reserved_word_test_mysql.rb → cases/reserved_word_test_mysql.rb} +4 -5
  111. data/test/{schema_authorization_test_postgresql.rb → cases/schema_authorization_test_postgresql.rb} +5 -5
  112. data/test/cases/schema_dumper_test.rb +138 -0
  113. data/test/cases/schema_test_postgresql.rb +102 -0
  114. data/test/{serialization_test.rb → cases/serialization_test.rb} +7 -7
  115. data/test/{synonym_test_oracle.rb → cases/synonym_test_oracle.rb} +5 -5
  116. data/test/{table_name_test_sqlserver.rb → cases/table_name_test_sqlserver.rb} +3 -3
  117. data/test/{threaded_connections_test.rb → cases/threaded_connections_test.rb} +7 -7
  118. data/test/{transactions_test.rb → cases/transactions_test.rb} +31 -5
  119. data/test/{unconnected_test.rb → cases/unconnected_test.rb} +2 -2
  120. data/test/{validations_test.rb → cases/validations_test.rb} +141 -39
  121. data/test/{xml_serialization_test.rb → cases/xml_serialization_test.rb} +12 -12
  122. data/test/config.rb +5 -0
  123. data/test/connections/native_db2/connection.rb +1 -1
  124. data/test/connections/native_firebird/connection.rb +1 -1
  125. data/test/connections/native_frontbase/connection.rb +1 -1
  126. data/test/connections/native_mysql/connection.rb +1 -1
  127. data/test/connections/native_openbase/connection.rb +1 -1
  128. data/test/connections/native_oracle/connection.rb +1 -1
  129. data/test/connections/native_postgresql/connection.rb +1 -3
  130. data/test/connections/native_sqlite/connection.rb +2 -2
  131. data/test/connections/native_sqlite3/connection.rb +2 -2
  132. data/test/connections/native_sqlite3/in_memory_connection.rb +3 -3
  133. data/test/connections/native_sybase/connection.rb +1 -1
  134. data/test/fixtures/author_addresses.yml +5 -0
  135. data/test/fixtures/authors.yml +2 -0
  136. data/test/fixtures/clubs.yml +6 -0
  137. data/test/fixtures/jobs.yml +7 -0
  138. data/test/fixtures/members.yml +4 -0
  139. data/test/fixtures/memberships.yml +20 -0
  140. data/test/fixtures/owners.yml +7 -0
  141. data/test/fixtures/people.yml +4 -1
  142. data/test/fixtures/pets.yml +14 -0
  143. data/test/fixtures/posts.yml +1 -0
  144. data/test/fixtures/price_estimates.yml +7 -0
  145. data/test/fixtures/readers.yml +5 -0
  146. data/test/fixtures/references.yml +17 -0
  147. data/test/fixtures/sponsors.yml +9 -0
  148. data/test/fixtures/subscribers.yml +7 -0
  149. data/test/fixtures/subscriptions.yml +12 -0
  150. data/test/fixtures/taggings.yml +4 -1
  151. data/test/fixtures/topics.yml +22 -2
  152. data/test/fixtures/warehouse-things.yml +3 -0
  153. data/test/{fixtures/migrations_with_decimal → migrations/decimal}/1_give_me_big_numbers.rb +0 -0
  154. data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/1_people_have_last_names.rb +1 -1
  155. data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/2_we_need_reminders.rb +1 -1
  156. data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/3_foo.rb +0 -0
  157. data/test/{fixtures/migrations → migrations/duplicate}/3_innocent_jointable.rb +0 -0
  158. data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
  159. data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
  160. data/test/{fixtures/migrations_with_duplicate → migrations/interleaved/pass_1}/3_innocent_jointable.rb +0 -0
  161. data/test/{fixtures/migrations → migrations/interleaved/pass_2}/1_people_have_last_names.rb +1 -1
  162. data/test/{fixtures/migrations_with_missing_versions/4_innocent_jointable.rb → migrations/interleaved/pass_2/3_innocent_jointable.rb} +0 -0
  163. data/test/{fixtures/migrations_with_missing_versions → migrations/interleaved/pass_3}/1_people_have_last_names.rb +1 -1
  164. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
  165. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
  166. data/test/{fixtures/migrations_with_missing_versions → migrations/missing}/1000_people_have_middle_names.rb +1 -1
  167. data/test/migrations/missing/1_people_have_last_names.rb +9 -0
  168. data/test/{fixtures/migrations_with_missing_versions → migrations/missing}/3_we_need_reminders.rb +1 -1
  169. data/test/migrations/missing/4_innocent_jointable.rb +12 -0
  170. data/test/migrations/valid/1_people_have_last_names.rb +9 -0
  171. data/test/{fixtures/migrations → migrations/valid}/2_we_need_reminders.rb +1 -1
  172. data/test/migrations/valid/3_innocent_jointable.rb +12 -0
  173. data/test/{fixtures → models}/author.rb +28 -4
  174. data/test/{fixtures → models}/auto_id.rb +0 -0
  175. data/test/{fixtures → models}/binary.rb +0 -0
  176. data/test/{fixtures → models}/book.rb +0 -0
  177. data/test/{fixtures → models}/categorization.rb +0 -0
  178. data/test/{fixtures → models}/category.rb +8 -5
  179. data/test/{fixtures → models}/citation.rb +0 -0
  180. data/test/models/club.rb +7 -0
  181. data/test/{fixtures → models}/column_name.rb +0 -0
  182. data/test/{fixtures → models}/comment.rb +5 -3
  183. data/test/{fixtures → models}/company.rb +15 -6
  184. data/test/{fixtures → models}/company_in_module.rb +5 -3
  185. data/test/{fixtures → models}/computer.rb +0 -1
  186. data/test/{fixtures → models}/contact.rb +1 -1
  187. data/test/{fixtures → models}/course.rb +0 -0
  188. data/test/{fixtures → models}/customer.rb +8 -8
  189. data/test/{fixtures → models}/default.rb +0 -0
  190. data/test/{fixtures → models}/developer.rb +14 -10
  191. data/test/{fixtures → models}/edge.rb +0 -0
  192. data/test/{fixtures → models}/entrant.rb +0 -0
  193. data/test/models/guid.rb +2 -0
  194. data/test/{fixtures → models}/item.rb +0 -0
  195. data/test/models/job.rb +5 -0
  196. data/test/{fixtures → models}/joke.rb +0 -0
  197. data/test/{fixtures → models}/keyboard.rb +0 -0
  198. data/test/{fixtures → models}/legacy_thing.rb +0 -0
  199. data/test/{fixtures → models}/matey.rb +0 -0
  200. data/test/models/member.rb +9 -0
  201. data/test/models/membership.rb +9 -0
  202. data/test/{fixtures → models}/minimalistic.rb +0 -0
  203. data/test/{fixtures → models}/mixed_case_monkey.rb +0 -0
  204. data/test/{fixtures → models}/movie.rb +0 -0
  205. data/test/{fixtures → models}/order.rb +2 -2
  206. data/test/models/owner.rb +4 -0
  207. data/test/{fixtures → models}/parrot.rb +0 -0
  208. data/test/models/person.rb +10 -0
  209. data/test/models/pet.rb +4 -0
  210. data/test/models/pirate.rb +9 -0
  211. data/test/{fixtures → models}/post.rb +23 -2
  212. data/test/models/price_estimate.rb +3 -0
  213. data/test/{fixtures → models}/project.rb +1 -0
  214. data/test/{fixtures → models}/reader.rb +0 -0
  215. data/test/models/reference.rb +4 -0
  216. data/test/{fixtures → models}/reply.rb +7 -5
  217. data/test/{fixtures → models}/ship.rb +0 -0
  218. data/test/models/sponsor.rb +4 -0
  219. data/test/{fixtures → models}/subject.rb +0 -0
  220. data/test/{fixtures → models}/subscriber.rb +2 -0
  221. data/test/models/subscription.rb +4 -0
  222. data/test/{fixtures → models}/tag.rb +0 -0
  223. data/test/{fixtures → models}/tagging.rb +0 -0
  224. data/test/{fixtures → models}/task.rb +0 -0
  225. data/test/{fixtures → models}/topic.rb +32 -4
  226. data/test/{fixtures → models}/treasure.rb +2 -0
  227. data/test/{fixtures → models}/vertex.rb +0 -0
  228. data/test/models/warehouse_thing.rb +5 -0
  229. data/test/schema/mysql_specific_schema.rb +12 -0
  230. data/test/schema/postgresql_specific_schema.rb +103 -0
  231. data/test/schema/schema.rb +421 -0
  232. data/test/schema/schema2.rb +6 -0
  233. data/test/schema/sqlite_specific_schema.rb +25 -0
  234. data/test/schema/sqlserver_specific_schema.rb +5 -0
  235. metadata +192 -176
  236. data/test/aaa_create_tables_test.rb +0 -72
  237. data/test/abstract_unit.rb +0 -84
  238. data/test/active_schema_test_mysql.rb +0 -46
  239. data/test/all.sh +0 -8
  240. data/test/association_inheritance_reload.rb +0 -14
  241. data/test/associations_test.rb +0 -2177
  242. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +0 -1
  243. data/test/fixtures/bad_fixtures/attr_with_spaces +0 -1
  244. data/test/fixtures/bad_fixtures/blank_line +0 -3
  245. data/test/fixtures/bad_fixtures/duplicate_attributes +0 -3
  246. data/test/fixtures/bad_fixtures/missing_value +0 -1
  247. data/test/fixtures/db_definitions/db2.drop.sql +0 -33
  248. data/test/fixtures/db_definitions/db2.sql +0 -235
  249. data/test/fixtures/db_definitions/db22.drop.sql +0 -2
  250. data/test/fixtures/db_definitions/db22.sql +0 -5
  251. data/test/fixtures/db_definitions/firebird.drop.sql +0 -65
  252. data/test/fixtures/db_definitions/firebird.sql +0 -310
  253. data/test/fixtures/db_definitions/firebird2.drop.sql +0 -2
  254. data/test/fixtures/db_definitions/firebird2.sql +0 -6
  255. data/test/fixtures/db_definitions/frontbase.drop.sql +0 -33
  256. data/test/fixtures/db_definitions/frontbase.sql +0 -273
  257. data/test/fixtures/db_definitions/frontbase2.drop.sql +0 -1
  258. data/test/fixtures/db_definitions/frontbase2.sql +0 -4
  259. data/test/fixtures/db_definitions/openbase.drop.sql +0 -2
  260. data/test/fixtures/db_definitions/openbase.sql +0 -318
  261. data/test/fixtures/db_definitions/openbase2.drop.sql +0 -2
  262. data/test/fixtures/db_definitions/openbase2.sql +0 -7
  263. data/test/fixtures/db_definitions/oracle.drop.sql +0 -67
  264. data/test/fixtures/db_definitions/oracle.sql +0 -330
  265. data/test/fixtures/db_definitions/oracle2.drop.sql +0 -2
  266. data/test/fixtures/db_definitions/oracle2.sql +0 -6
  267. data/test/fixtures/db_definitions/postgresql.drop.sql +0 -44
  268. data/test/fixtures/db_definitions/postgresql.sql +0 -292
  269. data/test/fixtures/db_definitions/postgresql2.drop.sql +0 -2
  270. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  271. data/test/fixtures/db_definitions/schema.rb +0 -354
  272. data/test/fixtures/db_definitions/schema2.rb +0 -11
  273. data/test/fixtures/db_definitions/sqlite.drop.sql +0 -33
  274. data/test/fixtures/db_definitions/sqlite.sql +0 -219
  275. data/test/fixtures/db_definitions/sqlite2.drop.sql +0 -2
  276. data/test/fixtures/db_definitions/sqlite2.sql +0 -5
  277. data/test/fixtures/db_definitions/sybase.drop.sql +0 -35
  278. data/test/fixtures/db_definitions/sybase.sql +0 -222
  279. data/test/fixtures/db_definitions/sybase2.drop.sql +0 -4
  280. data/test/fixtures/db_definitions/sybase2.sql +0 -5
  281. data/test/fixtures/developers_projects/david_action_controller +0 -3
  282. data/test/fixtures/developers_projects/david_active_record +0 -3
  283. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  284. data/test/fixtures/person.rb +0 -4
  285. data/test/fixtures/pirate.rb +0 -5
  286. data/test/fixtures/subscribers/first +0 -2
  287. data/test/fixtures/subscribers/second +0 -2
  288. data/test/schema_dumper_test.rb +0 -131
  289. data/test/schema_test_postgresql.rb +0 -64
@@ -0,0 +1,190 @@
1
+ require "cases/helper"
2
+ require 'models/post'
3
+ require 'models/person'
4
+ require 'models/reader'
5
+
6
+ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
7
+ fixtures :posts, :readers, :people
8
+
9
+ def test_associate_existing
10
+ assert_queries(2) { posts(:thinking);people(:david) }
11
+
12
+ assert_queries(1) do
13
+ posts(:thinking).people << people(:david)
14
+ end
15
+
16
+ assert_queries(1) do
17
+ assert posts(:thinking).people.include?(people(:david))
18
+ end
19
+
20
+ assert posts(:thinking).reload.people(true).include?(people(:david))
21
+ end
22
+
23
+ def test_associating_new
24
+ assert_queries(1) { posts(:thinking) }
25
+ new_person = nil # so block binding catches it
26
+
27
+ assert_queries(0) do
28
+ new_person = Person.new :first_name => 'bob'
29
+ end
30
+
31
+ # Associating new records always saves them
32
+ # Thus, 1 query for the new person record, 1 query for the new join table record
33
+ assert_queries(2) do
34
+ posts(:thinking).people << new_person
35
+ end
36
+
37
+ assert_queries(1) do
38
+ assert posts(:thinking).people.include?(new_person)
39
+ end
40
+
41
+ assert posts(:thinking).reload.people(true).include?(new_person)
42
+ end
43
+
44
+ def test_associate_new_by_building
45
+ assert_queries(1) { posts(:thinking) }
46
+
47
+ assert_queries(0) do
48
+ posts(:thinking).people.build(:first_name=>"Bob")
49
+ posts(:thinking).people.new(:first_name=>"Ted")
50
+ end
51
+
52
+ # Should only need to load the association once
53
+ assert_queries(1) do
54
+ assert posts(:thinking).people.collect(&:first_name).include?("Bob")
55
+ assert posts(:thinking).people.collect(&:first_name).include?("Ted")
56
+ end
57
+
58
+ # 2 queries for each new record (1 to save the record itself, 1 for the join model)
59
+ # * 2 new records = 4
60
+ # + 1 query to save the actual post = 5
61
+ assert_queries(5) do
62
+ posts(:thinking).body += '-changed'
63
+ posts(:thinking).save
64
+ end
65
+
66
+ assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Bob")
67
+ assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Ted")
68
+ end
69
+
70
+ def test_delete_association
71
+ assert_queries(2){posts(:welcome);people(:michael); }
72
+
73
+ assert_queries(1) do
74
+ posts(:welcome).people.delete(people(:michael))
75
+ end
76
+
77
+ assert_queries(1) do
78
+ assert posts(:welcome).people.empty?
79
+ end
80
+
81
+ assert posts(:welcome).reload.people(true).empty?
82
+ end
83
+
84
+ def test_replace_association
85
+ assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)}
86
+
87
+ # 1 query to delete the existing reader (michael)
88
+ # 1 query to associate the new reader (david)
89
+ assert_queries(2) do
90
+ posts(:welcome).people = [people(:david)]
91
+ end
92
+
93
+ assert_queries(0){
94
+ assert posts(:welcome).people.include?(people(:david))
95
+ assert !posts(:welcome).people.include?(people(:michael))
96
+ }
97
+
98
+ assert posts(:welcome).reload.people(true).include?(people(:david))
99
+ assert !posts(:welcome).reload.people(true).include?(people(:michael))
100
+ end
101
+
102
+ def test_associate_with_create
103
+ assert_queries(1) { posts(:thinking) }
104
+
105
+ # 1 query for the new record, 1 for the join table record
106
+ # No need to update the actual collection yet!
107
+ assert_queries(2) do
108
+ posts(:thinking).people.create(:first_name=>"Jeb")
109
+ end
110
+
111
+ # *Now* we actually need the collection so it's loaded
112
+ assert_queries(1) do
113
+ assert posts(:thinking).people.collect(&:first_name).include?("Jeb")
114
+ end
115
+
116
+ assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Jeb")
117
+ end
118
+
119
+ def test_associate_with_create_and_no_options
120
+ peeps = posts(:thinking).people.count
121
+ posts(:thinking).people.create(:first_name => 'foo')
122
+ assert_equal peeps + 1, posts(:thinking).people.count
123
+ end
124
+
125
+ def test_associate_with_create_exclamation_and_no_options
126
+ peeps = posts(:thinking).people.count
127
+ posts(:thinking).people.create!(:first_name => 'foo')
128
+ assert_equal peeps + 1, posts(:thinking).people.count
129
+ end
130
+
131
+ def test_clear_associations
132
+ assert_queries(2) { posts(:welcome);posts(:welcome).people(true) }
133
+
134
+ assert_queries(1) do
135
+ posts(:welcome).people.clear
136
+ end
137
+
138
+ assert_queries(0) do
139
+ assert posts(:welcome).people.empty?
140
+ end
141
+
142
+ assert posts(:welcome).reload.people(true).empty?
143
+ end
144
+
145
+ def test_association_callback_ordering
146
+ Post.reset_log
147
+ log = Post.log
148
+ post = posts(:thinking)
149
+
150
+ post.people_with_callbacks << people(:michael)
151
+ assert_equal [
152
+ [:added, :before, "Michael"],
153
+ [:added, :after, "Michael"]
154
+ ], log.last(2)
155
+
156
+ post.people_with_callbacks.push(people(:david), Person.create!(:first_name => "Bob"), Person.new(:first_name => "Lary"))
157
+ assert_equal [
158
+ [:added, :before, "David"],
159
+ [:added, :after, "David"],
160
+ [:added, :before, "Bob"],
161
+ [:added, :after, "Bob"],
162
+ [:added, :before, "Lary"],
163
+ [:added, :after, "Lary"]
164
+ ],log.last(6)
165
+
166
+ post.people_with_callbacks.build(:first_name => "Ted")
167
+ assert_equal [
168
+ [:added, :before, "Ted"],
169
+ [:added, :after, "Ted"]
170
+ ], log.last(2)
171
+
172
+ post.people_with_callbacks.create(:first_name => "Sam")
173
+ assert_equal [
174
+ [:added, :before, "Sam"],
175
+ [:added, :after, "Sam"]
176
+ ], log.last(2)
177
+
178
+ post.people_with_callbacks = [people(:michael),people(:david), Person.new(:first_name => "Julian"), Person.create!(:first_name => "Roger")]
179
+ assert_equal (%w(Ted Bob Sam Lary) * 2).sort, log[-12..-5].collect(&:last).sort
180
+ assert_equal [
181
+ [:added, :before, "Julian"],
182
+ [:added, :after, "Julian"],
183
+ [:added, :before, "Roger"],
184
+ [:added, :after, "Roger"]
185
+ ], log.last(4)
186
+
187
+ post.people_with_callbacks.clear
188
+ assert_equal (%w(Michael David Julian Roger) * 2).sort, log.last(8).collect(&:last).sort
189
+ end
190
+ end
@@ -0,0 +1,323 @@
1
+ require "cases/helper"
2
+ require 'models/developer'
3
+ require 'models/project'
4
+ require 'models/company'
5
+
6
+ class HasOneAssociationsTest < ActiveRecord::TestCase
7
+ fixtures :accounts, :companies, :developers, :projects, :developers_projects
8
+
9
+ def setup
10
+ Account.destroyed_account_ids.clear
11
+ end
12
+
13
+ def test_has_one
14
+ assert_equal companies(:first_firm).account, Account.find(1)
15
+ assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit
16
+ end
17
+
18
+ def test_has_one_cache_nils
19
+ firm = companies(:another_firm)
20
+ assert_queries(1) { assert_nil firm.account }
21
+ assert_queries(0) { assert_nil firm.account }
22
+
23
+ firms = Firm.find(:all, :include => :account)
24
+ assert_queries(0) { firms.each(&:account) }
25
+ end
26
+
27
+ def test_with_select
28
+ assert_equal Firm.find(1).account_with_select.attributes.size, 2
29
+ assert_equal Firm.find(1, :include => :account_with_select).account_with_select.attributes.size, 2
30
+ end
31
+
32
+ def test_can_marshal_has_one_association_with_nil_target
33
+ firm = Firm.new
34
+ assert_nothing_raised do
35
+ assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes
36
+ end
37
+
38
+ firm.account
39
+ assert_nothing_raised do
40
+ assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes
41
+ end
42
+ end
43
+
44
+ def test_proxy_assignment
45
+ company = companies(:first_firm)
46
+ assert_nothing_raised { company.account = company.account }
47
+ end
48
+
49
+ def test_triple_equality
50
+ assert Account === companies(:first_firm).account
51
+ assert companies(:first_firm).account === Account
52
+ end
53
+
54
+ def test_type_mismatch
55
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 }
56
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) }
57
+ end
58
+
59
+ def test_natural_assignment
60
+ apple = Firm.create("name" => "Apple")
61
+ citibank = Account.create("credit_limit" => 10)
62
+ apple.account = citibank
63
+ assert_equal apple.id, citibank.firm_id
64
+ end
65
+
66
+ def test_natural_assignment_to_nil
67
+ old_account_id = companies(:first_firm).account.id
68
+ companies(:first_firm).account = nil
69
+ companies(:first_firm).save
70
+ assert_nil companies(:first_firm).account
71
+ # account is dependent, therefore is destroyed when reference to owner is lost
72
+ assert_raises(ActiveRecord::RecordNotFound) { Account.find(old_account_id) }
73
+ end
74
+
75
+ def test_assignment_without_replacement
76
+ apple = Firm.create("name" => "Apple")
77
+ citibank = Account.create("credit_limit" => 10)
78
+ apple.account = citibank
79
+ assert_equal apple.id, citibank.firm_id
80
+
81
+ hsbc = apple.build_account({ :credit_limit => 20}, false)
82
+ assert_equal apple.id, hsbc.firm_id
83
+ hsbc.save
84
+ assert_equal apple.id, citibank.firm_id
85
+
86
+ nykredit = apple.create_account({ :credit_limit => 30}, false)
87
+ assert_equal apple.id, nykredit.firm_id
88
+ assert_equal apple.id, citibank.firm_id
89
+ assert_equal apple.id, hsbc.firm_id
90
+ end
91
+
92
+ def test_assignment_without_replacement_on_create
93
+ apple = Firm.create("name" => "Apple")
94
+ citibank = Account.create("credit_limit" => 10)
95
+ apple.account = citibank
96
+ assert_equal apple.id, citibank.firm_id
97
+
98
+ hsbc = apple.create_account({:credit_limit => 10}, false)
99
+ assert_equal apple.id, hsbc.firm_id
100
+ hsbc.save
101
+ assert_equal apple.id, citibank.firm_id
102
+ end
103
+
104
+ def test_dependence
105
+ num_accounts = Account.count
106
+
107
+ firm = Firm.find(1)
108
+ assert !firm.account.nil?
109
+ account_id = firm.account.id
110
+ assert_equal [], Account.destroyed_account_ids[firm.id]
111
+
112
+ firm.destroy
113
+ assert_equal num_accounts - 1, Account.count
114
+ assert_equal [account_id], Account.destroyed_account_ids[firm.id]
115
+ end
116
+
117
+ def test_exclusive_dependence
118
+ num_accounts = Account.count
119
+
120
+ firm = ExclusivelyDependentFirm.find(9)
121
+ assert !firm.account.nil?
122
+ account_id = firm.account.id
123
+ assert_equal [], Account.destroyed_account_ids[firm.id]
124
+
125
+ firm.destroy
126
+ assert_equal num_accounts - 1, Account.count
127
+ assert_equal [], Account.destroyed_account_ids[firm.id]
128
+ end
129
+
130
+ def test_dependence_with_nil_associate
131
+ firm = DependentFirm.new(:name => 'nullify')
132
+ firm.save!
133
+ assert_nothing_raised { firm.destroy }
134
+ end
135
+
136
+ def test_succesful_build_association
137
+ firm = Firm.new("name" => "GlobalMegaCorp")
138
+ firm.save
139
+
140
+ account = firm.build_account("credit_limit" => 1000)
141
+ assert account.save
142
+ assert_equal account, firm.account
143
+ end
144
+
145
+ def test_failing_build_association
146
+ firm = Firm.new("name" => "GlobalMegaCorp")
147
+ firm.save
148
+
149
+ account = firm.build_account
150
+ assert !account.save
151
+ assert_equal "can't be empty", account.errors.on("credit_limit")
152
+ end
153
+
154
+ def test_build_association_twice_without_saving_affects_nothing
155
+ count_of_account = Account.count
156
+ firm = Firm.find(:first)
157
+ account1 = firm.build_account("credit_limit" => 1000)
158
+ account2 = firm.build_account("credit_limit" => 2000)
159
+
160
+ assert_equal count_of_account, Account.count
161
+ end
162
+
163
+ def test_create_association
164
+ firm = Firm.create(:name => "GlobalMegaCorp")
165
+ account = firm.create_account(:credit_limit => 1000)
166
+ assert_equal account, firm.reload.account
167
+ end
168
+
169
+ def test_build
170
+ firm = Firm.new("name" => "GlobalMegaCorp")
171
+ firm.save
172
+
173
+ firm.account = account = Account.new("credit_limit" => 1000)
174
+ assert_equal account, firm.account
175
+ assert account.save
176
+ assert_equal account, firm.account
177
+ end
178
+
179
+ def test_build_before_child_saved
180
+ firm = Firm.find(1)
181
+
182
+ account = firm.account.build("credit_limit" => 1000)
183
+ assert_equal account, firm.account
184
+ assert account.new_record?
185
+ assert firm.save
186
+ assert_equal account, firm.account
187
+ assert !account.new_record?
188
+ end
189
+
190
+ def test_build_before_either_saved
191
+ firm = Firm.new("name" => "GlobalMegaCorp")
192
+
193
+ firm.account = account = Account.new("credit_limit" => 1000)
194
+ assert_equal account, firm.account
195
+ assert account.new_record?
196
+ assert firm.save
197
+ assert_equal account, firm.account
198
+ assert !account.new_record?
199
+ end
200
+
201
+ def test_failing_build_association
202
+ firm = Firm.new("name" => "GlobalMegaCorp")
203
+ firm.save
204
+
205
+ firm.account = account = Account.new
206
+ assert_equal account, firm.account
207
+ assert !account.save
208
+ assert_equal account, firm.account
209
+ assert_equal "can't be empty", account.errors.on("credit_limit")
210
+ end
211
+
212
+ def test_create
213
+ firm = Firm.new("name" => "GlobalMegaCorp")
214
+ firm.save
215
+ firm.account = account = Account.create("credit_limit" => 1000)
216
+ assert_equal account, firm.account
217
+ end
218
+
219
+ def test_create_before_save
220
+ firm = Firm.new("name" => "GlobalMegaCorp")
221
+ firm.account = account = Account.create("credit_limit" => 1000)
222
+ assert_equal account, firm.account
223
+ end
224
+
225
+ def test_dependence_with_missing_association
226
+ Account.destroy_all
227
+ firm = Firm.find(1)
228
+ assert firm.account.nil?
229
+ firm.destroy
230
+ end
231
+
232
+ def test_dependence_with_missing_association_and_nullify
233
+ Account.destroy_all
234
+ firm = DependentFirm.find(:first)
235
+ assert firm.account.nil?
236
+ firm.destroy
237
+ end
238
+
239
+ def test_assignment_before_parent_saved
240
+ firm = Firm.new("name" => "GlobalMegaCorp")
241
+ firm.account = a = Account.find(1)
242
+ assert firm.new_record?
243
+ assert_equal a, firm.account
244
+ assert firm.save
245
+ assert_equal a, firm.account
246
+ assert_equal a, firm.account(true)
247
+ end
248
+
249
+ def test_finding_with_interpolated_condition
250
+ firm = Firm.find(:first)
251
+ superior = firm.clients.create(:name => 'SuperiorCo')
252
+ superior.rating = 10
253
+ superior.save
254
+ assert_equal 10, firm.clients_with_interpolated_conditions.first.rating
255
+ end
256
+
257
+ def test_assignment_before_child_saved
258
+ firm = Firm.find(1)
259
+ firm.account = a = Account.new("credit_limit" => 1000)
260
+ assert !a.new_record?
261
+ assert_equal a, firm.account
262
+ assert_equal a, firm.account
263
+ assert_equal a, firm.account(true)
264
+ end
265
+
266
+ def test_save_fails_for_invalid_has_one
267
+ firm = Firm.find(:first)
268
+ assert firm.valid?
269
+
270
+ firm.account = Account.new
271
+
272
+ assert !firm.account.valid?
273
+ assert !firm.valid?
274
+ assert !firm.save
275
+ assert_equal "is invalid", firm.errors.on("account")
276
+ end
277
+
278
+ def test_assignment_before_either_saved
279
+ firm = Firm.new("name" => "GlobalMegaCorp")
280
+ firm.account = a = Account.new("credit_limit" => 1000)
281
+ assert firm.new_record?
282
+ assert a.new_record?
283
+ assert_equal a, firm.account
284
+ assert firm.save
285
+ assert !firm.new_record?
286
+ assert !a.new_record?
287
+ assert_equal a, firm.account
288
+ assert_equal a, firm.account(true)
289
+ end
290
+
291
+ def test_not_resaved_when_unchanged
292
+ firm = Firm.find(:first, :include => :account)
293
+ firm.name += '-changed'
294
+ assert_queries(1) { firm.save! }
295
+
296
+ firm = Firm.find(:first)
297
+ firm.account = Account.find(:first)
298
+ assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! }
299
+
300
+ firm = Firm.find(:first).clone
301
+ firm.account = Account.find(:first)
302
+ assert_queries(2) { firm.save! }
303
+
304
+ firm = Firm.find(:first).clone
305
+ firm.account = Account.find(:first).clone
306
+ assert_queries(2) { firm.save! }
307
+ end
308
+
309
+ def test_save_still_works_after_accessing_nil_has_one
310
+ jp = Company.new :name => 'Jaded Pixel'
311
+ jp.dummy_account.nil?
312
+
313
+ assert_nothing_raised do
314
+ jp.save!
315
+ end
316
+ end
317
+
318
+ def test_cant_save_readonly_association
319
+ assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_firm).readonly_account.save! }
320
+ assert companies(:first_firm).readonly_account.readonly?
321
+ end
322
+
323
+ end