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,278 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ # This is the root class of all association proxies:
4
+ #
5
+ # AssociationProxy
6
+ # BelongsToAssociation
7
+ # HasOneAssociation
8
+ # BelongsToPolymorphicAssociation
9
+ # AssociationCollection
10
+ # HasAndBelongsToManyAssociation
11
+ # HasManyAssociation
12
+ # HasManyThroughAssociation
13
+ # HasOneThroughAssociation
14
+ #
15
+ # Association proxies in Active Record are middlemen between the object that
16
+ # holds the association, known as the <tt>@owner</tt>, and the actual associated
17
+ # object, known as the <tt>@target</tt>. The kind of association any proxy is
18
+ # about is available in <tt>@reflection</tt>. That's an instance of the class
19
+ # ActiveRecord::Reflection::AssociationReflection.
20
+ #
21
+ # For example, given
22
+ #
23
+ # class Blog < ActiveRecord::Base
24
+ # has_many :posts
25
+ # end
26
+ #
27
+ # blog = Blog.find(:first)
28
+ #
29
+ # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
30
+ # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
31
+ # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
32
+ #
33
+ # This class has most of the basic instance methods removed, and delegates
34
+ # unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a
35
+ # corner case, it even removes the +class+ method and that's why you get
36
+ #
37
+ # blog.posts.class # => Array
38
+ #
39
+ # though the object behind <tt>blog.posts</tt> is not an Array, but an
40
+ # ActiveRecord::Associations::HasManyAssociation.
41
+ #
42
+ # The <tt>@target</tt> object is not \loaded until needed. For example,
43
+ #
44
+ # blog.posts.count
45
+ #
46
+ # is computed directly through SQL and does not trigger by itself the
47
+ # instantiation of the actual post records.
48
+ class AssociationProxy #:nodoc:
49
+ alias_method :proxy_respond_to?, :respond_to?
50
+ alias_method :proxy_extend, :extend
51
+ delegate :to_param, :to => :proxy_target
52
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
53
+
54
+ def initialize(owner, reflection)
55
+ @owner, @reflection = owner, reflection
56
+ Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
57
+ reset
58
+ end
59
+
60
+ # Returns the owner of the proxy.
61
+ def proxy_owner
62
+ @owner
63
+ end
64
+
65
+ # Returns the reflection object that represents the association handled
66
+ # by the proxy.
67
+ def proxy_reflection
68
+ @reflection
69
+ end
70
+
71
+ # Returns the \target of the proxy, same as +target+.
72
+ def proxy_target
73
+ @target
74
+ end
75
+
76
+ # Does the proxy or its \target respond to +symbol+?
77
+ def respond_to?(*args)
78
+ proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
79
+ end
80
+
81
+ # Forwards <tt>===</tt> explicitly to the \target because the instance method
82
+ # removal above doesn't catch it. Loads the \target if needed.
83
+ def ===(other)
84
+ load_target
85
+ other === @target
86
+ end
87
+
88
+ # Returns the name of the table of the related class:
89
+ #
90
+ # post.comments.aliased_table_name # => "comments"
91
+ #
92
+ def aliased_table_name
93
+ @reflection.klass.table_name
94
+ end
95
+
96
+ # Returns the SQL string that corresponds to the <tt>:conditions</tt>
97
+ # option of the macro, if given, or +nil+ otherwise.
98
+ def conditions
99
+ @conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions
100
+ end
101
+ alias :sql_conditions :conditions
102
+
103
+ # Resets the \loaded flag to +false+ and sets the \target to +nil+.
104
+ def reset
105
+ @loaded = false
106
+ @target = nil
107
+ end
108
+
109
+ # Reloads the \target and returns +self+ on success.
110
+ def reload
111
+ reset
112
+ load_target
113
+ self unless @target.nil?
114
+ end
115
+
116
+ # Has the \target been already \loaded?
117
+ def loaded?
118
+ @loaded
119
+ end
120
+
121
+ # Asserts the \target has been loaded setting the \loaded flag to +true+.
122
+ def loaded
123
+ @loaded = true
124
+ end
125
+
126
+ # Returns the target of this proxy, same as +proxy_target+.
127
+ def target
128
+ @target
129
+ end
130
+
131
+ # Sets the target of this proxy to <tt>\target</tt>, and the \loaded flag to +true+.
132
+ def target=(target)
133
+ @target = target
134
+ loaded
135
+ end
136
+
137
+ # Forwards the call to the target. Loads the \target if needed.
138
+ def inspect
139
+ load_target
140
+ @target.inspect
141
+ end
142
+
143
+ def send(method, *args)
144
+ if proxy_respond_to?(method)
145
+ super
146
+ else
147
+ load_target
148
+ @target.send(method, *args)
149
+ end
150
+ end
151
+
152
+ protected
153
+ # Does the association have a <tt>:dependent</tt> option?
154
+ def dependent?
155
+ @reflection.options[:dependent]
156
+ end
157
+
158
+ # Returns a string with the IDs of +records+ joined with a comma, quoted
159
+ # if needed. The result is ready to be inserted into a SQL IN clause.
160
+ #
161
+ # quoted_record_ids(records) # => "23,56,58,67"
162
+ #
163
+ def quoted_record_ids(records)
164
+ records.map { |record| record.quoted_id }.join(',')
165
+ end
166
+
167
+ def interpolate_sql(sql, record = nil)
168
+ @owner.send(:interpolate_sql, sql, record)
169
+ end
170
+
171
+ # Forwards the call to the reflection class.
172
+ def sanitize_sql(sql, table_name = @reflection.klass.quoted_table_name)
173
+ @reflection.klass.send(:sanitize_sql, sql, table_name)
174
+ end
175
+
176
+ # Assigns the ID of the owner to the corresponding foreign key in +record+.
177
+ # If the association is polymorphic the type of the owner is also set.
178
+ def set_belongs_to_association_for(record)
179
+ if @reflection.options[:as]
180
+ record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
181
+ record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
182
+ else
183
+ unless @owner.new_record?
184
+ primary_key = @reflection.options[:primary_key] || :id
185
+ record[@reflection.primary_key_name] = @owner.send(primary_key)
186
+ end
187
+ end
188
+ end
189
+
190
+ # Merges into +options+ the ones coming from the reflection.
191
+ def merge_options_from_reflection!(options)
192
+ options.reverse_merge!(
193
+ :group => @reflection.options[:group],
194
+ :having => @reflection.options[:having],
195
+ :limit => @reflection.options[:limit],
196
+ :offset => @reflection.options[:offset],
197
+ :joins => @reflection.options[:joins],
198
+ :include => @reflection.options[:include],
199
+ :select => @reflection.options[:select],
200
+ :readonly => @reflection.options[:readonly]
201
+ )
202
+ end
203
+
204
+ # Forwards +with_scope+ to the reflection.
205
+ def with_scope(*args, &block)
206
+ @reflection.klass.send :with_scope, *args, &block
207
+ end
208
+
209
+ private
210
+ # Forwards any missing method call to the \target.
211
+ def method_missing(method, *args)
212
+ if load_target
213
+ if @target.respond_to?(method)
214
+ if block_given?
215
+ @target.send(method, *args) { |*block_args| yield(*block_args) }
216
+ else
217
+ @target.send(method, *args)
218
+ end
219
+ else
220
+ super
221
+ end
222
+ end
223
+ end
224
+
225
+ # Loads the \target if needed and returns it.
226
+ #
227
+ # This method is abstract in the sense that it relies on +find_target+,
228
+ # which is expected to be provided by descendants.
229
+ #
230
+ # If the \target is already \loaded it is just returned. Thus, you can call
231
+ # +load_target+ unconditionally to get the \target.
232
+ #
233
+ # ActiveRecord::RecordNotFound is rescued within the method, and it is
234
+ # not reraised. The proxy is \reset and +nil+ is the return value.
235
+ def load_target
236
+ return nil unless defined?(@loaded)
237
+
238
+ if !loaded? and (!@owner.new_record? || foreign_key_present)
239
+ @target = find_target
240
+ end
241
+
242
+ @loaded = true
243
+ @target
244
+ rescue ActiveRecord::RecordNotFound
245
+ reset
246
+ end
247
+
248
+ # Can be overwritten by associations that might have the foreign key
249
+ # available for an association without having the object itself (and
250
+ # still being a new record). Currently, only +belongs_to+ presents
251
+ # this scenario (both vanilla and polymorphic).
252
+ def foreign_key_present
253
+ false
254
+ end
255
+
256
+ # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
257
+ # the kind of the class of the associated objects. Meant to be used as
258
+ # a sanity check when you are about to assign an associated record.
259
+ def raise_on_type_mismatch(record)
260
+ unless record.is_a?(@reflection.klass) || record.is_a?(@reflection.class_name.constantize)
261
+ message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
262
+ raise ActiveRecord::AssociationTypeMismatch, message
263
+ end
264
+ end
265
+
266
+ # Array#flatten has problems with recursive arrays. Going one level
267
+ # deeper solves the majority of the problems.
268
+ def flatten_deeper(array)
269
+ array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten
270
+ end
271
+
272
+ # Returns the ID of the owner, quoted if needed.
273
+ def owner_quoted_id
274
+ @owner.quoted_id
275
+ end
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,76 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class BelongsToAssociation < AssociationProxy #:nodoc:
4
+ def create(attributes = {})
5
+ replace(@reflection.create_association(attributes))
6
+ end
7
+
8
+ def build(attributes = {})
9
+ replace(@reflection.build_association(attributes))
10
+ end
11
+
12
+ def replace(record)
13
+ counter_cache_name = @reflection.counter_cache_column
14
+
15
+ if record.nil?
16
+ if counter_cache_name && !@owner.new_record?
17
+ @reflection.klass.decrement_counter(counter_cache_name, previous_record_id) if @owner[@reflection.primary_key_name]
18
+ end
19
+
20
+ @target = @owner[@reflection.primary_key_name] = nil
21
+ else
22
+ raise_on_type_mismatch(record)
23
+
24
+ if counter_cache_name && !@owner.new_record?
25
+ @reflection.klass.increment_counter(counter_cache_name, record.id)
26
+ @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
27
+ end
28
+
29
+ @target = (AssociationProxy === record ? record.target : record)
30
+ @owner[@reflection.primary_key_name] = record_id(record) unless record.new_record?
31
+ @updated = true
32
+ end
33
+
34
+ loaded
35
+ record
36
+ end
37
+
38
+ def updated?
39
+ @updated
40
+ end
41
+
42
+ private
43
+ def find_target
44
+ find_method = if @reflection.options[:primary_key]
45
+ "find_by_#{@reflection.options[:primary_key]}"
46
+ else
47
+ "find"
48
+ end
49
+ @reflection.klass.send(find_method,
50
+ @owner[@reflection.primary_key_name],
51
+ :select => @reflection.options[:select],
52
+ :conditions => conditions,
53
+ :include => @reflection.options[:include],
54
+ :readonly => @reflection.options[:readonly]
55
+ ) if @owner[@reflection.primary_key_name]
56
+ end
57
+
58
+ def foreign_key_present
59
+ !@owner[@reflection.primary_key_name].nil?
60
+ end
61
+
62
+ def record_id(record)
63
+ record.send(@reflection.options[:primary_key] || :id)
64
+ end
65
+
66
+ def previous_record_id
67
+ @previous_record_id ||= if @reflection.options[:primary_key]
68
+ previous_record = @owner.send(@reflection.name)
69
+ previous_record.nil? ? nil : previous_record.id
70
+ else
71
+ @owner[@reflection.primary_key_name]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,53 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
4
+ def replace(record)
5
+ if record.nil?
6
+ @target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
7
+ else
8
+ @target = (AssociationProxy === record ? record.target : record)
9
+
10
+ @owner[@reflection.primary_key_name] = record_id(record)
11
+ @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
12
+
13
+ @updated = true
14
+ end
15
+
16
+ loaded
17
+ record
18
+ end
19
+
20
+ def updated?
21
+ @updated
22
+ end
23
+
24
+ private
25
+ def find_target
26
+ return nil if association_class.nil?
27
+
28
+ if @reflection.options[:conditions]
29
+ association_class.find(
30
+ @owner[@reflection.primary_key_name],
31
+ :select => @reflection.options[:select],
32
+ :conditions => conditions,
33
+ :include => @reflection.options[:include]
34
+ )
35
+ else
36
+ association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include])
37
+ end
38
+ end
39
+
40
+ def foreign_key_present
41
+ !@owner[@reflection.primary_key_name].nil?
42
+ end
43
+
44
+ def record_id(record)
45
+ record.send(@reflection.options[:primary_key] || :id)
46
+ end
47
+
48
+ def association_class
49
+ @owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,143 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
4
+ def initialize(owner, reflection)
5
+ super
6
+ @primary_key_list = {}
7
+ end
8
+
9
+ def create(attributes = {})
10
+ create_record(attributes) { |record| insert_record(record) }
11
+ end
12
+
13
+ def create!(attributes = {})
14
+ create_record(attributes) { |record| insert_record(record, true) }
15
+ end
16
+
17
+ def columns
18
+ @reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
19
+ end
20
+
21
+ def reset_column_information
22
+ @reflection.reset_column_information
23
+ end
24
+
25
+ def has_primary_key?
26
+ return @has_primary_key unless @has_primary_key.nil?
27
+ @has_primary_key = (@owner.connection.supports_primary_key? &&
28
+ @owner.connection.primary_key(@reflection.options[:join_table]))
29
+ end
30
+
31
+ protected
32
+ def construct_find_options!(options)
33
+ options[:joins] = @join_sql
34
+ options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
35
+ options[:select] ||= (@reflection.options[:select] || '*')
36
+ end
37
+
38
+ def count_records
39
+ load_target.size
40
+ end
41
+
42
+ def insert_record(record, force = true, validate = true)
43
+ if has_primary_key?
44
+ raise ActiveRecord::ConfigurationError,
45
+ "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
46
+ end
47
+
48
+ if record.new_record?
49
+ if force
50
+ record.save!
51
+ else
52
+ return false unless record.save(validate)
53
+ end
54
+ end
55
+
56
+ if @reflection.options[:insert_sql]
57
+ @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
58
+ else
59
+ attributes = columns.inject({}) do |attrs, column|
60
+ case column.name.to_s
61
+ when @reflection.primary_key_name.to_s
62
+ attrs[column.name] = owner_quoted_id
63
+ when @reflection.association_foreign_key.to_s
64
+ attrs[column.name] = record.quoted_id
65
+ else
66
+ if record.has_attribute?(column.name)
67
+ value = @owner.send(:quote_value, record[column.name], column)
68
+ attrs[column.name] = value unless value.nil?
69
+ end
70
+ end
71
+ attrs
72
+ end
73
+
74
+ sql =
75
+ "INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
76
+ "VALUES (#{attributes.values.join(', ')})"
77
+
78
+ @owner.connection.insert(sql)
79
+ end
80
+
81
+ return true
82
+ end
83
+
84
+ def delete_records(records)
85
+ if sql = @reflection.options[:delete_sql]
86
+ records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
87
+ else
88
+ ids = quoted_record_ids(records)
89
+ sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
90
+ @owner.connection.delete(sql)
91
+ end
92
+ end
93
+
94
+ def construct_sql
95
+ if @reflection.options[:finder_sql]
96
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
97
+ else
98
+ @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
99
+ @finder_sql << " AND (#{conditions})" if conditions
100
+ end
101
+
102
+ @join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
103
+
104
+ if @reflection.options[:counter_sql]
105
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
106
+ elsif @reflection.options[:finder_sql]
107
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
108
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
109
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
110
+ else
111
+ @counter_sql = @finder_sql
112
+ end
113
+ end
114
+
115
+ def construct_scope
116
+ { :find => { :conditions => @finder_sql,
117
+ :joins => @join_sql,
118
+ :readonly => false,
119
+ :order => @reflection.options[:order],
120
+ :include => @reflection.options[:include],
121
+ :limit => @reflection.options[:limit] } }
122
+ end
123
+
124
+ # Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
125
+ # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
126
+ # an id column. This will then overwrite the id column of the records coming back.
127
+ def finding_with_ambiguous_select?(select_clause)
128
+ !select_clause && columns.size != 2
129
+ end
130
+
131
+ private
132
+ def create_record(attributes, &block)
133
+ # Can't use Base.create because the foreign key may be a protected attribute.
134
+ ensure_owner_is_not_new
135
+ if attributes.is_a?(Array)
136
+ attributes.collect { |attr| create(attr) }
137
+ else
138
+ build_record(attributes, &block)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,122 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ # This is the proxy that handles a has many association.
4
+ #
5
+ # If the association has a <tt>:through</tt> option further specialization
6
+ # is provided by its child HasManyThroughAssociation.
7
+ class HasManyAssociation < AssociationCollection #:nodoc:
8
+ protected
9
+ def owner_quoted_id
10
+ if @reflection.options[:primary_key]
11
+ quote_value(@owner.send(@reflection.options[:primary_key]))
12
+ else
13
+ @owner.quoted_id
14
+ end
15
+ end
16
+
17
+ # Returns the number of records in this collection.
18
+ #
19
+ # If the association has a counter cache it gets that value. Otherwise
20
+ # it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
21
+ # there's one. Some configuration options like :group make it impossible
22
+ # to do a SQL count, in those cases the array count will be used.
23
+ #
24
+ # That does not depend on whether the collection has already been loaded
25
+ # or not. The +size+ method is the one that takes the loaded flag into
26
+ # account and delegates to +count_records+ if needed.
27
+ #
28
+ # If the collection is empty the target is set to an empty array and
29
+ # the loaded flag is set to true as well.
30
+ def count_records
31
+ count = if has_cached_counter?
32
+ @owner.send(:read_attribute, cached_counter_attribute_name)
33
+ elsif @reflection.options[:counter_sql]
34
+ @reflection.klass.count_by_sql(@counter_sql)
35
+ else
36
+ @reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
37
+ end
38
+
39
+ # If there's nothing in the database and @target has no new records
40
+ # we are certain the current target is an empty array. This is a
41
+ # documented side-effect of the method that may avoid an extra SELECT.
42
+ @target ||= [] and loaded if count == 0
43
+
44
+ if @reflection.options[:limit]
45
+ count = [ @reflection.options[:limit], count ].min
46
+ end
47
+
48
+ return count
49
+ end
50
+
51
+ def has_cached_counter?
52
+ @owner.attribute_present?(cached_counter_attribute_name)
53
+ end
54
+
55
+ def cached_counter_attribute_name
56
+ "#{@reflection.name}_count"
57
+ end
58
+
59
+ def insert_record(record, force = false, validate = true)
60
+ set_belongs_to_association_for(record)
61
+ force ? record.save! : record.save(validate)
62
+ end
63
+
64
+ # Deletes the records according to the <tt>:dependent</tt> option.
65
+ def delete_records(records)
66
+ case @reflection.options[:dependent]
67
+ when :destroy
68
+ records.each { |r| r.destroy }
69
+ when :delete_all
70
+ @reflection.klass.delete(records.map { |record| record.id })
71
+ else
72
+ ids = quoted_record_ids(records)
73
+ @reflection.klass.update_all(
74
+ "#{@reflection.primary_key_name} = NULL",
75
+ "#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
76
+ )
77
+ @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
78
+ end
79
+ end
80
+
81
+ def target_obsolete?
82
+ false
83
+ end
84
+
85
+ def construct_sql
86
+ case
87
+ when @reflection.options[:finder_sql]
88
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
89
+
90
+ when @reflection.options[:as]
91
+ @finder_sql =
92
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
93
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
94
+ @finder_sql << " AND (#{conditions})" if conditions
95
+
96
+ else
97
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
98
+ @finder_sql << " AND (#{conditions})" if conditions
99
+ end
100
+
101
+ if @reflection.options[:counter_sql]
102
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
103
+ elsif @reflection.options[:finder_sql]
104
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
105
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
106
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
107
+ else
108
+ @counter_sql = @finder_sql
109
+ end
110
+ end
111
+
112
+ def construct_scope
113
+ create_scoping = {}
114
+ set_belongs_to_association_for(create_scoping)
115
+ {
116
+ :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
117
+ :create => create_scoping
118
+ }
119
+ end
120
+ end
121
+ end
122
+ end