activerecord_csi 2.3.5.p6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. data/CHANGELOG +5858 -0
  2. data/README +351 -0
  3. data/RUNNING_UNIT_TESTS +36 -0
  4. data/Rakefile +270 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/performance.rb +162 -0
  7. data/install.rb +30 -0
  8. data/lib/active_record/aggregations.rb +261 -0
  9. data/lib/active_record/association_preload.rb +389 -0
  10. data/lib/active_record/associations/association_collection.rb +475 -0
  11. data/lib/active_record/associations/association_proxy.rb +278 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +76 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +53 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +122 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +266 -0
  17. data/lib/active_record/associations/has_one_association.rb +133 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +37 -0
  19. data/lib/active_record/associations.rb +2241 -0
  20. data/lib/active_record/attribute_methods.rb +388 -0
  21. data/lib/active_record/autosave_association.rb +364 -0
  22. data/lib/active_record/base.rb +3171 -0
  23. data/lib/active_record/batches.rb +81 -0
  24. data/lib/active_record/calculations.rb +311 -0
  25. data/lib/active_record/callbacks.rb +360 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +371 -0
  27. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +139 -0
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +289 -0
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +94 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +722 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +434 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +241 -0
  34. data/lib/active_record/connection_adapters/mysql_adapter.rb +630 -0
  35. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1113 -0
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  37. data/lib/active_record/connection_adapters/sqlite_adapter.rb +453 -0
  38. data/lib/active_record/dirty.rb +183 -0
  39. data/lib/active_record/dynamic_finder_match.rb +41 -0
  40. data/lib/active_record/dynamic_scope_match.rb +25 -0
  41. data/lib/active_record/fixtures.rb +996 -0
  42. data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
  43. data/lib/active_record/locale/en.yml +58 -0
  44. data/lib/active_record/locking/optimistic.rb +148 -0
  45. data/lib/active_record/locking/pessimistic.rb +55 -0
  46. data/lib/active_record/migration.rb +566 -0
  47. data/lib/active_record/named_scope.rb +192 -0
  48. data/lib/active_record/nested_attributes.rb +392 -0
  49. data/lib/active_record/observer.rb +197 -0
  50. data/lib/active_record/query_cache.rb +33 -0
  51. data/lib/active_record/reflection.rb +320 -0
  52. data/lib/active_record/schema.rb +51 -0
  53. data/lib/active_record/schema_dumper.rb +182 -0
  54. data/lib/active_record/serialization.rb +101 -0
  55. data/lib/active_record/serializers/json_serializer.rb +91 -0
  56. data/lib/active_record/serializers/xml_serializer.rb +357 -0
  57. data/lib/active_record/session_store.rb +326 -0
  58. data/lib/active_record/test_case.rb +66 -0
  59. data/lib/active_record/timestamp.rb +71 -0
  60. data/lib/active_record/transactions.rb +235 -0
  61. data/lib/active_record/validations.rb +1135 -0
  62. data/lib/active_record/version.rb +9 -0
  63. data/lib/active_record.rb +84 -0
  64. data/lib/activerecord.rb +2 -0
  65. data/test/assets/example.log +1 -0
  66. data/test/assets/flowers.jpg +0 -0
  67. data/test/cases/aaa_create_tables_test.rb +24 -0
  68. data/test/cases/active_schema_test_mysql.rb +100 -0
  69. data/test/cases/active_schema_test_postgresql.rb +24 -0
  70. data/test/cases/adapter_test.rb +145 -0
  71. data/test/cases/aggregations_test.rb +167 -0
  72. data/test/cases/ar_schema_test.rb +32 -0
  73. data/test/cases/associations/belongs_to_associations_test.rb +425 -0
  74. data/test/cases/associations/callbacks_test.rb +161 -0
  75. data/test/cases/associations/cascaded_eager_loading_test.rb +131 -0
  76. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
  77. data/test/cases/associations/eager_load_nested_include_test.rb +130 -0
  78. data/test/cases/associations/eager_singularization_test.rb +145 -0
  79. data/test/cases/associations/eager_test.rb +834 -0
  80. data/test/cases/associations/extension_test.rb +62 -0
  81. data/test/cases/associations/habtm_join_table_test.rb +56 -0
  82. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +822 -0
  83. data/test/cases/associations/has_many_associations_test.rb +1134 -0
  84. data/test/cases/associations/has_many_through_associations_test.rb +346 -0
  85. data/test/cases/associations/has_one_associations_test.rb +330 -0
  86. data/test/cases/associations/has_one_through_associations_test.rb +209 -0
  87. data/test/cases/associations/inner_join_association_test.rb +93 -0
  88. data/test/cases/associations/join_model_test.rb +712 -0
  89. data/test/cases/associations_test.rb +262 -0
  90. data/test/cases/attribute_methods_test.rb +305 -0
  91. data/test/cases/autosave_association_test.rb +1142 -0
  92. data/test/cases/base_test.rb +2154 -0
  93. data/test/cases/batches_test.rb +61 -0
  94. data/test/cases/binary_test.rb +30 -0
  95. data/test/cases/calculations_test.rb +348 -0
  96. data/test/cases/callbacks_observers_test.rb +38 -0
  97. data/test/cases/callbacks_test.rb +438 -0
  98. data/test/cases/class_inheritable_attributes_test.rb +32 -0
  99. data/test/cases/column_alias_test.rb +17 -0
  100. data/test/cases/column_definition_test.rb +70 -0
  101. data/test/cases/connection_pool_test.rb +25 -0
  102. data/test/cases/connection_test_firebird.rb +8 -0
  103. data/test/cases/connection_test_mysql.rb +64 -0
  104. data/test/cases/copy_table_test_sqlite.rb +80 -0
  105. data/test/cases/database_statements_test.rb +12 -0
  106. data/test/cases/datatype_test_postgresql.rb +204 -0
  107. data/test/cases/date_time_test.rb +37 -0
  108. data/test/cases/default_test_firebird.rb +16 -0
  109. data/test/cases/defaults_test.rb +111 -0
  110. data/test/cases/deprecated_finder_test.rb +30 -0
  111. data/test/cases/dirty_test.rb +316 -0
  112. data/test/cases/finder_respond_to_test.rb +76 -0
  113. data/test/cases/finder_test.rb +1066 -0
  114. data/test/cases/fixtures_test.rb +656 -0
  115. data/test/cases/helper.rb +68 -0
  116. data/test/cases/i18n_test.rb +46 -0
  117. data/test/cases/inheritance_test.rb +262 -0
  118. data/test/cases/invalid_date_test.rb +24 -0
  119. data/test/cases/json_serialization_test.rb +205 -0
  120. data/test/cases/lifecycle_test.rb +193 -0
  121. data/test/cases/locking_test.rb +304 -0
  122. data/test/cases/method_scoping_test.rb +704 -0
  123. data/test/cases/migration_test.rb +1523 -0
  124. data/test/cases/migration_test_firebird.rb +124 -0
  125. data/test/cases/mixin_test.rb +96 -0
  126. data/test/cases/modules_test.rb +81 -0
  127. data/test/cases/multiple_db_test.rb +85 -0
  128. data/test/cases/named_scope_test.rb +361 -0
  129. data/test/cases/nested_attributes_test.rb +581 -0
  130. data/test/cases/pk_test.rb +119 -0
  131. data/test/cases/pooled_connections_test.rb +103 -0
  132. data/test/cases/query_cache_test.rb +123 -0
  133. data/test/cases/readonly_test.rb +107 -0
  134. data/test/cases/reflection_test.rb +194 -0
  135. data/test/cases/reload_models_test.rb +22 -0
  136. data/test/cases/repair_helper.rb +50 -0
  137. data/test/cases/reserved_word_test_mysql.rb +176 -0
  138. data/test/cases/sanitize_test.rb +25 -0
  139. data/test/cases/schema_authorization_test_postgresql.rb +75 -0
  140. data/test/cases/schema_dumper_test.rb +211 -0
  141. data/test/cases/schema_test_postgresql.rb +178 -0
  142. data/test/cases/serialization_test.rb +47 -0
  143. data/test/cases/synonym_test_oracle.rb +17 -0
  144. data/test/cases/timestamp_test.rb +75 -0
  145. data/test/cases/transactions_test.rb +522 -0
  146. data/test/cases/unconnected_test.rb +32 -0
  147. data/test/cases/validations_i18n_test.rb +955 -0
  148. data/test/cases/validations_test.rb +1640 -0
  149. data/test/cases/xml_serialization_test.rb +240 -0
  150. data/test/config.rb +5 -0
  151. data/test/connections/jdbc_jdbcderby/connection.rb +18 -0
  152. data/test/connections/jdbc_jdbch2/connection.rb +18 -0
  153. data/test/connections/jdbc_jdbchsqldb/connection.rb +18 -0
  154. data/test/connections/jdbc_jdbcmysql/connection.rb +26 -0
  155. data/test/connections/jdbc_jdbcpostgresql/connection.rb +26 -0
  156. data/test/connections/jdbc_jdbcsqlite3/connection.rb +25 -0
  157. data/test/connections/native_db2/connection.rb +25 -0
  158. data/test/connections/native_firebird/connection.rb +26 -0
  159. data/test/connections/native_frontbase/connection.rb +27 -0
  160. data/test/connections/native_mysql/connection.rb +25 -0
  161. data/test/connections/native_openbase/connection.rb +21 -0
  162. data/test/connections/native_oracle/connection.rb +27 -0
  163. data/test/connections/native_postgresql/connection.rb +25 -0
  164. data/test/connections/native_sqlite/connection.rb +25 -0
  165. data/test/connections/native_sqlite3/connection.rb +25 -0
  166. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  167. data/test/connections/native_sybase/connection.rb +23 -0
  168. data/test/fixtures/accounts.yml +29 -0
  169. data/test/fixtures/all/developers.yml +0 -0
  170. data/test/fixtures/all/people.csv +0 -0
  171. data/test/fixtures/all/tasks.yml +0 -0
  172. data/test/fixtures/author_addresses.yml +5 -0
  173. data/test/fixtures/author_favorites.yml +4 -0
  174. data/test/fixtures/authors.yml +9 -0
  175. data/test/fixtures/binaries.yml +132 -0
  176. data/test/fixtures/books.yml +7 -0
  177. data/test/fixtures/categories/special_categories.yml +9 -0
  178. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  179. data/test/fixtures/categories.yml +14 -0
  180. data/test/fixtures/categories_ordered.yml +7 -0
  181. data/test/fixtures/categories_posts.yml +23 -0
  182. data/test/fixtures/categorizations.yml +17 -0
  183. data/test/fixtures/clubs.yml +6 -0
  184. data/test/fixtures/comments.yml +59 -0
  185. data/test/fixtures/companies.yml +56 -0
  186. data/test/fixtures/computers.yml +4 -0
  187. data/test/fixtures/courses.yml +7 -0
  188. data/test/fixtures/customers.yml +26 -0
  189. data/test/fixtures/developers.yml +21 -0
  190. data/test/fixtures/developers_projects.yml +17 -0
  191. data/test/fixtures/edges.yml +6 -0
  192. data/test/fixtures/entrants.yml +14 -0
  193. data/test/fixtures/fixture_database.sqlite3 +0 -0
  194. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  195. data/test/fixtures/fk_test_has_fk.yml +3 -0
  196. data/test/fixtures/fk_test_has_pk.yml +2 -0
  197. data/test/fixtures/funny_jokes.yml +10 -0
  198. data/test/fixtures/items.yml +4 -0
  199. data/test/fixtures/jobs.yml +7 -0
  200. data/test/fixtures/legacy_things.yml +3 -0
  201. data/test/fixtures/mateys.yml +4 -0
  202. data/test/fixtures/member_types.yml +6 -0
  203. data/test/fixtures/members.yml +6 -0
  204. data/test/fixtures/memberships.yml +20 -0
  205. data/test/fixtures/minimalistics.yml +2 -0
  206. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  207. data/test/fixtures/mixins.yml +29 -0
  208. data/test/fixtures/movies.yml +7 -0
  209. data/test/fixtures/naked/csv/accounts.csv +1 -0
  210. data/test/fixtures/naked/yml/accounts.yml +1 -0
  211. data/test/fixtures/naked/yml/companies.yml +1 -0
  212. data/test/fixtures/naked/yml/courses.yml +1 -0
  213. data/test/fixtures/organizations.yml +5 -0
  214. data/test/fixtures/owners.yml +7 -0
  215. data/test/fixtures/parrots.yml +27 -0
  216. data/test/fixtures/parrots_pirates.yml +7 -0
  217. data/test/fixtures/people.yml +15 -0
  218. data/test/fixtures/pets.yml +14 -0
  219. data/test/fixtures/pirates.yml +9 -0
  220. data/test/fixtures/posts.yml +52 -0
  221. data/test/fixtures/price_estimates.yml +7 -0
  222. data/test/fixtures/projects.yml +7 -0
  223. data/test/fixtures/readers.yml +9 -0
  224. data/test/fixtures/references.yml +17 -0
  225. data/test/fixtures/reserved_words/distinct.yml +5 -0
  226. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  227. data/test/fixtures/reserved_words/group.yml +14 -0
  228. data/test/fixtures/reserved_words/select.yml +8 -0
  229. data/test/fixtures/reserved_words/values.yml +7 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/sponsors.yml +9 -0
  232. data/test/fixtures/subscribers.yml +7 -0
  233. data/test/fixtures/subscriptions.yml +12 -0
  234. data/test/fixtures/taggings.yml +28 -0
  235. data/test/fixtures/tags.yml +7 -0
  236. data/test/fixtures/tasks.yml +7 -0
  237. data/test/fixtures/topics.yml +42 -0
  238. data/test/fixtures/toys.yml +4 -0
  239. data/test/fixtures/treasures.yml +10 -0
  240. data/test/fixtures/vertices.yml +4 -0
  241. data/test/fixtures/warehouse-things.yml +3 -0
  242. data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
  243. data/test/migrations/decimal/1_give_me_big_numbers.rb +15 -0
  244. data/test/migrations/duplicate/1_people_have_last_names.rb +9 -0
  245. data/test/migrations/duplicate/2_we_need_reminders.rb +12 -0
  246. data/test/migrations/duplicate/3_foo.rb +7 -0
  247. data/test/migrations/duplicate/3_innocent_jointable.rb +12 -0
  248. data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
  249. data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
  250. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +12 -0
  251. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +9 -0
  252. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +12 -0
  253. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +9 -0
  254. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
  255. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
  256. data/test/migrations/missing/1000_people_have_middle_names.rb +9 -0
  257. data/test/migrations/missing/1_people_have_last_names.rb +9 -0
  258. data/test/migrations/missing/3_we_need_reminders.rb +12 -0
  259. data/test/migrations/missing/4_innocent_jointable.rb +12 -0
  260. data/test/migrations/valid/1_people_have_last_names.rb +9 -0
  261. data/test/migrations/valid/2_we_need_reminders.rb +12 -0
  262. data/test/migrations/valid/3_innocent_jointable.rb +12 -0
  263. data/test/models/author.rb +146 -0
  264. data/test/models/auto_id.rb +4 -0
  265. data/test/models/binary.rb +2 -0
  266. data/test/models/bird.rb +3 -0
  267. data/test/models/book.rb +4 -0
  268. data/test/models/categorization.rb +5 -0
  269. data/test/models/category.rb +34 -0
  270. data/test/models/citation.rb +6 -0
  271. data/test/models/club.rb +13 -0
  272. data/test/models/column_name.rb +3 -0
  273. data/test/models/comment.rb +29 -0
  274. data/test/models/company.rb +171 -0
  275. data/test/models/company_in_module.rb +61 -0
  276. data/test/models/computer.rb +3 -0
  277. data/test/models/contact.rb +16 -0
  278. data/test/models/contract.rb +5 -0
  279. data/test/models/course.rb +3 -0
  280. data/test/models/customer.rb +73 -0
  281. data/test/models/default.rb +2 -0
  282. data/test/models/developer.rb +101 -0
  283. data/test/models/edge.rb +5 -0
  284. data/test/models/entrant.rb +3 -0
  285. data/test/models/essay.rb +3 -0
  286. data/test/models/event.rb +3 -0
  287. data/test/models/guid.rb +2 -0
  288. data/test/models/item.rb +7 -0
  289. data/test/models/job.rb +5 -0
  290. data/test/models/joke.rb +3 -0
  291. data/test/models/keyboard.rb +3 -0
  292. data/test/models/legacy_thing.rb +3 -0
  293. data/test/models/matey.rb +4 -0
  294. data/test/models/member.rb +12 -0
  295. data/test/models/member_detail.rb +5 -0
  296. data/test/models/member_type.rb +3 -0
  297. data/test/models/membership.rb +9 -0
  298. data/test/models/minimalistic.rb +2 -0
  299. data/test/models/mixed_case_monkey.rb +3 -0
  300. data/test/models/movie.rb +5 -0
  301. data/test/models/order.rb +4 -0
  302. data/test/models/organization.rb +6 -0
  303. data/test/models/owner.rb +5 -0
  304. data/test/models/parrot.rb +16 -0
  305. data/test/models/person.rb +16 -0
  306. data/test/models/pet.rb +5 -0
  307. data/test/models/pirate.rb +70 -0
  308. data/test/models/post.rb +100 -0
  309. data/test/models/price_estimate.rb +3 -0
  310. data/test/models/project.rb +30 -0
  311. data/test/models/reader.rb +4 -0
  312. data/test/models/reference.rb +4 -0
  313. data/test/models/reply.rb +46 -0
  314. data/test/models/ship.rb +10 -0
  315. data/test/models/ship_part.rb +5 -0
  316. data/test/models/sponsor.rb +4 -0
  317. data/test/models/subject.rb +4 -0
  318. data/test/models/subscriber.rb +8 -0
  319. data/test/models/subscription.rb +4 -0
  320. data/test/models/tag.rb +7 -0
  321. data/test/models/tagging.rb +10 -0
  322. data/test/models/task.rb +3 -0
  323. data/test/models/topic.rb +80 -0
  324. data/test/models/toy.rb +6 -0
  325. data/test/models/treasure.rb +8 -0
  326. data/test/models/vertex.rb +9 -0
  327. data/test/models/warehouse_thing.rb +5 -0
  328. data/test/schema/mysql_specific_schema.rb +24 -0
  329. data/test/schema/postgresql_specific_schema.rb +114 -0
  330. data/test/schema/schema.rb +493 -0
  331. data/test/schema/schema2.rb +6 -0
  332. data/test/schema/sqlite_specific_schema.rb +25 -0
  333. metadata +420 -0
@@ -0,0 +1,389 @@
1
+ module ActiveRecord
2
+ # See ActiveRecord::AssociationPreload::ClassMethods for documentation.
3
+ module AssociationPreload #:nodoc:
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # Implements the details of eager loading of ActiveRecord associations.
9
+ # Application developers should not use this module directly.
10
+ #
11
+ # ActiveRecord::Base is extended with this module. The source code in
12
+ # ActiveRecord::Base references methods defined in this module.
13
+ #
14
+ # Note that 'eager loading' and 'preloading' are actually the same thing.
15
+ # However, there are two different eager loading strategies.
16
+ #
17
+ # The first one is by using table joins. This was only strategy available
18
+ # prior to Rails 2.1. Suppose that you have an Author model with columns
19
+ # 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
20
+ # this strategy, ActiveRecord would try to retrieve all data for an author
21
+ # and all of its books via a single query:
22
+ #
23
+ # SELECT * FROM authors
24
+ # LEFT OUTER JOIN books ON authors.id = books.id
25
+ # WHERE authors.name = 'Ken Akamatsu'
26
+ #
27
+ # However, this could result in many rows that contain redundant data. After
28
+ # having received the first row, we already have enough data to instantiate
29
+ # the Author object. In all subsequent rows, only the data for the joined
30
+ # 'books' table is useful; the joined 'authors' data is just redundant, and
31
+ # processing this redundant data takes memory and CPU time. The problem
32
+ # quickly becomes worse and worse as the level of eager loading increases
33
+ # (i.e. if ActiveRecord is to eager load the associations' assocations as
34
+ # well).
35
+ #
36
+ # The second strategy is to use multiple database queries, one for each
37
+ # level of association. Since Rails 2.1, this is the default strategy. In
38
+ # situations where a table join is necessary (e.g. when the +:conditions+
39
+ # option references an association's column), it will fallback to the table
40
+ # join strategy.
41
+ #
42
+ # See also ActiveRecord::Associations::ClassMethods, which explains eager
43
+ # loading in a more high-level (application developer-friendly) manner.
44
+ module ClassMethods
45
+ protected
46
+
47
+ # Eager loads the named associations for the given ActiveRecord record(s).
48
+ #
49
+ # In this description, 'association name' shall refer to the name passed
50
+ # to an association creation method. For example, a model that specifies
51
+ # <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
52
+ # names +:author+ and +:buyers+.
53
+ #
54
+ # == Parameters
55
+ # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
56
+ # i.e. +records+ itself may also contain arrays of records. In any case,
57
+ # +preload_associations+ will preload the associations all records by
58
+ # flattening +records+.
59
+ #
60
+ # +associations+ specifies one or more associations that you want to
61
+ # preload. It may be:
62
+ # - a Symbol or a String which specifies a single association name. For
63
+ # example, specifiying +:books+ allows this method to preload all books
64
+ # for an Author.
65
+ # - an Array which specifies multiple association names. This array
66
+ # is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
67
+ # allows this method to preload an author's avatar as well as all of his
68
+ # books.
69
+ # - a Hash which specifies multiple association names, as well as
70
+ # association names for the to-be-preloaded association objects. For
71
+ # example, specifying <tt>{ :author => :avatar }</tt> will preload a
72
+ # book's author, as well as that author's avatar.
73
+ #
74
+ # +:associations+ has the same format as the +:include+ option for
75
+ # <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
76
+ #
77
+ # :books
78
+ # [ :books, :author ]
79
+ # { :author => :avatar }
80
+ # [ :books, { :author => :avatar } ]
81
+ #
82
+ # +preload_options+ contains options that will be passed to ActiveRecord#find
83
+ # (which is called under the hood for preloading records). But it is passed
84
+ # only one level deep in the +associations+ argument, i.e. it's not passed
85
+ # to the child associations when +associations+ is a Hash.
86
+ def preload_associations(records, associations, preload_options={})
87
+ records = [records].flatten.compact.uniq
88
+ return if records.empty?
89
+ case associations
90
+ when Array then associations.each {|association| preload_associations(records, association, preload_options)}
91
+ when Symbol, String then preload_one_association(records, associations.to_sym, preload_options)
92
+ when Hash then
93
+ associations.each do |parent, child|
94
+ raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol)
95
+ preload_associations(records, parent, preload_options)
96
+ reflection = reflections[parent]
97
+ parents = records.map {|record| record.send(reflection.name)}.flatten.compact
98
+ unless parents.empty?
99
+ parents.first.class.preload_associations(parents, child)
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ # Preloads a specific named association for the given records. This is
108
+ # called by +preload_associations+ as its base case.
109
+ def preload_one_association(records, association, preload_options={})
110
+ class_to_reflection = {}
111
+ # Not all records have the same class, so group then preload
112
+ # group on the reflection itself so that if various subclass share the same association then we do not split them
113
+ # unnecessarily
114
+ records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
115
+ raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
116
+
117
+ # 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
118
+ # the following could call 'preload_belongs_to_association',
119
+ # 'preload_has_many_association', etc.
120
+ send("preload_#{reflection.macro}_association", records, reflection, preload_options)
121
+ end
122
+ end
123
+
124
+ def add_preloaded_records_to_collection(parent_records, reflection_name, associated_record)
125
+ parent_records.each do |parent_record|
126
+ association_proxy = parent_record.send(reflection_name)
127
+ association_proxy.loaded
128
+ association_proxy.target.push(*[associated_record].flatten)
129
+ end
130
+ end
131
+
132
+ def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
133
+ parent_records.each do |parent_record|
134
+ parent_record.send("set_#{reflection_name}_target", associated_record)
135
+ end
136
+ end
137
+
138
+ def set_association_collection_records(id_to_record_map, reflection_name, associated_records, key)
139
+ associated_records.each do |associated_record|
140
+ mapped_records = id_to_record_map[associated_record[key].to_s]
141
+ add_preloaded_records_to_collection(mapped_records, reflection_name, associated_record)
142
+ end
143
+ end
144
+
145
+ def set_association_single_records(id_to_record_map, reflection_name, associated_records, key)
146
+ seen_keys = {}
147
+ associated_records.each do |associated_record|
148
+ #this is a has_one or belongs_to: there should only be one record.
149
+ #Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please
150
+ # only one row per distinct foo_id' so this where we enforce that
151
+ next if seen_keys[associated_record[key].to_s]
152
+ seen_keys[associated_record[key].to_s] = true
153
+ mapped_records = id_to_record_map[associated_record[key].to_s]
154
+ mapped_records.each do |mapped_record|
155
+ mapped_record.send("set_#{reflection_name}_target", associated_record)
156
+ end
157
+ end
158
+ end
159
+
160
+ # Given a collection of ActiveRecord objects, constructs a Hash which maps
161
+ # the objects' IDs to the relevant objects. Returns a 2-tuple
162
+ # <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash,
163
+ # and +ids+ is an Array of record IDs.
164
+ def construct_id_map(records, primary_key=nil)
165
+ id_to_record_map = {}
166
+ ids = []
167
+ records.each do |record|
168
+ primary_key ||= record.class.primary_key
169
+ ids << record[primary_key]
170
+ mapped_records = (id_to_record_map[ids.last.to_s] ||= [])
171
+ mapped_records << record
172
+ end
173
+ ids.uniq!
174
+ return id_to_record_map, ids
175
+ end
176
+
177
+ def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
178
+ table_name = reflection.klass.quoted_table_name
179
+ id_to_record_map, ids = construct_id_map(records)
180
+ records.each {|record| record.send(reflection.name).loaded}
181
+ options = reflection.options
182
+
183
+ conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
184
+ conditions << append_conditions(reflection, preload_options)
185
+
186
+ associated_records = reflection.klass.with_exclusive_scope do
187
+ reflection.klass.find(:all, :conditions => [conditions, ids],
188
+ :include => options[:include],
189
+ :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
190
+ :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
191
+ :order => options[:order])
192
+ end
193
+ set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
194
+ end
195
+
196
+ def preload_has_one_association(records, reflection, preload_options={})
197
+ return if records.first.send("loaded_#{reflection.name}?")
198
+ id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
199
+ options = reflection.options
200
+ records.each {|record| record.send("set_#{reflection.name}_target", nil)}
201
+ if options[:through]
202
+ through_records = preload_through_records(records, reflection, options[:through])
203
+ through_reflection = reflections[options[:through]]
204
+ through_primary_key = through_reflection.primary_key_name
205
+ unless through_records.empty?
206
+ source = reflection.source_reflection.name
207
+ through_records.first.class.preload_associations(through_records, source)
208
+ if through_reflection.macro == :belongs_to
209
+ rev_id_to_record_map, rev_ids = construct_id_map(records, through_primary_key)
210
+ rev_primary_key = through_reflection.klass.primary_key
211
+ through_records.each do |through_record|
212
+ add_preloaded_record_to_collection(rev_id_to_record_map[through_record[rev_primary_key].to_s],
213
+ reflection.name, through_record.send(source))
214
+ end
215
+ else
216
+ through_records.each do |through_record|
217
+ add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
218
+ reflection.name, through_record.send(source))
219
+ end
220
+ end
221
+ end
222
+ else
223
+ set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
224
+ end
225
+ end
226
+
227
+ def preload_has_many_association(records, reflection, preload_options={})
228
+ return if records.first.send(reflection.name).loaded?
229
+ options = reflection.options
230
+
231
+ primary_key_name = reflection.through_reflection_primary_key_name
232
+ id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
233
+ records.each {|record| record.send(reflection.name).loaded}
234
+
235
+ if options[:through]
236
+ through_records = preload_through_records(records, reflection, options[:through])
237
+ through_reflection = reflections[options[:through]]
238
+ unless through_records.empty?
239
+ source = reflection.source_reflection.name
240
+ through_records.first.class.preload_associations(through_records, source, options)
241
+ through_records.each do |through_record|
242
+ through_record_id = through_record[reflection.through_reflection_primary_key].to_s
243
+ add_preloaded_records_to_collection(id_to_record_map[through_record_id], reflection.name, through_record.send(source))
244
+ end
245
+ end
246
+
247
+ else
248
+ set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
249
+ reflection.primary_key_name)
250
+ end
251
+ end
252
+
253
+ def preload_through_records(records, reflection, through_association)
254
+ through_reflection = reflections[through_association]
255
+ through_primary_key = through_reflection.primary_key_name
256
+
257
+ if reflection.options[:source_type]
258
+ interface = reflection.source_reflection.options[:foreign_type]
259
+ preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]}
260
+
261
+ records.compact!
262
+ records.first.class.preload_associations(records, through_association, preload_options)
263
+
264
+ # Dont cache the association - we would only be caching a subset
265
+ through_records = []
266
+ records.each do |record|
267
+ proxy = record.send(through_association)
268
+
269
+ if proxy.respond_to?(:target)
270
+ through_records << proxy.target
271
+ proxy.reset
272
+ else # this is a has_one :through reflection
273
+ through_records << proxy if proxy
274
+ end
275
+ end
276
+ through_records.flatten!
277
+ else
278
+ records.first.class.preload_associations(records, through_association)
279
+ through_records = records.map {|record| record.send(through_association)}.flatten
280
+ end
281
+ through_records.compact!
282
+ through_records
283
+ end
284
+
285
+ def preload_belongs_to_association(records, reflection, preload_options={})
286
+ return if records.first.send("loaded_#{reflection.name}?")
287
+ options = reflection.options
288
+ primary_key_name = reflection.primary_key_name
289
+
290
+ if options[:polymorphic]
291
+ polymorph_type = options[:foreign_type]
292
+ klasses_and_ids = {}
293
+
294
+ # Construct a mapping from klass to a list of ids to load and a mapping of those ids back to their parent_records
295
+ records.each do |record|
296
+ if klass = record.send(polymorph_type)
297
+ klass_id = record.send(primary_key_name)
298
+ if klass_id
299
+ id_map = klasses_and_ids[klass] ||= {}
300
+ id_list_for_klass_id = (id_map[klass_id.to_s] ||= [])
301
+ id_list_for_klass_id << record
302
+ end
303
+ end
304
+ end
305
+ klasses_and_ids = klasses_and_ids.to_a
306
+ else
307
+ id_map = {}
308
+ records.each do |record|
309
+ key = record.send(primary_key_name)
310
+ if key
311
+ mapped_records = (id_map[key.to_s] ||= [])
312
+ mapped_records << record
313
+ end
314
+ end
315
+ klasses_and_ids = [[reflection.klass.name, id_map]]
316
+ end
317
+
318
+ klasses_and_ids.each do |klass_and_id|
319
+ klass_name, id_map = *klass_and_id
320
+ next if id_map.empty?
321
+ klass = klass_name.constantize
322
+
323
+ table_name = klass.quoted_table_name
324
+ primary_key = klass.primary_key
325
+ column_type = klass.columns.detect{|c| c.name == primary_key}.type
326
+ ids = id_map.keys.map do |id|
327
+ if column_type == :integer
328
+ id.to_i
329
+ elsif column_type == :float
330
+ id.to_f
331
+ else
332
+ id
333
+ end
334
+ end
335
+ conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
336
+ conditions << append_conditions(reflection, preload_options)
337
+ associated_records = klass.with_exclusive_scope do
338
+ klass.find(:all, :conditions => [conditions, ids],
339
+ :include => options[:include],
340
+ :select => options[:select],
341
+ :joins => options[:joins],
342
+ :order => options[:order])
343
+ end
344
+ set_association_single_records(id_map, reflection.name, associated_records, primary_key)
345
+ end
346
+ end
347
+
348
+ def find_associated_records(ids, reflection, preload_options)
349
+ options = reflection.options
350
+ table_name = reflection.klass.quoted_table_name
351
+
352
+ if interface = reflection.options[:as]
353
+ conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"
354
+ else
355
+ foreign_key = reflection.primary_key_name
356
+ conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}"
357
+ end
358
+
359
+ conditions << append_conditions(reflection, preload_options)
360
+
361
+ reflection.klass.with_exclusive_scope do
362
+ reflection.klass.find(:all,
363
+ :select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
364
+ :include => preload_options[:include] || options[:include],
365
+ :conditions => [conditions, ids],
366
+ :joins => options[:joins],
367
+ :group => preload_options[:group] || options[:group],
368
+ :order => preload_options[:order] || options[:order])
369
+ end
370
+ end
371
+
372
+
373
+ def interpolate_sql_for_preload(sql)
374
+ instance_eval("%@#{sql.gsub('@', '\@')}@")
375
+ end
376
+
377
+ def append_conditions(reflection, preload_options)
378
+ sql = ""
379
+ sql << " AND (#{interpolate_sql_for_preload(reflection.sanitized_conditions)})" if reflection.sanitized_conditions
380
+ sql << " AND (#{sanitize_sql preload_options[:conditions]})" if preload_options[:conditions]
381
+ sql
382
+ end
383
+
384
+ def in_or_equals_for_ids(ids)
385
+ ids.size > 1 ? "IN (?)" : "= ?"
386
+ end
387
+ end
388
+ end
389
+ end