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
@@ -92,30 +92,39 @@ module ActiveRecord
92
92
  #
93
93
  # == Writing value objects
94
94
  #
95
- # Value objects are immutable and interchangeable objects that represent a given value, such as a +Money+ object representing
96
- # $5. Two +Money+ objects both representing $5 should be equal (through methods such as == and <=> from +Comparable+ if ranking
97
- # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as +Customer+ can
95
+ # Value objects are immutable and interchangeable objects that represent a given value, such as a Money object representing
96
+ # $5. Two Money objects both representing $5 should be equal (through methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking
97
+ # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as Customer can
98
98
  # easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
99
- # relational unique identifiers (such as primary keys). Normal <tt>ActiveRecord::Base</tt> classes are entity objects.
99
+ # relational unique identifiers (such as primary keys). Normal ActiveRecord::Base classes are entity objects.
100
100
  #
101
- # It's also important to treat the value objects as immutable. Don't allow the +Money+ object to have its amount changed after
102
- # creation. Create a new +Money+ object with the new value instead. This is exemplified by the <tt>Money#exchanged_to</tt> method that
101
+ # It's also important to treat the value objects as immutable. Don't allow the Money object to have its amount changed after
102
+ # creation. Create a new Money object with the new value instead. This is exemplified by the Money#exchanged_to method that
103
103
  # returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
104
104
  # changed through means other than the writer method.
105
105
  #
106
106
  # The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
107
- # change it afterwards will result in a <tt>TypeError</tt>.
107
+ # change it afterwards will result in a ActiveSupport::FrozenObjectError.
108
108
  #
109
109
  # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
110
110
  # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
111
+ #
112
+ # == Finding records by a value object
113
+ #
114
+ # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database by specifying an instance
115
+ # of the value object in the conditions hash. The following example finds all customers with +balance_amount+ equal to 20 and
116
+ # +balance_currency+ equal to "USD":
117
+ #
118
+ # Customer.find(:all, :conditions => {:balance => Money.new(20, "USD")})
119
+ #
111
120
  module ClassMethods
112
121
  # Adds reader and writer methods for manipulating a value object:
113
122
  # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
114
123
  #
115
124
  # Options are:
116
125
  # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
117
- # from the part id. So <tt>composed_of :address</tt> will by default be linked to the +Address+ class, but
118
- # if the real class name is +CompanyAddress+, you'll have to specify it with this option.
126
+ # from the part id. So <tt>composed_of :address</tt> will by default be linked to the Address class, but
127
+ # if the real class name is CompanyAddress, you'll have to specify it with this option.
119
128
  # * <tt>:mapping</tt> - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name
120
129
  # to a constructor parameter on the value class.
121
130
  # * <tt>:allow_nil</tt> - specifies that the aggregate object will not be instantiated when all mapped
@@ -155,7 +164,7 @@ module ActiveRecord
155
164
  if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
156
165
  instance_variable_set("@#{name}", class_name.constantize.new(*mapping.collect {|pair| read_attribute(pair.first)}))
157
166
  end
158
- return instance_variable_get("@#{name}")
167
+ instance_variable_get("@#{name}")
159
168
  end
160
169
  end
161
170
 
@@ -165,11 +174,11 @@ module ActiveRecord
165
174
  module_eval do
166
175
  define_method("#{name}=") do |part|
167
176
  if part.nil? && allow_nil
168
- mapping.each { |pair| @attributes[pair.first] = nil }
177
+ mapping.each { |pair| self[pair.first] = nil }
169
178
  instance_variable_set("@#{name}", nil)
170
179
  else
171
180
  part = conversion.call(part) unless part.is_a?(class_name.constantize) || conversion.nil?
172
- mapping.each { |pair| @attributes[pair.first] = part.send(pair.last) }
181
+ mapping.each { |pair| self[pair.first] = part.send(pair.last) }
173
182
  instance_variable_set("@#{name}", part.freeze)
174
183
  end
175
184
  end
@@ -0,0 +1,277 @@
1
+ module ActiveRecord
2
+ module AssociationPreload #:nodoc:
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ # Loads the named associations for the activerecord record (or records) given
10
+ # preload_options is passed only one level deep: don't pass to the child associations when associations is a Hash
11
+ protected
12
+ def preload_associations(records, associations, preload_options={})
13
+ records = [records].flatten.compact.uniq
14
+ return if records.empty?
15
+ case associations
16
+ when Array then associations.each {|association| preload_associations(records, association, preload_options)}
17
+ when Symbol, String then preload_one_association(records, associations.to_sym, preload_options)
18
+ when Hash then
19
+ associations.each do |parent, child|
20
+ raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol)
21
+ preload_associations(records, parent, preload_options)
22
+ reflection = reflections[parent]
23
+ parents = records.map {|record| record.send(reflection.name)}.flatten
24
+ unless parents.empty? || parents.first.nil?
25
+ parents.first.class.preload_associations(parents, child)
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def preload_one_association(records, association, preload_options={})
34
+ class_to_reflection = {}
35
+ # Not all records have the same class, so group then preload
36
+ # group on the reflection itself so that if various subclass share the same association then we do not split them
37
+ # unncessarily
38
+ records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
39
+ raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
40
+ send("preload_#{reflection.macro}_association", records, reflection, preload_options)
41
+ end
42
+ end
43
+
44
+ def add_preloaded_records_to_collection(parent_records, reflection_name, associated_record)
45
+ parent_records.each do |parent_record|
46
+ association_proxy = parent_record.send(reflection_name)
47
+ association_proxy.loaded
48
+ association_proxy.target.push(*[associated_record].flatten)
49
+ end
50
+ end
51
+
52
+ def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
53
+ parent_records.each do |parent_record|
54
+ association_proxy = parent_record.send(reflection_name)
55
+ association_proxy.loaded
56
+ association_proxy.target = associated_record
57
+ end
58
+ end
59
+
60
+ def set_association_collection_records(id_to_record_map, reflection_name, associated_records, key)
61
+ associated_records.each do |associated_record|
62
+ mapped_records = id_to_record_map[associated_record[key].to_s]
63
+ add_preloaded_records_to_collection(mapped_records, reflection_name, associated_record)
64
+ end
65
+ end
66
+
67
+ def set_association_single_records(id_to_record_map, reflection_name, associated_records, key)
68
+ seen_keys = {}
69
+ associated_records.each do |associated_record|
70
+ #this is a has_one or belongs_to: there should only be one record.
71
+ #Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please
72
+ # only one row per distinct foo_id' so this where we enforce that
73
+ next if seen_keys[associated_record[key].to_s]
74
+ seen_keys[associated_record[key].to_s] = true
75
+ mapped_records = id_to_record_map[associated_record[key].to_s]
76
+ mapped_records.each do |mapped_record|
77
+ mapped_record.send("set_#{reflection_name}_target", associated_record)
78
+ end
79
+ end
80
+ end
81
+
82
+ def construct_id_map(records)
83
+ id_to_record_map = {}
84
+ ids = []
85
+ records.each do |record|
86
+ ids << record.id
87
+ mapped_records = (id_to_record_map[record.id.to_s] ||= [])
88
+ mapped_records << record
89
+ end
90
+ ids.uniq!
91
+ return id_to_record_map, ids
92
+ end
93
+
94
+ def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
95
+ table_name = reflection.klass.quoted_table_name
96
+ id_to_record_map, ids = construct_id_map(records)
97
+ records.each {|record| record.send(reflection.name).loaded}
98
+ options = reflection.options
99
+
100
+ conditions = "t0.#{reflection.primary_key_name} IN (?)"
101
+ conditions << append_conditions(options, preload_options)
102
+
103
+ associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
104
+ :include => options[:include],
105
+ :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} as t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
106
+ :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as _parent_record_id",
107
+ :order => options[:order])
108
+
109
+ set_association_collection_records(id_to_record_map, reflection.name, associated_records, '_parent_record_id')
110
+ end
111
+
112
+ def preload_has_one_association(records, reflection, preload_options={})
113
+ id_to_record_map, ids = construct_id_map(records)
114
+ options = reflection.options
115
+ if options[:through]
116
+ records.each {|record| record.send(reflection.name) && record.send(reflection.name).loaded}
117
+ through_records = preload_through_records(records, reflection, options[:through])
118
+ through_reflection = reflections[options[:through]]
119
+ through_primary_key = through_reflection.primary_key_name
120
+ unless through_records.empty?
121
+ source = reflection.source_reflection.name
122
+ through_records.first.class.preload_associations(through_records, source)
123
+ through_records.each do |through_record|
124
+ add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
125
+ reflection.name, through_record.send(source))
126
+ end
127
+ end
128
+ else
129
+ records.each {|record| record.send("set_#{reflection.name}_target", nil)}
130
+
131
+ set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
132
+ end
133
+ end
134
+
135
+ def preload_has_many_association(records, reflection, preload_options={})
136
+ id_to_record_map, ids = construct_id_map(records)
137
+ records.each {|record| record.send(reflection.name).loaded}
138
+ options = reflection.options
139
+
140
+ if options[:through]
141
+ through_records = preload_through_records(records, reflection, options[:through])
142
+ through_reflection = reflections[options[:through]]
143
+ through_primary_key = through_reflection.primary_key_name
144
+ unless through_records.empty?
145
+ source = reflection.source_reflection.name
146
+ #add conditions from reflection!
147
+ through_records.first.class.preload_associations(through_records, source, reflection.options)
148
+ through_records.each do |through_record|
149
+ add_preloaded_records_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
150
+ reflection.name, through_record.send(source))
151
+ end
152
+ end
153
+ else
154
+ set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
155
+ reflection.primary_key_name)
156
+ end
157
+ end
158
+
159
+ def preload_through_records(records, reflection, through_association)
160
+ through_reflection = reflections[through_association]
161
+ through_primary_key = through_reflection.primary_key_name
162
+
163
+ if reflection.options[:source_type]
164
+ interface = reflection.source_reflection.options[:foreign_type]
165
+ preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]}
166
+
167
+ records.compact!
168
+ records.first.class.preload_associations(records, through_association, preload_options)
169
+
170
+ # Dont cache the association - we would only be caching a subset
171
+ through_records = []
172
+ records.each do |record|
173
+ proxy = record.send(through_association)
174
+
175
+ if proxy.respond_to?(:target)
176
+ through_records << proxy.target
177
+ proxy.reset
178
+ else # this is a has_one :through reflection
179
+ through_records << proxy if proxy
180
+ end
181
+ end
182
+ through_records.flatten!
183
+ else
184
+ records.first.class.preload_associations(records, through_association)
185
+ through_records = records.map {|record| record.send(through_association)}.flatten
186
+ end
187
+ through_records.compact!
188
+ through_records
189
+ end
190
+
191
+ # FIXME: quoting
192
+ def preload_belongs_to_association(records, reflection, preload_options={})
193
+ options = reflection.options
194
+ primary_key_name = reflection.primary_key_name
195
+
196
+ if options[:polymorphic]
197
+ polymorph_type = options[:foreign_type]
198
+ klasses_and_ids = {}
199
+
200
+ # Construct a mapping from klass to a list of ids to load and a mapping of those ids back to their parent_records
201
+ records.each do |record|
202
+ if klass = record.send(polymorph_type)
203
+ klass_id = record.send(primary_key_name)
204
+ if klass_id
205
+ id_map = klasses_and_ids[klass] ||= {}
206
+ id_list_for_klass_id = (id_map[klass_id.to_s] ||= [])
207
+ id_list_for_klass_id << record
208
+ end
209
+ end
210
+ end
211
+ klasses_and_ids = klasses_and_ids.to_a
212
+ else
213
+ id_map = {}
214
+ records.each do |record|
215
+ key = record.send(primary_key_name)
216
+ if key
217
+ mapped_records = (id_map[key.to_s] ||= [])
218
+ mapped_records << record
219
+ end
220
+ end
221
+ klasses_and_ids = [[reflection.klass.name, id_map]]
222
+ end
223
+
224
+ klasses_and_ids.each do |klass_and_id|
225
+ klass_name, id_map = *klass_and_id
226
+ klass = klass_name.constantize
227
+
228
+ table_name = klass.quoted_table_name
229
+ primary_key = klass.primary_key
230
+ conditions = "#{table_name}.#{primary_key} IN (?)"
231
+ conditions << append_conditions(options, preload_options)
232
+ associated_records = klass.find(:all, :conditions => [conditions, id_map.keys.uniq],
233
+ :include => options[:include],
234
+ :select => options[:select],
235
+ :joins => options[:joins],
236
+ :order => options[:order])
237
+ set_association_single_records(id_map, reflection.name, associated_records, primary_key)
238
+ end
239
+ end
240
+
241
+ def find_associated_records(ids, reflection, preload_options)
242
+ options = reflection.options
243
+ table_name = reflection.klass.quoted_table_name
244
+
245
+ if interface = reflection.options[:as]
246
+ conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.name.demodulize}'"
247
+ else
248
+ foreign_key = reflection.primary_key_name
249
+ conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} IN (?)"
250
+ end
251
+
252
+ conditions << append_conditions(options, preload_options)
253
+
254
+ reflection.klass.find(:all,
255
+ :select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
256
+ :include => preload_options[:include] || options[:include],
257
+ :conditions => [conditions, ids],
258
+ :joins => options[:joins],
259
+ :group => preload_options[:group] || options[:group],
260
+ :order => preload_options[:order] || options[:order])
261
+ end
262
+
263
+
264
+ def interpolate_sql_for_preload(sql)
265
+ instance_eval("%@#{sql.gsub('@', '\@')}@")
266
+ end
267
+
268
+ def append_conditions(options, preload_options)
269
+ sql = ""
270
+ sql << " AND (#{interpolate_sql_for_preload(sanitize_sql(options[:conditions]))})" if options[:conditions]
271
+ sql << " AND (#{sanitize_sql preload_options[:conditions]})" if preload_options[:conditions]
272
+ sql
273
+ end
274
+
275
+ end
276
+ end
277
+ end
@@ -6,6 +6,7 @@ require 'active_record/associations/has_one_association'
6
6
  require 'active_record/associations/has_many_association'
7
7
  require 'active_record/associations/has_many_through_association'
8
8
  require 'active_record/associations/has_and_belongs_to_many_association'
9
+ require 'active_record/associations/has_one_through_association'
9
10
 
10
11
  module ActiveRecord
11
12
  class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -43,6 +44,11 @@ module ActiveRecord
43
44
  end
44
45
  end
45
46
 
47
+ class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc:
48
+ def initialize(owner, reflection)
49
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
50
+ end
51
+ end
46
52
  class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
47
53
  def initialize(owner, reflection)
48
54
  super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
@@ -104,11 +110,11 @@ module ActiveRecord
104
110
  #
105
111
  # Don't create associations that have the same name as instance methods of ActiveRecord::Base. Since the association
106
112
  # adds a method with that name to its model, it will override the inherited method and break things.
107
- # For instance, #attributes and #connection would be bad choices for association names.
113
+ # For instance, +attributes+ and +connection+ would be bad choices for association names.
108
114
  #
109
115
  # == Auto-generated methods
110
116
  #
111
- # ===Singular associations (one-to-one)
117
+ # === Singular associations (one-to-one)
112
118
  # | | belongs_to |
113
119
  # generated methods | belongs_to | :polymorphic | has_one
114
120
  # ----------------------------------+------------+--------------+---------
@@ -124,34 +130,34 @@ module ActiveRecord
124
130
  # generated methods | habtm | has_many | :through
125
131
  # ----------------------------------+-------+----------+----------
126
132
  # #others | X | X | X
127
- # #others=(other,other,...) | X | X |
133
+ # #others=(other,other,...) | X | X | X
128
134
  # #other_ids | X | X | X
129
- # #other_ids=(id,id,...) | X | X |
135
+ # #other_ids=(id,id,...) | X | X | X
130
136
  # #others<< | X | X | X
131
137
  # #others.push | X | X | X
132
138
  # #others.concat | X | X | X
133
- # #others.build(attributes={}) | X | X |
134
- # #others.create(attributes={}) | X | X |
139
+ # #others.build(attributes={}) | X | X | X
140
+ # #others.create(attributes={}) | X | X | X
135
141
  # #others.create!(attributes={}) | X | X | X
136
142
  # #others.size | X | X | X
137
143
  # #others.length | X | X | X
138
- # #others.count | | X | X
144
+ # #others.count | X | X | X
139
145
  # #others.sum(args*,&block) | X | X | X
140
146
  # #others.empty? | X | X | X
141
- # #others.clear | X | X |
147
+ # #others.clear | X | X | X
142
148
  # #others.delete(other,other,...) | X | X | X
143
149
  # #others.delete_all | X | X |
144
150
  # #others.destroy_all | X | X | X
145
151
  # #others.find(*args) | X | X | X
146
152
  # #others.find_first | X | |
147
- # #others.uniq | X | X |
153
+ # #others.uniq | X | X | X
148
154
  # #others.reset | X | X | X
149
155
  #
150
156
  # == Cardinality and associations
151
157
  #
152
- # ActiveRecord associations can be used to describe relations with one-to-one, one-to-many
153
- # and many-to-many cardinality. Each model uses an association to describe its role in
154
- # the relation. In each case, the +belongs_to+ association is used in the model that has
158
+ # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
159
+ # relationships between models. Each model uses an association to describe its role in
160
+ # the relation. The +belongs_to+ association is always used in the model that has
155
161
  # the foreign key.
156
162
  #
157
163
  # === One-to-one
@@ -252,7 +258,7 @@ module ActiveRecord
252
258
  # order to update their primary keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
253
259
  # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment
254
260
  # is cancelled.
255
- # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>#association.build</tt> method (documented below).
261
+ # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>association.build</tt> method (documented below).
256
262
  # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It
257
263
  # does not save the parent either.
258
264
  #
@@ -260,8 +266,8 @@ module ActiveRecord
260
266
  #
261
267
  # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object
262
268
  # (the owner of the collection) is not yet stored in the database.
263
- # * If saving any of the objects being added to a collection (via <tt>#push</tt> or similar) fails, then <tt>#push</tt> returns +false+.
264
- # * You can add an object to a collection without automatically saving it by using the <tt>#collection.build</tt> method (documented below).
269
+ # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) fails, then <tt>push</tt> returns +false+.
270
+ # * You can add an object to a collection without automatically saving it by using the <tt>collection.build</tt> method (documented below).
265
271
  # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically saved when the parent is saved.
266
272
  #
267
273
  # === Association callbacks
@@ -435,9 +441,9 @@ module ActiveRecord
435
441
  #
436
442
  # == Eager loading of associations
437
443
  #
438
- # Eager loading is a way to find objects of a certain class and a number of named associations along with it in a single SQL call. This is
444
+ # Eager loading is a way to find objects of a certain class and a number of named associations. This is
439
445
  # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each need to display their author
440
- # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 1. Example:
446
+ # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 2. Example:
441
447
  #
442
448
  # class Post < ActiveRecord::Base
443
449
  # belongs_to :author
@@ -446,7 +452,7 @@ module ActiveRecord
446
452
  #
447
453
  # Consider the following loop using the class above:
448
454
  #
449
- # for post in Post.find(:all)
455
+ # for post in Post.all
450
456
  # puts "Post: " + post.title
451
457
  # puts "Written by: " + post.author.name
452
458
  # puts "Last comment on: " + post.comments.first.created_on
@@ -456,14 +462,15 @@ module ActiveRecord
456
462
  #
457
463
  # for post in Post.find(:all, :include => :author)
458
464
  #
459
- # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> symbol, so the find will now weave in a join something
460
- # like this: <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Doing so will cut down the number of queries from 201 to 101.
465
+ # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> symbol. After loading the posts, find
466
+ # will collect the +author_id+ from each one and load all the referenced authors with one query. Doing so will cut down the number of queries from 201 to 102.
461
467
  #
462
468
  # We can improve upon the situation further by referencing both associations in the finder with:
463
469
  #
464
470
  # for post in Post.find(:all, :include => [ :author, :comments ])
465
471
  #
466
- # That'll add another join along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt>. And we'll be down to 1 query.
472
+ # This will load all comments with a single query. This reduces the total number of queries to 3. More generally the number of queries
473
+ # will be 1 plus the number of associations named (except if some of the associations are polymorphic +belongs_to+ - see below).
467
474
  #
468
475
  # To include a deep hierarchy of associations, use a hash:
469
476
  #
@@ -476,77 +483,91 @@ module ActiveRecord
476
483
  # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
477
484
  # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
478
485
  #
479
- # Since the eager loading pulls from multiple tables, you'll have to disambiguate any column references in both conditions and orders. So
480
- # <tt>:order => "posts.id DESC"</tt> will work while <tt>:order => "id DESC"</tt> will not. Because eager loading generates the +SELECT+ statement too, the
481
- # <tt>:select</tt> option is ignored.
486
+ # Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case
487
+ # Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example
488
+ #
489
+ # Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
490
+ #
491
+ # will result in a single SQL query with joins along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
492
+ # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions like this can have unintended consequences.
493
+ # In the above example posts with no approved comments are not returned at all, because the conditions apply to the SQL statement as a whole
494
+ # and not just to the association. You must disambiguate column references for this fallback to happen, for example
495
+ # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
496
+ #
497
+ # If you do want eagerload only some members of an association it is usually more natural to <tt>:include</tt> an association
498
+ # which has conditions defined on it:
499
+ #
500
+ # class Post < ActiveRecord::Base
501
+ # has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
502
+ # end
482
503
  #
483
- # You can use eager loading on multiple associations from the same table, but you cannot use those associations in orders and conditions
484
- # as there is currently not any way to disambiguate them. Eager loading will not pull additional attributes on join tables, so "rich
485
- # associations" with +has_and_belongs_to_many+ are not a good fit for eager loading.
504
+ # Post.find(:all, :include => :approved_comments)
505
+ #
506
+ # will load posts and eager load the +approved_comments+ association, which contains only those comments that have been approved.
486
507
  #
487
508
  # When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
488
509
  # before the actual model exists.
489
510
  #
490
- # Eager loading is not possible with polymorphic associations. Given
511
+ # Eager loading is supported with polymorphic associations.
491
512
  #
492
513
  # class Address < ActiveRecord::Base
493
514
  # belongs_to :addressable, :polymorphic => true
494
515
  # end
495
516
  #
496
- # a call that tries to eager load the addressable model
497
- #
498
- # Address.find(:all, :include => :addressable) # INVALID
517
+ # A call that tries to eager load the addressable model
499
518
  #
500
- # will raise <tt>ActiveRecord::EagerLoadPolymorphicError</tt>. The reason is that the parent model's type
501
- # is a column value so its corresponding table name cannot be put in the FROM/JOIN clauses of that early query.
519
+ # Address.find(:all, :include => :addressable)
502
520
  #
503
- # It does work the other way around though: if the <tt>User</tt> model is <tt>addressable</tt> you can eager load
504
- # their addresses with <tt>:include</tt> just fine, every piece needed to construct the query is known beforehand.
521
+ # will execute one query to load the addresses and load the addressables with one query per addressable type.
522
+ # For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of
523
+ # addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback
524
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
525
+ # model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query.
505
526
  #
506
527
  # == Table Aliasing
507
528
  #
508
- # ActiveRecord uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
529
+ # Active Record uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
509
530
  # the standard table name is used. The second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. Indexes are appended
510
531
  # for any more successive uses of the table name.
511
532
  #
512
- # Post.find :all, :include => :comments
513
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ...
514
- # Post.find :all, :include => :special_comments # STI
515
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... AND comments.type = 'SpecialComment'
516
- # Post.find :all, :include => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
517
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments ON ... LEFT OUTER JOIN comments special_comments_posts
533
+ # Post.find :all, :joins => :comments
534
+ # # => SELECT ... FROM posts INNER JOIN comments ON ...
535
+ # Post.find :all, :joins => :special_comments # STI
536
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
537
+ # Post.find :all, :joins => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
538
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
518
539
  #
519
540
  # Acts as tree example:
520
541
  #
521
- # TreeMixin.find :all, :include => :children
522
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
523
- # TreeMixin.find :all, :include => {:children => :parent} # using cascading eager includes
524
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
525
- # LEFT OUTER JOIN parents_mixins ...
526
- # TreeMixin.find :all, :include => {:children => {:parent => :children}}
527
- # # => SELECT ... FROM mixins LEFT OUTER JOIN mixins childrens_mixins ...
528
- # LEFT OUTER JOIN parents_mixins ...
529
- # LEFT OUTER JOIN mixins childrens_mixins_2
542
+ # TreeMixin.find :all, :joins => :children
543
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
544
+ # TreeMixin.find :all, :joins => {:children => :parent}
545
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
546
+ # INNER JOIN parents_mixins ...
547
+ # TreeMixin.find :all, :joins => {:children => {:parent => :children}}
548
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
549
+ # INNER JOIN parents_mixins ...
550
+ # INNER JOIN mixins childrens_mixins_2
530
551
  #
531
552
  # Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
532
553
  #
533
- # Post.find :all, :include => :categories
534
- # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
535
- # Post.find :all, :include => {:categories => :posts}
536
- # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
537
- # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
538
- # Post.find :all, :include => {:categories => {:posts => :categories}}
539
- # # => SELECT ... FROM posts LEFT OUTER JOIN categories_posts ... LEFT OUTER JOIN categories ...
540
- # LEFT OUTER JOIN categories_posts posts_categories_join LEFT OUTER JOIN posts posts_categories
541
- # LEFT OUTER JOIN categories_posts categories_posts_join LEFT OUTER JOIN categories categories_posts
554
+ # Post.find :all, :joins => :categories
555
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
556
+ # Post.find :all, :joins => {:categories => :posts}
557
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
558
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
559
+ # Post.find :all, :joins => {:categories => {:posts => :categories}}
560
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
561
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
562
+ # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
542
563
  #
543
564
  # If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table names will take precedence over the eager associations:
544
565
  #
545
- # Post.find :all, :include => :comments, :joins => "inner join comments ..."
546
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments_posts ON ... INNER JOIN comments ...
547
- # Post.find :all, :include => [:comments, :special_comments], :joins => "inner join comments ..."
548
- # # => SELECT ... FROM posts LEFT OUTER JOIN comments comments_posts ON ...
549
- # LEFT OUTER JOIN comments special_comments_posts ...
566
+ # Post.find :all, :joins => :comments, :joins => "inner join comments ..."
567
+ # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
568
+ # Post.find :all, :joins => [:comments, :special_comments], :joins => "inner join comments ..."
569
+ # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
570
+ # INNER JOIN comments special_comments_posts ...
550
571
  # INNER JOIN comments ...
551
572
  #
552
573
  # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
@@ -565,7 +586,7 @@ module ActiveRecord
565
586
  # end
566
587
  # end
567
588
  #
568
- # When <tt>Firm#clients</tt> is called, it will in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
589
+ # When Firm#clients is called, it will in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
569
590
  # with a class in another module scope, this can be done by specifying the complete class name. Example:
570
591
  #
571
592
  # module MyApplication
@@ -593,28 +614,28 @@ module ActiveRecord
593
614
  # Adds the following methods for retrieval and query of collections of associated objects:
594
615
  # +collection+ is replaced with the symbol passed as the first argument, so
595
616
  # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
596
- # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
617
+ # * <tt>collection(force_reload = false)</tt> - Returns an array of all the associated objects.
597
618
  # An empty array is returned if none are found.
598
- # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
599
- # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by setting their foreign keys to NULL.
619
+ # * <tt>collection<<(object, ...)</tt> - Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
620
+ # * <tt>collection.delete(object, ...)</tt> - Removes one or more objects from the collection by setting their foreign keys to +NULL+.
600
621
  # This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model.
601
- # * <tt>collection=objects</tt> - replaces the collections content by deleting and adding objects as appropriate.
602
- # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids
603
- # * <tt>collection_singular_ids=ids</tt> - replace the collection with the objects identified by the primary keys in +ids+
604
- # * <tt>collection.clear</tt> - removes every object from the collection. This destroys the associated objects if they
622
+ # * <tt>collection=objects</tt> - Replaces the collections content by deleting and adding objects as appropriate.
623
+ # * <tt>collection_singular_ids</tt> - Returns an array of the associated objects' ids
624
+ # * <tt>collection_singular_ids=ids</tt> - Replace the collection with the objects identified by the primary keys in +ids+
625
+ # * <tt>collection.clear</tt> - Removes every object from the collection. This destroys the associated objects if they
605
626
  # are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the database if <tt>:dependent => :delete_all</tt>,
606
- # otherwise sets their foreign keys to NULL.
607
- # * <tt>collection.empty?</tt> - returns +true+ if there are no associated objects.
608
- # * <tt>collection.size</tt> - returns the number of associated objects.
609
- # * <tt>collection.find</tt> - finds an associated object according to the same rules as Base.find.
610
- # * <tt>collection.build(attributes = {}, ...)</tt> - returns one or more new objects of the collection type that have been instantiated
627
+ # otherwise sets their foreign keys to +NULL+.
628
+ # * <tt>collection.empty?</tt> - Returns +true+ if there are no associated objects.
629
+ # * <tt>collection.size</tt> - Returns the number of associated objects.
630
+ # * <tt>collection.find</tt> - Finds an associated object according to the same rules as Base.find.
631
+ # * <tt>collection.build(attributes = {}, ...)</tt> - Returns one or more new objects of the collection type that have been instantiated
611
632
  # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an
612
633
  # associated object already exists, not if it's +nil+!
613
- # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
634
+ # * <tt>collection.create(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated
614
635
  # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
615
636
  # *Note:* This only works if an associated object already exists, not if it's +nil+!
616
637
  #
617
- # Example: A +Firm+ class declares <tt>has_many :clients</tt>, which will add:
638
+ # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
618
639
  # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
619
640
  # * <tt>Firm#clients<<</tt>
620
641
  # * <tt>Firm#clients.delete</tt>
@@ -630,41 +651,45 @@ module ActiveRecord
630
651
  # The declaration can also include an options hash to specialize the behavior of the association.
631
652
  #
632
653
  # Options are:
633
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
634
- # from the association name. So <tt>has_many :products</tt> will by default be linked to the +Product+ class, but
635
- # if the real class name is +SpecialProduct+, you'll have to specify it with this option.
636
- # * <tt>:conditions</tt> - specify the conditions that the associated objects must meet in order to be included as a +WHERE+
637
- # SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>.
638
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
639
- # such as <tt>last_name, first_name DESC</tt>
640
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
641
- # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_many+ association will use +person_id+
642
- # as the default +foreign_key+.
643
- # * <tt>:dependent</tt> - if set to <tt>:destroy</tt> all the associated objects are destroyed
644
- # alongside this object by calling their destroy method. If set to <tt>:delete_all</tt> all associated
645
- # objects are deleted *without* calling their destroy method. If set to <tt>:nullify</tt> all associated
646
- # objects' foreign keys are set to +NULL+ *without* calling their save callbacks.
647
- # * <tt>:finder_sql</tt> - specify a complete SQL statement to fetch the association. This is a good way to go for complex
654
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
655
+ # from the association name. So <tt>has_many :products</tt> will by default be linked to the Product class, but
656
+ # if the real class name is SpecialProduct, you'll have to specify it with this option.
657
+ # * <tt>:conditions</tt> - Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
658
+ # SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from the association are scoped if a hash
659
+ # is used. <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
660
+ # or <tt>@blog.posts.build</tt>.
661
+ # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
662
+ # such as <tt>last_name, first_name DESC</tt>.
663
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
664
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id"
665
+ # as the default <tt>:foreign_key</tt>.
666
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt> all the associated objects are destroyed
667
+ # alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
668
+ # objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
669
+ # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. *Warning:* This option is ignored when also using
670
+ # the <tt>:through</tt> option.
671
+ # * <tt>:finder_sql</tt> - Specify a complete SQL statement to fetch the association. This is a good way to go for complex
648
672
  # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
649
- # * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
673
+ # * <tt>:counter_sql</tt> - Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
650
674
  # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
651
- # * <tt>:extend</tt> - specify a named module for extending the proxy. See "Association extensions".
652
- # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
653
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
654
- # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
655
- # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
656
- # * <tt>:select</tt>: By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
657
- # but not include the joined columns.
658
- # * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>).
659
- # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
675
+ # * <tt>:extend</tt> - Specify a named module for extending the proxy. See "Association extensions".
676
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when the collection is loaded.
677
+ # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
678
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
679
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
680
+ # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
681
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error.
682
+ # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
683
+ # * <tt>:through</tt> - Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
660
684
  # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
661
685
  # or <tt>has_many</tt> association on the join model.
662
- # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
686
+ # * <tt>:source</tt> - Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
663
687
  # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
664
- # <tt>:subscriber</tt> on +Subscription+, unless a <tt>:source</tt> is given.
665
- # * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
688
+ # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
689
+ # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
666
690
  # association is a polymorphic +belongs_to+.
667
- # * <tt>:uniq</tt> - if set to +true+, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
691
+ # * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
692
+ # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
668
693
  #
669
694
  # Option examples:
670
695
  # has_many :comments, :order => "posted_on"
@@ -673,6 +698,7 @@ module ActiveRecord
673
698
  # has_many :tracks, :order => "position", :dependent => :destroy
674
699
  # has_many :comments, :dependent => :nullify
675
700
  # has_many :tags, :as => :taggable
701
+ # has_many :reports, :readonly => true
676
702
  # has_many :subscribers, :through => :subscriptions, :source => :user
677
703
  # has_many :subscribers, :class_name => "Person", :finder_sql =>
678
704
  # 'SELECT DISTINCT people.* ' +
@@ -684,11 +710,12 @@ module ActiveRecord
684
710
 
685
711
  configure_dependency_for_has_many(reflection)
686
712
 
713
+ add_multiple_associated_save_callbacks(reflection.name)
714
+ add_association_callbacks(reflection.name, reflection.options)
715
+
687
716
  if options[:through]
688
- collection_accessor_methods(reflection, HasManyThroughAssociation, false)
717
+ collection_accessor_methods(reflection, HasManyThroughAssociation)
689
718
  else
690
- add_multiple_associated_save_callbacks(reflection.name)
691
- add_association_callbacks(reflection.name, reflection.options)
692
719
  collection_accessor_methods(reflection, HasManyAssociation)
693
720
  end
694
721
  end
@@ -696,14 +723,14 @@ module ActiveRecord
696
723
  # Adds the following methods for retrieval and query of a single associated object:
697
724
  # +association+ is replaced with the symbol passed as the first argument, so
698
725
  # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
699
- # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found.
700
- # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, sets it as the foreign key,
726
+ # * <tt>association(force_reload = false)</tt> - Returns the associated object. +nil+ is returned if none is found.
727
+ # * <tt>association=(associate)</tt> - Assigns the associate object, extracts the primary key, sets it as the foreign key,
701
728
  # and saves the associate object.
702
- # * <tt>association.nil?</tt> - returns +true+ if there is no associated object.
703
- # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
729
+ # * <tt>association.nil?</tt> - Returns +true+ if there is no associated object.
730
+ # * <tt>build_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
704
731
  # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. Note: This ONLY works if
705
732
  # an association already exists. It will NOT work if the association is +nil+.
706
- # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
733
+ # * <tt>create_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
707
734
  # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
708
735
  #
709
736
  # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
@@ -716,60 +743,80 @@ module ActiveRecord
716
743
  # The declaration can also include an options hash to specialize the behavior of the association.
717
744
  #
718
745
  # Options are:
719
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
720
- # from the association name. So <tt>has_one :manager</tt> will by default be linked to the +Manager+ class, but
721
- # if the real class name is +Person+, you'll have to specify it with this option.
722
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+
746
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
747
+ # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
748
+ # if the real class name is Person, you'll have to specify it with this option.
749
+ # * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
723
750
  # SQL fragment, such as <tt>rank = 5</tt>.
724
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
725
- # such as <tt>last_name, first_name DESC</tt>
726
- # * <tt>:dependent</tt> - if set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
751
+ # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
752
+ # such as <tt>last_name, first_name DESC</tt>.
753
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
727
754
  # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to <tt>:nullify</tt>, the associated
728
755
  # object's foreign key is set to +NULL+. Also, association is assigned.
729
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
730
- # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_one+ association will use +person_id+
731
- # as the default +foreign_key+.
732
- # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
733
- # * <tt>:as</tt>: Specifies a polymorphic interface (See <tt>#belongs_to</tt>).
734
- #
756
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
757
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id"
758
+ # as the default <tt>:foreign_key</tt>.
759
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
760
+ # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
761
+ # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
762
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
763
+ # * <tt>:through</tt>: Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
764
+ # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
765
+ # <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model.
766
+ # * <tt>:source</tt> - Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
767
+ # inferred from the association. <tt>has_one :favorite, :through => :favorites</tt> will look for a
768
+ # <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
769
+ # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
770
+ # association is a polymorphic +belongs_to+.
771
+ # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
772
+ #
735
773
  # Option examples:
736
774
  # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
737
775
  # has_one :credit_card, :dependent => :nullify # updates the associated records foreign key value to NULL rather than destroying it
738
776
  # has_one :last_comment, :class_name => "Comment", :order => "posted_on"
739
777
  # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
740
778
  # has_one :attachment, :as => :attachable
779
+ # has_one :boss, :readonly => :true
780
+ # has_one :club, :through => :membership
781
+ # has_one :primary_address, :through => :addressables, :conditions => ["addressable.primary = ?", true], :source => :addressable
741
782
  def has_one(association_id, options = {})
742
- reflection = create_has_one_reflection(association_id, options)
783
+ if options[:through]
784
+ reflection = create_has_one_through_reflection(association_id, options)
785
+ association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation)
786
+ else
787
+ reflection = create_has_one_reflection(association_id, options)
743
788
 
744
- ivar = "@#{reflection.name}"
789
+ ivar = "@#{reflection.name}"
745
790
 
746
- module_eval do
747
- after_save <<-EOF
791
+ method_name = "has_one_after_save_for_#{reflection.name}".to_sym
792
+ define_method(method_name) do
748
793
  association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
749
794
 
750
795
  if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
751
796
  association["#{reflection.primary_key_name}"] = id
752
797
  association.save(true)
753
798
  end
754
- EOF
755
- end
799
+ end
800
+ after_save method_name
756
801
 
757
- association_accessor_methods(reflection, HasOneAssociation)
758
- association_constructor_method(:build, reflection, HasOneAssociation)
759
- association_constructor_method(:create, reflection, HasOneAssociation)
802
+ add_single_associated_save_callbacks(reflection.name)
803
+ association_accessor_methods(reflection, HasOneAssociation)
804
+ association_constructor_method(:build, reflection, HasOneAssociation)
805
+ association_constructor_method(:create, reflection, HasOneAssociation)
760
806
 
761
- configure_dependency_for_has_one(reflection)
807
+ configure_dependency_for_has_one(reflection)
808
+ end
762
809
  end
763
810
 
764
811
  # Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
765
812
  # +association+ is replaced with the symbol passed as the first argument, so
766
813
  # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
767
- # * <tt>association(force_reload = false)</tt> - returns the associated object. +nil+ is returned if none is found.
768
- # * <tt>association=(associate)</tt> - assigns the associate object, extracts the primary key, and sets it as the foreign key.
769
- # * <tt>association.nil?</tt> - returns +true+ if there is no associated object.
770
- # * <tt>build_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
814
+ # * <tt>association(force_reload = false)</tt> - Returns the associated object. +nil+ is returned if none is found.
815
+ # * <tt>association=(associate)</tt> - Assigns the associate object, extracts the primary key, and sets it as the foreign key.
816
+ # * <tt>association.nil?</tt> - Returns +true+ if there is no associated object.
817
+ # * <tt>build_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
771
818
  # with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
772
- # * <tt>create_association(attributes = {})</tt> - returns a new object of the associated type that has been instantiated
819
+ # * <tt>create_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
773
820
  # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
774
821
  #
775
822
  # Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
@@ -782,27 +829,34 @@ module ActiveRecord
782
829
  # The declaration can also include an options hash to specialize the behavior of the association.
783
830
  #
784
831
  # Options are:
785
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
786
- # from the association name. So <tt>has_one :author</tt> will by default be linked to the +Author+ class, but
787
- # if the real class name is +Person+, you'll have to specify it with this option.
788
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+
832
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
833
+ # from the association name. So <tt>has_one :author</tt> will by default be linked to the Author class, but
834
+ # if the real class name is Person, you'll have to specify it with this option.
835
+ # * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
789
836
  # SQL fragment, such as <tt>authorized = 1</tt>.
790
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
791
- # such as <tt>last_name, first_name DESC</tt>
792
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
793
- # of the association with an +_id+ suffix. So a class that defines a +belongs_to :person+ association will use +person_id+ as the default +foreign_key+.
794
- # Similarly, +belongs_to :favorite_person, :class_name => "Person"+ will use a foreign key of +favorite_person_id+.
795
- # * <tt>:counter_cache</tt> - caches the number of belonging objects on the associate class through the use of +increment_counter+
837
+ # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
838
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
839
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
840
+ # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> association will use
841
+ # "person_id" as the default <tt>:foreign_key</tt>. Similarly, <tt>belongs_to :favorite_person, :class_name => "Person"</tt>
842
+ # will use a foreign key of "favorite_person_id".
843
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
844
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. This option should not be specified when
845
+ # <tt>belongs_to</tt> is used in conjunction with a <tt>has_many</tt> relationship on another class because of the potential to leave
846
+ # orphaned records behind.
847
+ # * <tt>:counter_cache</tt> - Caches the number of belonging objects on the associate class through the use of +increment_counter+
796
848
  # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
797
- # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging +Comment+ class)
798
- # is used on the associate class (such as a +Post+ class). You can also specify a custom counter cache column by providing
849
+ # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
850
+ # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing
799
851
  # a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
800
- # Note: Specifying a counter_cache will add it to that model's list of readonly attributes using #attr_readonly.
801
- # * <tt>:include</tt> - specify second-order associations that should be eager loaded when this object is loaded.
802
- # Not allowed if the association is polymorphic.
803
- # * <tt>:polymorphic</tt> - specify this association is a polymorphic association by passing +true+.
852
+ # When creating a counter cache column, the database statement or migration must specify a default value of <tt>0</tt>, failing to do
853
+ # this results in a counter with +NULL+ value, which will never increment.
854
+ # Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
855
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
856
+ # * <tt>:polymorphic</tt> - Specify this association is a polymorphic association by passing +true+.
804
857
  # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
805
- # to the attr_readonly list in the associated classes (e.g. class Post; attr_readonly :comments_count; end).
858
+ # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
859
+ # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
806
860
  #
807
861
  # Option examples:
808
862
  # belongs_to :firm, :foreign_key => "client_of"
@@ -810,6 +864,8 @@ module ActiveRecord
810
864
  # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
811
865
  # :conditions => 'discounts > #{payments_count}'
812
866
  # belongs_to :attachable, :polymorphic => true
867
+ # belongs_to :project, :readonly => true
868
+ # belongs_to :post, :counter_cache => true
813
869
  def belongs_to(association_id, options = {})
814
870
  reflection = create_belongs_to_reflection(association_id, options)
815
871
 
@@ -818,42 +874,42 @@ module ActiveRecord
818
874
  if reflection.options[:polymorphic]
819
875
  association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
820
876
 
821
- module_eval do
822
- before_save <<-EOF
823
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
877
+ method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
878
+ define_method(method_name) do
879
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
824
880
 
825
- if association && association.target
826
- if association.new_record?
827
- association.save(true)
828
- end
881
+ if association && association.target
882
+ if association.new_record?
883
+ association.save(true)
884
+ end
829
885
 
830
- if association.updated?
831
- self["#{reflection.primary_key_name}"] = association.id
832
- self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
833
- end
886
+ if association.updated?
887
+ self["#{reflection.primary_key_name}"] = association.id
888
+ self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
834
889
  end
835
- EOF
890
+ end
836
891
  end
892
+ before_save method_name
837
893
  else
838
894
  association_accessor_methods(reflection, BelongsToAssociation)
839
895
  association_constructor_method(:build, reflection, BelongsToAssociation)
840
896
  association_constructor_method(:create, reflection, BelongsToAssociation)
841
897
 
842
- module_eval do
843
- before_save <<-EOF
844
- association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
898
+ method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
899
+ define_method(method_name) do
900
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
845
901
 
846
- if !association.nil?
847
- if association.new_record?
848
- association.save(true)
849
- end
902
+ if !association.nil?
903
+ if association.new_record?
904
+ association.save(true)
905
+ end
850
906
 
851
- if association.updated?
852
- self["#{reflection.primary_key_name}"] = association.id
853
- end
907
+ if association.updated?
908
+ self["#{reflection.primary_key_name}"] = association.id
854
909
  end
855
- EOF
910
+ end
856
911
  end
912
+ before_save method_name
857
913
  end
858
914
 
859
915
  # Create the callbacks to update counter cache
@@ -862,57 +918,63 @@ module ActiveRecord
862
918
  "#{self.to_s.underscore.pluralize}_count" :
863
919
  options[:counter_cache]
864
920
 
865
- module_eval(
866
- "after_create '#{reflection.name}.class.increment_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
867
- " unless #{reflection.name}.nil?'"
868
- )
921
+ method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
922
+ define_method(method_name) do
923
+ association = send("#{reflection.name}")
924
+ association.class.increment_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
925
+ end
926
+ after_create method_name
869
927
 
870
- module_eval(
871
- "before_destroy '#{reflection.name}.class.decrement_counter(\"#{cache_column}\", #{reflection.primary_key_name})" +
872
- " unless #{reflection.name}.nil?'"
873
- )
928
+ method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
929
+ define_method(method_name) do
930
+ association = send("#{reflection.name}")
931
+ association.class.decrement_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
932
+ end
933
+ before_destroy method_name
874
934
 
875
935
  module_eval(
876
936
  "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
877
937
  )
878
938
  end
939
+
940
+ configure_dependency_for_belongs_to(reflection)
879
941
  end
880
942
 
881
943
  # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
882
- # an option, it is guessed using the lexical order of the class names. So a join between +Developer+ and +Project+
883
- # will give the default join table name of +developers_projects+ because "D" outranks "P". Note that this precedence
884
- # is calculated using the <tt><</tt> operator for <tt>String</tt>. This means that if the strings are of different lengths,
944
+ # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
945
+ # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence
946
+ # is calculated using the <tt><</tt> operator for String. This means that if the strings are of different lengths,
885
947
  # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
886
- # lexical precedence than the shorter one. For example, one would expect the tables <tt>paper_boxes</tt> and <tt>papers</tt>
887
- # to generate a join table name of <tt>papers_paper_boxes</tt> because of the length of the name <tt>paper_boxes</tt>,
888
- # but it in fact generates a join table name of <tt>paper_boxes_papers</tt>. Be aware of this caveat, and use the
889
- # custom <tt>join_table</tt> option if you need to.
948
+ # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
949
+ # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
950
+ # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
951
+ # custom <tt>:join_table</tt> option if you need to.
890
952
  #
891
953
  # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
892
954
  # +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as
893
- # +ReadOnly+ (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any
955
+ # readonly (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any
894
956
  # associations with attributes to a real join model (see introduction).
895
957
  #
896
958
  # Adds the following methods for retrieval and query:
897
959
  # +collection+ is replaced with the symbol passed as the first argument, so
898
960
  # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.
899
- # * <tt>collection(force_reload = false)</tt> - returns an array of all the associated objects.
961
+ # * <tt>collection(force_reload = false)</tt> - Returns an array of all the associated objects.
900
962
  # An empty array is returned if none are found.
901
- # * <tt>collection<<(object, ...)</tt> - adds one or more objects to the collection by creating associations in the join table
963
+ # * <tt>collection<<(object, ...)</tt> - Adds one or more objects to the collection by creating associations in the join table
902
964
  # (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
903
- # * <tt>collection.delete(object, ...)</tt> - removes one or more objects from the collection by removing their associations from the join table.
965
+ # * <tt>collection.delete(object, ...)</tt> - Removes one or more objects from the collection by removing their associations from the join table.
904
966
  # This does not destroy the objects.
905
- # * <tt>collection=objects</tt> - replaces the collection's content by deleting and adding objects as appropriate.
906
- # * <tt>collection_singular_ids</tt> - returns an array of the associated objects' ids
907
- # * <tt>collection_singular_ids=ids</tt> - replace the collection by the objects identified by the primary keys in +ids+
908
- # * <tt>collection.clear</tt> - removes every object from the collection. This does not destroy the objects.
909
- # * <tt>collection.empty?</tt> - returns +true+ if there are no associated objects.
910
- # * <tt>collection.size</tt> - returns the number of associated objects.
911
- # * <tt>collection.find(id)</tt> - finds an associated object responding to the +id+ and that
967
+ # * <tt>collection=objects</tt> - Replaces the collection's content by deleting and adding objects as appropriate.
968
+ # * <tt>collection_singular_ids</tt> - Returns an array of the associated objects' ids.
969
+ # * <tt>collection_singular_ids=ids</tt> - Replace the collection by the objects identified by the primary keys in +ids+.
970
+ # * <tt>collection.clear</tt> - Removes every object from the collection. This does not destroy the objects.
971
+ # * <tt>collection.empty?</tt> - Returns +true+ if there are no associated objects.
972
+ # * <tt>collection.size</tt> - Returns the number of associated objects.
973
+ # * <tt>collection.find(id)</tt> - Finds an associated object responding to the +id+ and that
912
974
  # meets the condition that it has to be associated with this object.
913
- # * <tt>collection.build(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
975
+ # * <tt>collection.build(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated
914
976
  # with +attributes+ and linked to this object through the join table, but has not yet been saved.
915
- # * <tt>collection.create(attributes = {})</tt> - returns a new object of the collection type that has been instantiated
977
+ # * <tt>collection.create(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated
916
978
  # with +attributes+, linked to this object through the join table, and that has already been saved (if it passed the validation).
917
979
  #
918
980
  # Example: A Developer class declares <tt>has_and_belongs_to_many :projects</tt>, which will add:
@@ -931,41 +993,45 @@ module ActiveRecord
931
993
  # The declaration may include an options hash to specialize the behavior of the association.
932
994
  #
933
995
  # Options are:
934
- # * <tt>:class_name</tt> - specify the class name of the association. Use it only if that name can't be inferred
996
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
935
997
  # from the association name. So <tt>has_and_belongs_to_many :projects</tt> will by default be linked to the
936
- # +Project+ class, but if the real class name is +SuperProject+, you'll have to specify it with this option.
937
- # * <tt>:join_table</tt> - specify the name of the join table if the default based on lexical order isn't what you want.
998
+ # Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
999
+ # * <tt>:join_table</tt> - Specify the name of the join table if the default based on lexical order isn't what you want.
938
1000
  # WARNING: If you're overwriting the table name of either class, the +table_name+ method MUST be declared underneath any
939
1001
  # +has_and_belongs_to_many+ declaration in order to work.
940
- # * <tt>:foreign_key</tt> - specify the foreign key used for the association. By default this is guessed to be the name
941
- # of this class in lower-case and +_id+ suffixed. So a +Person+ class that makes a +has_and_belongs_to_many+ association
942
- # will use +person_id+ as the default +foreign_key+.
943
- # * <tt>:association_foreign_key</tt> - specify the association foreign key used for the association. By default this is
944
- # guessed to be the name of the associated class in lower-case and +_id+ suffixed. So if the associated class is +Project+,
945
- # the +has_and_belongs_to_many+ association will use +project_id+ as the default association +foreign_key+.
946
- # * <tt>:conditions</tt> - specify the conditions that the associated object must meet in order to be included as a +WHERE+
947
- # SQL fragment, such as <tt>authorized = 1</tt>.
948
- # * <tt>:order</tt> - specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
1002
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
1003
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association
1004
+ # will use "person_id" as the default <tt>:foreign_key</tt>.
1005
+ # * <tt>:association_foreign_key</tt> - Specify the association foreign key used for the association. By default this is
1006
+ # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is Project,
1007
+ # the +has_and_belongs_to_many+ association will use "project_id" as the default <tt>:association_foreign_key</tt>.
1008
+ # * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
1009
+ # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
1010
+ # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
1011
+ # or <tt>@blog.posts.build</tt>.
1012
+ # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
949
1013
  # such as <tt>last_name, first_name DESC</tt>
950
- # * <tt>:uniq</tt> - if set to +true+, duplicate associated objects will be ignored by accessors and query methods
951
- # * <tt>:finder_sql</tt> - overwrite the default generated SQL statement used to fetch the association with a manual statement
952
- # * <tt>:delete_sql</tt> - overwrite the default generated SQL statement used to remove links between the associated
953
- # classes with a manual statement
954
- # * <tt>:insert_sql</tt> - overwrite the default generated SQL statement used to add links between the associated classes
955
- # with a manual statement
956
- # * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions".
957
- # * <tt>:include</tt> - specify second-order associations that should be eager loaded when the collection is loaded.
958
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
959
- # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
960
- # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
961
- # * <tt>:select</tt>: By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
962
- # but not include the joined columns.
1014
+ # * <tt>:uniq</tt> - If true, duplicate associated objects will be ignored by accessors and query methods.
1015
+ # * <tt>:finder_sql</tt> - Overwrite the default generated SQL statement used to fetch the association with a manual statement
1016
+ # * <tt>:delete_sql</tt> - Overwrite the default generated SQL statement used to remove links between the associated
1017
+ # classes with a manual statement.
1018
+ # * <tt>:insert_sql</tt> - Overwrite the default generated SQL statement used to add links between the associated classes
1019
+ # with a manual statement.
1020
+ # * <tt>:extend</tt> - Anonymous module for extending the proxy, see "Association extensions".
1021
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when the collection is loaded.
1022
+ # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
1023
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
1024
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
1025
+ # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
1026
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
1027
+ # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
963
1028
  #
964
1029
  # Option examples:
965
1030
  # has_and_belongs_to_many :projects
966
1031
  # has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
967
1032
  # has_and_belongs_to_many :nations, :class_name => "Country"
968
1033
  # has_and_belongs_to_many :categories, :join_table => "prods_cats"
1034
+ # has_and_belongs_to_many :categories, :readonly => true
969
1035
  # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
970
1036
  # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
971
1037
  def has_and_belongs_to_many(association_id, options = {}, &extension)
@@ -1033,7 +1099,12 @@ module ActiveRecord
1033
1099
  association = association_proxy_class.new(self, reflection)
1034
1100
  end
1035
1101
 
1036
- association.replace(new_value)
1102
+ if association_proxy_class == HasOneThroughAssociation
1103
+ association.create_through_record(new_value)
1104
+ self.send(reflection.name, new_value)
1105
+ else
1106
+ association.replace(new_value)
1107
+ end
1037
1108
 
1038
1109
  instance_variable_set(ivar, new_value.nil? ? nil : association)
1039
1110
  end
@@ -1085,7 +1156,19 @@ module ActiveRecord
1085
1156
  end
1086
1157
  end
1087
1158
  end
1088
-
1159
+
1160
+ def add_single_associated_save_callbacks(association_name)
1161
+ method_name = "validate_associated_records_for_#{association_name}".to_sym
1162
+ define_method(method_name) do
1163
+ association = instance_variable_get("@#{association_name}")
1164
+ if !association.nil?
1165
+ errors.add "#{association_name}" unless association.target.nil? || association.valid?
1166
+ end
1167
+ end
1168
+
1169
+ validate method_name
1170
+ end
1171
+
1089
1172
  def add_multiple_associated_save_callbacks(association_name)
1090
1173
  method_name = "validate_associated_records_for_#{association_name}".to_sym
1091
1174
  ivar = "@#{association_name}"
@@ -1107,9 +1190,16 @@ module ActiveRecord
1107
1190
  end
1108
1191
 
1109
1192
  validate method_name
1110
- before_save("@new_record_before_save = new_record?; true")
1111
1193
 
1112
- after_callback = <<-end_eval
1194
+ method_name = "before_save_associated_records_for_#{association_name}".to_sym
1195
+ define_method(method_name) do
1196
+ @new_record_before_save = new_record?
1197
+ true
1198
+ end
1199
+ before_save method_name
1200
+
1201
+ method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym
1202
+ define_method(method_name) do
1113
1203
  association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
1114
1204
 
1115
1205
  records_to_save = if @new_record_before_save
@@ -1126,11 +1216,11 @@ module ActiveRecord
1126
1216
 
1127
1217
  # reconstruct the SQL queries now that we know the owner's id
1128
1218
  association.send(:construct_sql) if association.respond_to?(:construct_sql)
1129
- end_eval
1219
+ end
1130
1220
 
1131
1221
  # Doesn't use after_save as that would save associations added in after_create/after_update twice
1132
- after_create(after_callback)
1133
- after_update(after_callback)
1222
+ after_create method_name
1223
+ after_update method_name
1134
1224
  end
1135
1225
 
1136
1226
  def association_constructor_method(constructor, reflection, association_proxy_class)
@@ -1176,7 +1266,11 @@ module ActiveRecord
1176
1266
 
1177
1267
  case reflection.options[:dependent]
1178
1268
  when :destroy
1179
- module_eval "before_destroy '#{reflection.name}.each { |o| o.destroy }'"
1269
+ method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
1270
+ define_method(method_name) do
1271
+ send("#{reflection.name}").each { |o| o.destroy }
1272
+ end
1273
+ before_destroy method_name
1180
1274
  when :delete_all
1181
1275
  module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
1182
1276
  when :nullify
@@ -1191,17 +1285,55 @@ module ActiveRecord
1191
1285
  if reflection.options.include?(:dependent)
1192
1286
  case reflection.options[:dependent]
1193
1287
  when :destroy
1194
- module_eval "before_destroy '#{reflection.name}.destroy unless #{reflection.name}.nil?'"
1288
+ method_name = "has_one_dependent_destroy_for_#{reflection.name}".to_sym
1289
+ define_method(method_name) do
1290
+ association = send("#{reflection.name}")
1291
+ association.destroy unless association.nil?
1292
+ end
1293
+ before_destroy method_name
1195
1294
  when :delete
1196
- module_eval "before_destroy '#{reflection.class_name}.delete(#{reflection.name}.id) unless #{reflection.name}.nil?'"
1295
+ method_name = "has_one_dependent_delete_for_#{reflection.name}".to_sym
1296
+ define_method(method_name) do
1297
+ association = send("#{reflection.name}")
1298
+ association.class.delete(association.id) unless association.nil?
1299
+ end
1300
+ before_destroy method_name
1197
1301
  when :nullify
1198
- module_eval "before_destroy '#{reflection.name}.update_attribute(\"#{reflection.primary_key_name}\", nil) unless #{reflection.name}.nil?'"
1302
+ method_name = "has_one_dependent_nullify_for_#{reflection.name}".to_sym
1303
+ define_method(method_name) do
1304
+ association = send("#{reflection.name}")
1305
+ association.update_attribute("#{reflection.primary_key_name}", nil) unless association.nil?
1306
+ end
1307
+ before_destroy method_name
1199
1308
  else
1200
1309
  raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify (#{reflection.options[:dependent].inspect})"
1201
1310
  end
1202
1311
  end
1203
1312
  end
1204
1313
 
1314
+ def configure_dependency_for_belongs_to(reflection)
1315
+ if reflection.options.include?(:dependent)
1316
+ case reflection.options[:dependent]
1317
+ when :destroy
1318
+ method_name = "belongs_to_dependent_destroy_for_#{reflection.name}".to_sym
1319
+ define_method(method_name) do
1320
+ association = send("#{reflection.name}")
1321
+ association.destroy unless association.nil?
1322
+ end
1323
+ before_destroy method_name
1324
+ when :delete
1325
+ method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym
1326
+ define_method(method_name) do
1327
+ association = send("#{reflection.name}")
1328
+ association.class.delete(association.id) unless association.nil?
1329
+ end
1330
+ before_destroy method_name
1331
+ else
1332
+ raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
1333
+ end
1334
+ end
1335
+ end
1336
+
1205
1337
  def create_has_many_reflection(association_id, options, &extension)
1206
1338
  options.assert_valid_keys(
1207
1339
  :class_name, :table_name, :foreign_key,
@@ -1211,7 +1343,7 @@ module ActiveRecord
1211
1343
  :uniq,
1212
1344
  :finder_sql, :counter_sql,
1213
1345
  :before_add, :after_add, :before_remove, :after_remove,
1214
- :extend
1346
+ :extend, :readonly
1215
1347
  )
1216
1348
 
1217
1349
  options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1221,16 +1353,23 @@ module ActiveRecord
1221
1353
 
1222
1354
  def create_has_one_reflection(association_id, options)
1223
1355
  options.assert_valid_keys(
1224
- :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as
1356
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly
1225
1357
  )
1226
1358
 
1227
1359
  create_reflection(:has_one, association_id, options, self)
1228
1360
  end
1361
+
1362
+ def create_has_one_through_reflection(association_id, options)
1363
+ options.assert_valid_keys(
1364
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type
1365
+ )
1366
+ create_reflection(:has_one, association_id, options, self)
1367
+ end
1229
1368
 
1230
1369
  def create_belongs_to_reflection(association_id, options)
1231
1370
  options.assert_valid_keys(
1232
- :class_name, :foreign_key, :foreign_type, :remote, :conditions, :order, :include, :dependent,
1233
- :counter_cache, :extend, :polymorphic
1371
+ :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
1372
+ :counter_cache, :extend, :polymorphic, :readonly
1234
1373
  )
1235
1374
 
1236
1375
  reflection = create_reflection(:belongs_to, association_id, options, self)
@@ -1249,7 +1388,7 @@ module ActiveRecord
1249
1388
  :uniq,
1250
1389
  :finder_sql, :delete_sql, :insert_sql,
1251
1390
  :before_add, :after_add, :before_remove, :after_remove,
1252
- :extend
1391
+ :extend, :readonly
1253
1392
  )
1254
1393
 
1255
1394
  options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1317,7 +1456,16 @@ module ActiveRecord
1317
1456
 
1318
1457
  def construct_finder_sql_for_association_limiting(options, join_dependency)
1319
1458
  scope = scope(:find)
1320
- is_distinct = !options[:joins].blank? || include_eager_conditions?(options) || include_eager_order?(options)
1459
+
1460
+ # Only join tables referenced in order or conditions since this is particularly slow on the pre-query.
1461
+ tables_from_conditions = conditions_tables(options)
1462
+ tables_from_order = order_tables(options)
1463
+ all_tables = tables_from_conditions + tables_from_order
1464
+ distinct_join_associations = all_tables.uniq.map{|table|
1465
+ join_dependency.joins_for_table_name(table)
1466
+ }.flatten.compact.uniq
1467
+
1468
+ is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
1321
1469
  sql = "SELECT "
1322
1470
  if is_distinct
1323
1471
  sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])
@@ -1327,7 +1475,7 @@ module ActiveRecord
1327
1475
  sql << " FROM #{connection.quote_table_name table_name} "
1328
1476
 
1329
1477
  if is_distinct
1330
- sql << join_dependency.join_associations.collect(&:association_join).join
1478
+ sql << distinct_join_associations.collect(&:association_join).join
1331
1479
  add_joins!(sql, options, scope)
1332
1480
  end
1333
1481
 
@@ -1345,8 +1493,7 @@ module ActiveRecord
1345
1493
  return sanitize_sql(sql)
1346
1494
  end
1347
1495
 
1348
- # Checks if the conditions reference a table other than the current model table
1349
- def include_eager_conditions?(options)
1496
+ def conditions_tables(options)
1350
1497
  # look in both sets of conditions
1351
1498
  conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
1352
1499
  case cond
@@ -1355,19 +1502,37 @@ module ActiveRecord
1355
1502
  else all << cond
1356
1503
  end
1357
1504
  end
1358
- return false unless conditions.any?
1359
- conditions.join(' ').scan(/([\.\w]+)\.\w+/).flatten.any? do |condition_table_name|
1360
- condition_table_name != table_name
1361
- end
1505
+ conditions.join(' ').scan(/([\.\w]+).?\./).flatten
1362
1506
  end
1363
1507
 
1364
- # Checks if the query order references a table other than the current model's table.
1365
- def include_eager_order?(options)
1508
+ def order_tables(options)
1366
1509
  order = options[:order]
1367
- return false unless order
1368
- order.scan(/([\.\w]+)\.\w+/).flatten.any? do |order_table_name|
1369
- order_table_name != table_name
1370
- end
1510
+ return [] unless order && order.is_a?(String)
1511
+ order.scan(/([\.\w]+).?\./).flatten
1512
+ end
1513
+
1514
+ def selects_tables(options)
1515
+ select = options[:select]
1516
+ return [] unless select && select.is_a?(String)
1517
+ select.scan(/"?([\.\w]+)"?.?\./).flatten
1518
+ end
1519
+
1520
+ # Checks if the conditions reference a table other than the current model table
1521
+ def include_eager_conditions?(options, tables = nil)
1522
+ ((tables || conditions_tables(options)) - [table_name]).any?
1523
+ end
1524
+
1525
+ # Checks if the query order references a table other than the current model's table.
1526
+ def include_eager_order?(options, tables = nil)
1527
+ ((tables || order_tables(options)) - [table_name]).any?
1528
+ end
1529
+
1530
+ def include_eager_select?(options)
1531
+ (selects_tables(options) - [table_name]).any?
1532
+ end
1533
+
1534
+ def references_eager_loaded_tables?(options)
1535
+ include_eager_order?(options) || include_eager_conditions?(options) || include_eager_select?(options)
1371
1536
  end
1372
1537
 
1373
1538
  def using_limitable_reflections?(reflections)
@@ -1461,8 +1626,10 @@ module ActiveRecord
1461
1626
  is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
1462
1627
 
1463
1628
  parent_records = records.map do |record|
1464
- next unless record.send(reflection.name)
1465
- is_collection ? record.send(reflection.name).target.uniq! : record.send(reflection.name)
1629
+ descendant = record.send(reflection.name)
1630
+ next unless descendant
1631
+ descendant.target.uniq! if is_collection
1632
+ descendant
1466
1633
  end.flatten.compact
1467
1634
 
1468
1635
  remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty?
@@ -1470,6 +1637,23 @@ module ActiveRecord
1470
1637
  end
1471
1638
  end
1472
1639
 
1640
+ def join_for_table_name(table_name)
1641
+ @joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil
1642
+ end
1643
+
1644
+ def joins_for_table_name(table_name)
1645
+ join = join_for_table_name(table_name)
1646
+ result = nil
1647
+ if join && join.is_a?(JoinAssociation)
1648
+ result = [join]
1649
+ if join.parent && join.parent.is_a?(JoinAssociation)
1650
+ result = joins_for_table_name(join.parent.aliased_table_name) +
1651
+ result
1652
+ end
1653
+ end
1654
+ result
1655
+ end
1656
+
1473
1657
  protected
1474
1658
  def build(associations, parent = nil)
1475
1659
  parent ||= @joins.last
@@ -1600,36 +1784,19 @@ module ActiveRecord
1600
1784
  end
1601
1785
 
1602
1786
  super(reflection.klass)
1787
+ @join_dependency = join_dependency
1603
1788
  @parent = parent
1604
1789
  @reflection = reflection
1605
1790
  @aliased_prefix = "t#{ join_dependency.joins.size }"
1606
- @aliased_table_name = table_name #.tr('.', '_') # start with the table name, sub out any .'s
1607
1791
  @parent_table_name = parent.active_record.table_name
1608
-
1609
- if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{aliased_table_name.downcase}\son}
1610
- join_dependency.table_aliases[aliased_table_name] += 1
1792
+ @aliased_table_name = aliased_table_name_for(table_name)
1793
+
1794
+ if reflection.macro == :has_and_belongs_to_many
1795
+ @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
1611
1796
  end
1612
-
1613
- unless join_dependency.table_aliases[aliased_table_name].zero?
1614
- # if the table name has been used, then use an alias
1615
- @aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}"
1616
- table_index = join_dependency.table_aliases[aliased_table_name]
1617
- join_dependency.table_aliases[aliased_table_name] += 1
1618
- @aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
1619
- else
1620
- join_dependency.table_aliases[aliased_table_name] += 1
1621
- end
1622
-
1623
- if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
1624
- @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name
1625
- unless join_dependency.table_aliases[aliased_join_table_name].zero?
1626
- @aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join"
1627
- table_index = join_dependency.table_aliases[aliased_join_table_name]
1628
- join_dependency.table_aliases[aliased_join_table_name] += 1
1629
- @aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
1630
- else
1631
- join_dependency.table_aliases[aliased_join_table_name] += 1
1632
- end
1797
+
1798
+ if reflection.macro == :has_many && reflection.options[:through]
1799
+ @aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
1633
1800
  end
1634
1801
  end
1635
1802
 
@@ -1766,6 +1933,25 @@ module ActiveRecord
1766
1933
  end
1767
1934
 
1768
1935
  protected
1936
+
1937
+ def aliased_table_name_for(name, suffix = nil)
1938
+ if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{name.downcase}\son}
1939
+ @join_dependency.table_aliases[name] += 1
1940
+ end
1941
+
1942
+ unless @join_dependency.table_aliases[name].zero?
1943
+ # if the table name has been used, then use an alias
1944
+ name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
1945
+ table_index = @join_dependency.table_aliases[name]
1946
+ @join_dependency.table_aliases[name] += 1
1947
+ name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
1948
+ else
1949
+ @join_dependency.table_aliases[name] += 1
1950
+ end
1951
+
1952
+ name
1953
+ end
1954
+
1769
1955
  def pluralize(table_name)
1770
1956
  ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
1771
1957
  end