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,266 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class HasManyThroughAssociation < HasManyAssociation #:nodoc:
4
+ def initialize(owner, reflection)
5
+ reflection.check_validity!
6
+ super
7
+ end
8
+
9
+ alias_method :new, :build
10
+
11
+ def create!(attrs = nil)
12
+ transaction do
13
+ self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!)
14
+ object
15
+ end
16
+ end
17
+
18
+ def create(attrs = nil)
19
+ transaction do
20
+ object = if attrs
21
+ @reflection.klass.send(:with_scope, :create => attrs) {
22
+ @reflection.create_association
23
+ }
24
+ else
25
+ @reflection.create_association
26
+ end
27
+ raise_on_type_mismatch(object)
28
+ add_record_to_target_with_callbacks(object) do |r|
29
+ insert_record(object, false)
30
+ end
31
+ object
32
+ end
33
+ end
34
+
35
+ # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
36
+ # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero,
37
+ # and you need to fetch that collection afterwards, it'll take one fewer SELECT query if you use #length.
38
+ def size
39
+ return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
40
+ return @target.size if loaded?
41
+ return count
42
+ end
43
+
44
+ protected
45
+ def target_reflection_has_associated_record?
46
+ if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
47
+ false
48
+ else
49
+ true
50
+ end
51
+ end
52
+
53
+ def construct_find_options!(options)
54
+ options[:select] = construct_select(options[:select])
55
+ options[:from] ||= construct_from
56
+ options[:joins] = construct_joins(options[:joins])
57
+ options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
58
+ end
59
+
60
+ def insert_record(record, force = true, validate = true)
61
+ if record.new_record?
62
+ if force
63
+ record.save!
64
+ else
65
+ return false unless record.save(validate)
66
+ end
67
+ end
68
+ through_reflection = @reflection.through_reflection
69
+ klass = through_reflection.klass
70
+ @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { through_reflection.create_association! }
71
+ end
72
+
73
+ # TODO - add dependent option support
74
+ def delete_records(records)
75
+ klass = @reflection.through_reflection.klass
76
+ records.each do |associate|
77
+ klass.delete_all(construct_join_attributes(associate))
78
+ end
79
+ end
80
+
81
+ def find_target
82
+ return [] unless target_reflection_has_associated_record?
83
+ @reflection.klass.find(:all,
84
+ :select => construct_select,
85
+ :conditions => construct_conditions,
86
+ :from => construct_from,
87
+ :joins => construct_joins,
88
+ :order => @reflection.options[:order],
89
+ :limit => @reflection.options[:limit],
90
+ :group => @reflection.options[:group],
91
+ :readonly => @reflection.options[:readonly],
92
+ :include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
93
+ )
94
+ end
95
+
96
+ # Construct attributes for associate pointing to owner.
97
+ def construct_owner_attributes(reflection)
98
+ if as = reflection.options[:as]
99
+ { "#{as}_id" => @owner.id,
100
+ "#{as}_type" => @owner.class.base_class.name.to_s }
101
+ else
102
+ { reflection.primary_key_name => @owner.id }
103
+ end
104
+ end
105
+
106
+ # Construct attributes for :through pointing to owner and associate.
107
+ def construct_join_attributes(associate)
108
+ # TODO: revist this to allow it for deletion, supposing dependent option is supported
109
+ raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
110
+ join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
111
+ if @reflection.options[:source_type]
112
+ join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
113
+ end
114
+ join_attributes
115
+ end
116
+
117
+ # Associate attributes pointing to owner, quoted.
118
+ def construct_quoted_owner_attributes(reflection)
119
+ if as = reflection.options[:as]
120
+ { "#{as}_id" => owner_quoted_id,
121
+ "#{as}_type" => reflection.klass.quote_value(
122
+ @owner.class.base_class.name.to_s,
123
+ reflection.klass.columns_hash["#{as}_type"]) }
124
+ elsif reflection.macro == :belongs_to
125
+ { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
126
+ else
127
+ { reflection.primary_key_name => owner_quoted_id }
128
+ end
129
+ end
130
+
131
+ # Build SQL conditions from attributes, qualified by table name.
132
+ def construct_conditions
133
+ table_name = @reflection.through_reflection.quoted_table_name
134
+ conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
135
+ "#{table_name}.#{attr} = #{value}"
136
+ end
137
+ conditions << sql_conditions if sql_conditions
138
+ "(" + conditions.join(') AND (') + ")"
139
+ end
140
+
141
+ def construct_from
142
+ @reflection.quoted_table_name
143
+ end
144
+
145
+ def construct_select(custom_select = nil)
146
+ distinct = "DISTINCT " if @reflection.options[:uniq]
147
+ selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
148
+ end
149
+
150
+ def construct_joins(custom_joins = nil)
151
+ polymorphic_join = nil
152
+ if @reflection.source_reflection.macro == :belongs_to
153
+ reflection_primary_key = @reflection.klass.primary_key
154
+ source_primary_key = @reflection.source_reflection.primary_key_name
155
+ if @reflection.options[:source_type]
156
+ polymorphic_join = "AND %s.%s = %s" % [
157
+ @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
158
+ @owner.class.quote_value(@reflection.options[:source_type])
159
+ ]
160
+ end
161
+ else
162
+ reflection_primary_key = @reflection.source_reflection.primary_key_name
163
+ source_primary_key = @reflection.through_reflection.klass.primary_key
164
+ if @reflection.source_reflection.options[:as]
165
+ polymorphic_join = "AND %s.%s = %s" % [
166
+ @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
167
+ @owner.class.quote_value(@reflection.through_reflection.klass.name)
168
+ ]
169
+ end
170
+ end
171
+
172
+ "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
173
+ @reflection.through_reflection.quoted_table_name,
174
+ @reflection.quoted_table_name, reflection_primary_key,
175
+ @reflection.through_reflection.quoted_table_name, source_primary_key,
176
+ polymorphic_join
177
+ ]
178
+ end
179
+
180
+ def construct_scope
181
+ { :create => construct_owner_attributes(@reflection),
182
+ :find => { :from => construct_from,
183
+ :conditions => construct_conditions,
184
+ :joins => construct_joins,
185
+ :include => @reflection.options[:include],
186
+ :select => construct_select,
187
+ :order => @reflection.options[:order],
188
+ :limit => @reflection.options[:limit],
189
+ :readonly => @reflection.options[:readonly],
190
+ } }
191
+ end
192
+
193
+ def construct_sql
194
+ case
195
+ when @reflection.options[:finder_sql]
196
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
197
+
198
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
199
+ @finder_sql << " AND (#{conditions})" if conditions
200
+ else
201
+ @finder_sql = construct_conditions
202
+ end
203
+
204
+ if @reflection.options[:counter_sql]
205
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
206
+ elsif @reflection.options[:finder_sql]
207
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
208
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
209
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
210
+ else
211
+ @counter_sql = @finder_sql
212
+ end
213
+ end
214
+
215
+ def conditions
216
+ @conditions = build_conditions unless defined?(@conditions)
217
+ @conditions
218
+ end
219
+
220
+ def build_conditions
221
+ association_conditions = @reflection.options[:conditions]
222
+ through_conditions = build_through_conditions
223
+ source_conditions = @reflection.source_reflection.options[:conditions]
224
+ uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
225
+
226
+ if association_conditions || through_conditions || source_conditions || uses_sti
227
+ all = []
228
+
229
+ [association_conditions, source_conditions].each do |conditions|
230
+ all << interpolate_sql(sanitize_sql(conditions)) if conditions
231
+ end
232
+
233
+ all << through_conditions if through_conditions
234
+ all << build_sti_condition if uses_sti
235
+
236
+ all.map { |sql| "(#{sql})" } * ' AND '
237
+ end
238
+ end
239
+
240
+ def build_through_conditions
241
+ conditions = @reflection.through_reflection.options[:conditions]
242
+ if conditions.is_a?(Hash)
243
+ interpolate_sql(sanitize_sql(conditions)).gsub(
244
+ @reflection.quoted_table_name,
245
+ @reflection.through_reflection.quoted_table_name)
246
+ elsif conditions
247
+ interpolate_sql(sanitize_sql(conditions))
248
+ end
249
+ end
250
+
251
+ def build_sti_condition
252
+ @reflection.through_reflection.klass.send(:type_condition)
253
+ end
254
+
255
+ alias_method :sql_conditions, :conditions
256
+
257
+ def has_cached_counter?
258
+ @owner.attribute_present?(cached_counter_attribute_name)
259
+ end
260
+
261
+ def cached_counter_attribute_name
262
+ "#{@reflection.name}_count"
263
+ end
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,133 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class HasOneAssociation < BelongsToAssociation #:nodoc:
4
+ def initialize(owner, reflection)
5
+ super
6
+ construct_sql
7
+ end
8
+
9
+ def create(attrs = {}, replace_existing = true)
10
+ new_record(replace_existing) do |reflection|
11
+ attrs = merge_with_conditions(attrs)
12
+ reflection.create_association(attrs)
13
+ end
14
+ end
15
+
16
+ def create!(attrs = {}, replace_existing = true)
17
+ new_record(replace_existing) do |reflection|
18
+ attrs = merge_with_conditions(attrs)
19
+ reflection.create_association!(attrs)
20
+ end
21
+ end
22
+
23
+ def build(attrs = {}, replace_existing = true)
24
+ new_record(replace_existing) do |reflection|
25
+ attrs = merge_with_conditions(attrs)
26
+ reflection.build_association(attrs)
27
+ end
28
+ end
29
+
30
+ def replace(obj, dont_save = false)
31
+ load_target
32
+
33
+ unless @target.nil? || @target == obj
34
+ if dependent? && !dont_save
35
+ case @reflection.options[:dependent]
36
+ when :delete
37
+ @target.delete unless @target.new_record?
38
+ @owner.clear_association_cache
39
+ when :destroy
40
+ @target.destroy unless @target.new_record?
41
+ @owner.clear_association_cache
42
+ when :nullify
43
+ @target[@reflection.primary_key_name] = nil
44
+ @target.save unless @owner.new_record? || @target.new_record?
45
+ end
46
+ else
47
+ @target[@reflection.primary_key_name] = nil
48
+ @target.save unless @owner.new_record? || @target.new_record?
49
+ end
50
+ end
51
+
52
+ if obj.nil?
53
+ @target = nil
54
+ else
55
+ raise_on_type_mismatch(obj)
56
+ set_belongs_to_association_for(obj)
57
+ @target = (AssociationProxy === obj ? obj.target : obj)
58
+ end
59
+
60
+ @loaded = true
61
+
62
+ unless @owner.new_record? or obj.nil? or dont_save
63
+ return (obj.save ? self : false)
64
+ else
65
+ return (obj.nil? ? nil : self)
66
+ end
67
+ end
68
+
69
+ protected
70
+ def owner_quoted_id
71
+ if @reflection.options[:primary_key]
72
+ @owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
73
+ else
74
+ @owner.quoted_id
75
+ end
76
+ end
77
+
78
+ private
79
+ def find_target
80
+ @reflection.klass.find(:first,
81
+ :conditions => @finder_sql,
82
+ :select => @reflection.options[:select],
83
+ :order => @reflection.options[:order],
84
+ :include => @reflection.options[:include],
85
+ :readonly => @reflection.options[:readonly]
86
+ )
87
+ end
88
+
89
+ def construct_sql
90
+ case
91
+ when @reflection.options[:as]
92
+ @finder_sql =
93
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
94
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
95
+ else
96
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
97
+ end
98
+ @finder_sql << " AND (#{conditions})" if conditions
99
+ end
100
+
101
+ def construct_scope
102
+ create_scoping = {}
103
+ set_belongs_to_association_for(create_scoping)
104
+ { :create => create_scoping }
105
+ end
106
+
107
+ def new_record(replace_existing)
108
+ # Make sure we load the target first, if we plan on replacing the existing
109
+ # instance. Otherwise, if the target has not previously been loaded
110
+ # elsewhere, the instance we create will get orphaned.
111
+ load_target if replace_existing
112
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) do
113
+ yield @reflection
114
+ end
115
+
116
+ if replace_existing
117
+ replace(record, true)
118
+ else
119
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
120
+ self.target = record
121
+ end
122
+
123
+ record
124
+ end
125
+
126
+ def merge_with_conditions(attrs={})
127
+ attrs ||= {}
128
+ attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
129
+ attrs
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,37 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class HasOneThroughAssociation < HasManyThroughAssociation
4
+
5
+ def create_through_record(new_value) #nodoc:
6
+ klass = @reflection.through_reflection.klass
7
+
8
+ current_object = @owner.send(@reflection.through_reflection.name)
9
+
10
+ if current_object
11
+ new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy
12
+ elsif new_value
13
+ if @owner.new_record?
14
+ self.target = new_value
15
+ through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name)
16
+ through_association.build(construct_join_attributes(new_value))
17
+ else
18
+ @owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value)))
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+ def find(*args)
25
+ super(args.merge(:limit => 1))
26
+ end
27
+
28
+ def find_target
29
+ super.first
30
+ end
31
+
32
+ def reset_target!
33
+ @target = nil
34
+ end
35
+ end
36
+ end
37
+ end