activerecord 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (311) hide show
  1. data/CHANGELOG +4928 -3
  2. data/README +45 -46
  3. data/RUNNING_UNIT_TESTS +8 -11
  4. data/Rakefile +247 -0
  5. data/install.rb +8 -38
  6. data/lib/active_record/aggregations.rb +64 -49
  7. data/lib/active_record/associations/association_collection.rb +217 -47
  8. data/lib/active_record/associations/association_proxy.rb +159 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +155 -37
  12. data/lib/active_record/associations/has_many_association.rb +145 -75
  13. data/lib/active_record/associations/has_many_through_association.rb +283 -0
  14. data/lib/active_record/associations/has_one_association.rb +96 -0
  15. data/lib/active_record/associations.rb +1537 -304
  16. data/lib/active_record/attribute_methods.rb +328 -0
  17. data/lib/active_record/base.rb +2001 -588
  18. data/lib/active_record/calculations.rb +269 -0
  19. data/lib/active_record/callbacks.rb +169 -165
  20. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +308 -0
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -0
  22. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  23. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +472 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -0
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +125 -279
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +442 -77
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +805 -135
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +353 -69
  31. data/lib/active_record/fixtures.rb +946 -100
  32. data/lib/active_record/locking/optimistic.rb +144 -0
  33. data/lib/active_record/locking/pessimistic.rb +77 -0
  34. data/lib/active_record/migration.rb +417 -0
  35. data/lib/active_record/observer.rb +142 -32
  36. data/lib/active_record/query_cache.rb +23 -0
  37. data/lib/active_record/reflection.rb +163 -70
  38. data/lib/active_record/schema.rb +58 -0
  39. data/lib/active_record/schema_dumper.rb +171 -0
  40. data/lib/active_record/serialization.rb +98 -0
  41. data/lib/active_record/serializers/json_serializer.rb +71 -0
  42. data/lib/active_record/serializers/xml_serializer.rb +315 -0
  43. data/lib/active_record/timestamp.rb +41 -0
  44. data/lib/active_record/transactions.rb +87 -57
  45. data/lib/active_record/validations.rb +909 -122
  46. data/lib/active_record/vendor/db2.rb +362 -0
  47. data/lib/active_record/vendor/mysql.rb +126 -29
  48. data/lib/active_record/version.rb +9 -0
  49. data/lib/active_record.rb +35 -7
  50. data/lib/activerecord.rb +1 -0
  51. data/test/aaa_create_tables_test.rb +72 -0
  52. data/test/abstract_unit.rb +73 -5
  53. data/test/active_schema_test_mysql.rb +43 -0
  54. data/test/adapter_test.rb +105 -0
  55. data/test/adapter_test_sqlserver.rb +95 -0
  56. data/test/aggregations_test.rb +110 -16
  57. data/test/all.sh +2 -2
  58. data/test/ar_schema_test.rb +33 -0
  59. data/test/association_inheritance_reload.rb +14 -0
  60. data/test/associations/ar_joins_test.rb +0 -0
  61. data/test/associations/callbacks_test.rb +147 -0
  62. data/test/associations/cascaded_eager_loading_test.rb +110 -0
  63. data/test/associations/eager_singularization_test.rb +145 -0
  64. data/test/associations/eager_test.rb +442 -0
  65. data/test/associations/extension_test.rb +47 -0
  66. data/test/associations/inner_join_association_test.rb +88 -0
  67. data/test/associations/join_model_test.rb +553 -0
  68. data/test/associations_test.rb +1930 -267
  69. data/test/attribute_methods_test.rb +146 -0
  70. data/test/base_test.rb +1316 -84
  71. data/test/binary_test.rb +32 -0
  72. data/test/calculations_test.rb +251 -0
  73. data/test/callbacks_test.rb +400 -0
  74. data/test/class_inheritable_attributes_test.rb +3 -4
  75. data/test/column_alias_test.rb +17 -0
  76. data/test/connection_test_firebird.rb +8 -0
  77. data/test/connection_test_mysql.rb +30 -0
  78. data/test/connections/native_db2/connection.rb +25 -0
  79. data/test/connections/native_firebird/connection.rb +26 -0
  80. data/test/connections/native_frontbase/connection.rb +27 -0
  81. data/test/connections/native_mysql/connection.rb +21 -18
  82. data/test/connections/native_openbase/connection.rb +21 -0
  83. data/test/connections/native_oracle/connection.rb +27 -0
  84. data/test/connections/native_postgresql/connection.rb +17 -18
  85. data/test/connections/native_sqlite/connection.rb +17 -16
  86. data/test/connections/native_sqlite3/connection.rb +25 -0
  87. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  88. data/test/connections/native_sybase/connection.rb +23 -0
  89. data/test/copy_table_test_sqlite.rb +69 -0
  90. data/test/datatype_test_postgresql.rb +203 -0
  91. data/test/date_time_test.rb +37 -0
  92. data/test/default_test_firebird.rb +16 -0
  93. data/test/defaults_test.rb +67 -0
  94. data/test/deprecated_finder_test.rb +30 -0
  95. data/test/finder_test.rb +607 -32
  96. data/test/fixtures/accounts.yml +28 -0
  97. data/test/fixtures/all/developers.yml +0 -0
  98. data/test/fixtures/all/people.csv +0 -0
  99. data/test/fixtures/all/tasks.yml +0 -0
  100. data/test/fixtures/author.rb +107 -0
  101. data/test/fixtures/author_favorites.yml +4 -0
  102. data/test/fixtures/authors.yml +7 -0
  103. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  104. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  105. data/test/fixtures/bad_fixtures/blank_line +3 -0
  106. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  107. data/test/fixtures/bad_fixtures/missing_value +1 -0
  108. data/test/fixtures/binaries.yml +132 -0
  109. data/test/fixtures/binary.rb +2 -0
  110. data/test/fixtures/book.rb +4 -0
  111. data/test/fixtures/books.yml +7 -0
  112. data/test/fixtures/categories/special_categories.yml +9 -0
  113. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  114. data/test/fixtures/categories.yml +14 -0
  115. data/test/fixtures/categories_ordered.yml +7 -0
  116. data/test/fixtures/categories_posts.yml +23 -0
  117. data/test/fixtures/categorization.rb +5 -0
  118. data/test/fixtures/categorizations.yml +17 -0
  119. data/test/fixtures/category.rb +26 -0
  120. data/test/fixtures/citation.rb +6 -0
  121. data/test/fixtures/comment.rb +23 -0
  122. data/test/fixtures/comments.yml +59 -0
  123. data/test/fixtures/companies.yml +55 -0
  124. data/test/fixtures/company.rb +81 -4
  125. data/test/fixtures/company_in_module.rb +32 -6
  126. data/test/fixtures/computer.rb +4 -0
  127. data/test/fixtures/computers.yml +4 -0
  128. data/test/fixtures/contact.rb +16 -0
  129. data/test/fixtures/courses.yml +7 -0
  130. data/test/fixtures/customer.rb +28 -3
  131. data/test/fixtures/customers.yml +17 -0
  132. data/test/fixtures/db_definitions/db2.drop.sql +33 -0
  133. data/test/fixtures/db_definitions/db2.sql +235 -0
  134. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  135. data/test/fixtures/db_definitions/db22.sql +5 -0
  136. data/test/fixtures/db_definitions/firebird.drop.sql +65 -0
  137. data/test/fixtures/db_definitions/firebird.sql +310 -0
  138. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  139. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  140. data/test/fixtures/db_definitions/frontbase.drop.sql +33 -0
  141. data/test/fixtures/db_definitions/frontbase.sql +273 -0
  142. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  143. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  144. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  145. data/test/fixtures/db_definitions/openbase.sql +318 -0
  146. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  148. data/test/fixtures/db_definitions/oracle.drop.sql +67 -0
  149. data/test/fixtures/db_definitions/oracle.sql +330 -0
  150. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  151. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  152. data/test/fixtures/db_definitions/postgresql.drop.sql +44 -0
  153. data/test/fixtures/db_definitions/postgresql.sql +217 -38
  154. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/postgresql2.sql +2 -2
  156. data/test/fixtures/db_definitions/schema.rb +354 -0
  157. data/test/fixtures/db_definitions/schema2.rb +11 -0
  158. data/test/fixtures/db_definitions/sqlite.drop.sql +33 -0
  159. data/test/fixtures/db_definitions/sqlite.sql +139 -5
  160. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  161. data/test/fixtures/db_definitions/sqlite2.sql +1 -0
  162. data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
  163. data/test/fixtures/db_definitions/sybase.sql +222 -0
  164. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  165. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  166. data/test/fixtures/developer.rb +70 -6
  167. data/test/fixtures/developers.yml +21 -0
  168. data/test/fixtures/developers_projects/david_action_controller +2 -1
  169. data/test/fixtures/developers_projects/david_active_record +2 -1
  170. data/test/fixtures/developers_projects.yml +17 -0
  171. data/test/fixtures/edge.rb +5 -0
  172. data/test/fixtures/edges.yml +6 -0
  173. data/test/fixtures/entrants.yml +14 -0
  174. data/test/fixtures/example.log +1 -0
  175. data/test/fixtures/fk_test_has_fk.yml +3 -0
  176. data/test/fixtures/fk_test_has_pk.yml +2 -0
  177. data/test/fixtures/flowers.jpg +0 -0
  178. data/test/fixtures/funny_jokes.yml +10 -0
  179. data/test/fixtures/item.rb +7 -0
  180. data/test/fixtures/items.yml +4 -0
  181. data/test/fixtures/joke.rb +3 -0
  182. data/test/fixtures/keyboard.rb +3 -0
  183. data/test/fixtures/legacy_thing.rb +3 -0
  184. data/test/fixtures/legacy_things.yml +3 -0
  185. data/test/fixtures/matey.rb +4 -0
  186. data/test/fixtures/mateys.yml +4 -0
  187. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  188. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  189. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  190. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  191. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  192. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  193. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  194. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  195. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  196. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  197. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  198. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  199. data/test/fixtures/minimalistic.rb +2 -0
  200. data/test/fixtures/minimalistics.yml +2 -0
  201. data/test/fixtures/mixed_case_monkey.rb +3 -0
  202. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  203. data/test/fixtures/mixins.yml +29 -0
  204. data/test/fixtures/movies.yml +7 -0
  205. data/test/fixtures/naked/csv/accounts.csv +1 -0
  206. data/test/fixtures/naked/yml/accounts.yml +1 -0
  207. data/test/fixtures/naked/yml/companies.yml +1 -0
  208. data/test/fixtures/naked/yml/courses.yml +1 -0
  209. data/test/fixtures/order.rb +4 -0
  210. data/test/fixtures/parrot.rb +13 -0
  211. data/test/fixtures/parrots.yml +27 -0
  212. data/test/fixtures/parrots_pirates.yml +7 -0
  213. data/test/fixtures/people.yml +3 -0
  214. data/test/fixtures/person.rb +4 -0
  215. data/test/fixtures/pirate.rb +5 -0
  216. data/test/fixtures/pirates.yml +9 -0
  217. data/test/fixtures/post.rb +59 -0
  218. data/test/fixtures/posts.yml +48 -0
  219. data/test/fixtures/project.rb +27 -2
  220. data/test/fixtures/projects.yml +7 -0
  221. data/test/fixtures/reader.rb +4 -0
  222. data/test/fixtures/readers.yml +4 -0
  223. data/test/fixtures/reply.rb +18 -2
  224. data/test/fixtures/reserved_words/distinct.yml +5 -0
  225. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  226. data/test/fixtures/reserved_words/group.yml +14 -0
  227. data/test/fixtures/reserved_words/select.yml +8 -0
  228. data/test/fixtures/reserved_words/values.yml +7 -0
  229. data/test/fixtures/ship.rb +3 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/subject.rb +4 -0
  232. data/test/fixtures/subscriber.rb +4 -3
  233. data/test/fixtures/tag.rb +7 -0
  234. data/test/fixtures/tagging.rb +10 -0
  235. data/test/fixtures/taggings.yml +25 -0
  236. data/test/fixtures/tags.yml +7 -0
  237. data/test/fixtures/task.rb +3 -0
  238. data/test/fixtures/tasks.yml +7 -0
  239. data/test/fixtures/topic.rb +20 -3
  240. data/test/fixtures/topics.yml +22 -0
  241. data/test/fixtures/treasure.rb +4 -0
  242. data/test/fixtures/treasures.yml +10 -0
  243. data/test/fixtures/vertex.rb +9 -0
  244. data/test/fixtures/vertices.yml +4 -0
  245. data/test/fixtures_test.rb +574 -8
  246. data/test/inheritance_test.rb +113 -27
  247. data/test/json_serialization_test.rb +180 -0
  248. data/test/lifecycle_test.rb +56 -29
  249. data/test/locking_test.rb +273 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +933 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_test.rb +95 -0
  254. data/test/modules_test.rb +23 -10
  255. data/test/multiple_db_test.rb +17 -3
  256. data/test/pk_test.rb +59 -15
  257. data/test/query_cache_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +124 -27
  260. data/test/reserved_word_test_mysql.rb +177 -0
  261. data/test/schema_authorization_test_postgresql.rb +75 -0
  262. data/test/schema_dumper_test.rb +131 -0
  263. data/test/schema_test_postgresql.rb +64 -0
  264. data/test/serialization_test.rb +47 -0
  265. data/test/synonym_test_oracle.rb +17 -0
  266. data/test/table_name_test_sqlserver.rb +23 -0
  267. data/test/threaded_connections_test.rb +48 -0
  268. data/test/transactions_test.rb +227 -29
  269. data/test/unconnected_test.rb +14 -6
  270. data/test/validations_test.rb +1293 -32
  271. data/test/xml_serialization_test.rb +202 -0
  272. metadata +347 -143
  273. data/dev-utils/eval_debugger.rb +0 -9
  274. data/examples/associations.rb +0 -87
  275. data/examples/shared_setup.rb +0 -15
  276. data/examples/validation.rb +0 -88
  277. data/lib/active_record/deprecated_associations.rb +0 -70
  278. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  279. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  280. data/lib/active_record/support/clean_logger.rb +0 -10
  281. data/lib/active_record/support/inflector.rb +0 -70
  282. data/lib/active_record/vendor/simple.rb +0 -702
  283. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  284. data/lib/active_record/wrappings.rb +0 -59
  285. data/rakefile +0 -122
  286. data/test/deprecated_associations_test.rb +0 -336
  287. data/test/fixtures/accounts/signals37 +0 -3
  288. data/test/fixtures/accounts/unknown +0 -2
  289. data/test/fixtures/companies/first_client +0 -6
  290. data/test/fixtures/companies/first_firm +0 -4
  291. data/test/fixtures/companies/second_client +0 -6
  292. data/test/fixtures/courses/java +0 -2
  293. data/test/fixtures/courses/ruby +0 -2
  294. data/test/fixtures/customers/david +0 -6
  295. data/test/fixtures/db_definitions/mysql.sql +0 -96
  296. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  297. data/test/fixtures/developers/david +0 -2
  298. data/test/fixtures/developers/jamis +0 -2
  299. data/test/fixtures/entrants/first +0 -3
  300. data/test/fixtures/entrants/second +0 -3
  301. data/test/fixtures/entrants/third +0 -3
  302. data/test/fixtures/fixture_database.sqlite +0 -0
  303. data/test/fixtures/fixture_database_2.sqlite +0 -0
  304. data/test/fixtures/movies/first +0 -2
  305. data/test/fixtures/movies/second +0 -2
  306. data/test/fixtures/projects/action_controller +0 -2
  307. data/test/fixtures/projects/active_record +0 -2
  308. data/test/fixtures/topics/first +0 -9
  309. data/test/fixtures/topics/second +0 -8
  310. data/test/inflector_test.rb +0 -104
  311. data/test/thread_safety_test.rb +0 -33
@@ -0,0 +1,283 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class HasManyThroughAssociation < AssociationProxy #:nodoc:
4
+ def initialize(owner, reflection)
5
+ super
6
+ reflection.check_validity!
7
+ @finder_sql = construct_conditions
8
+ construct_sql
9
+ end
10
+
11
+ def find(*args)
12
+ options = args.extract_options!
13
+
14
+ conditions = "#{@finder_sql}"
15
+ if sanitized_conditions = sanitize_sql(options[:conditions])
16
+ conditions << " AND (#{sanitized_conditions})"
17
+ end
18
+ options[:conditions] = conditions
19
+
20
+ if options[:order] && @reflection.options[:order]
21
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
22
+ elsif @reflection.options[:order]
23
+ options[:order] = @reflection.options[:order]
24
+ end
25
+
26
+ options[:select] = construct_select(options[:select])
27
+ options[:from] ||= construct_from
28
+ options[:joins] = construct_joins(options[:joins])
29
+ options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
30
+
31
+ merge_options_from_reflection!(options)
32
+
33
+ # Pass through args exactly as we received them.
34
+ args << options
35
+ @reflection.klass.find(*args)
36
+ end
37
+
38
+ def reset
39
+ @target = []
40
+ @loaded = false
41
+ end
42
+
43
+ # Adds records to the association. The source record and its associates
44
+ # must have ids in order to create records associating them, so this
45
+ # will raise ActiveRecord::HasManyThroughCantAssociateNewRecords if
46
+ # either is a new record. Calls create! so you can rescue errors.
47
+ #
48
+ # The :before_add and :after_add callbacks are not yet supported.
49
+ def <<(*records)
50
+ return if records.empty?
51
+ through = @reflection.through_reflection
52
+ raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) if @owner.new_record?
53
+
54
+ klass = through.klass
55
+ klass.transaction do
56
+ flatten_deeper(records).each do |associate|
57
+ raise_on_type_mismatch(associate)
58
+ raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
59
+
60
+ @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(associate)) { klass.create! }
61
+ @target << associate if loaded?
62
+ end
63
+ end
64
+
65
+ self
66
+ end
67
+
68
+ [:push, :concat].each { |method| alias_method method, :<< }
69
+
70
+ # Removes +records+ from this association. Does not destroy +records+.
71
+ def delete(*records)
72
+ records = flatten_deeper(records)
73
+ records.each { |associate| raise_on_type_mismatch(associate) }
74
+
75
+ through = @reflection.through_reflection
76
+ raise ActiveRecord::HasManyThroughCantDissociateNewRecords.new(@owner, through) if @owner.new_record?
77
+
78
+ load_target
79
+
80
+ klass = through.klass
81
+ klass.transaction do
82
+ flatten_deeper(records).each do |associate|
83
+ raise_on_type_mismatch(associate)
84
+ raise ActiveRecord::HasManyThroughCantDissociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
85
+
86
+ @owner.send(through.name).proxy_target.delete(klass.delete_all(construct_join_attributes(associate)))
87
+ @target.delete(associate)
88
+ end
89
+ end
90
+
91
+ self
92
+ end
93
+
94
+ def build(attrs = nil)
95
+ raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, @reflection.through_reflection)
96
+ end
97
+ alias_method :new, :build
98
+
99
+ def create!(attrs = nil)
100
+ @reflection.klass.transaction do
101
+ self << (object = @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! })
102
+ object
103
+ end
104
+ end
105
+
106
+ # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
107
+ # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
108
+ # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
109
+ def size
110
+ return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
111
+ return @target.size if loaded?
112
+ return count
113
+ end
114
+
115
+ # Calculate sum using SQL, not Enumerable
116
+ def sum(*args, &block)
117
+ calculate(:sum, *args, &block)
118
+ end
119
+
120
+ def count(*args)
121
+ column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
122
+ if @reflection.options[:uniq]
123
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid sql statement.
124
+ column_name = "#{@reflection.klass.table_name}.#{@reflection.klass.primary_key}" if column_name == :all
125
+ options.merge!(:distinct => true)
126
+ end
127
+ @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
128
+ end
129
+
130
+ protected
131
+ def method_missing(method, *args, &block)
132
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
133
+ super
134
+ else
135
+ @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.send(method, *args, &block) }
136
+ end
137
+ end
138
+
139
+ def find_target
140
+ records = @reflection.klass.find(:all,
141
+ :select => construct_select,
142
+ :conditions => construct_conditions,
143
+ :from => construct_from,
144
+ :joins => construct_joins,
145
+ :order => @reflection.options[:order],
146
+ :limit => @reflection.options[:limit],
147
+ :group => @reflection.options[:group],
148
+ :include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
149
+ )
150
+
151
+ @reflection.options[:uniq] ? records.to_set.to_a : records
152
+ end
153
+
154
+ # Construct attributes for associate pointing to owner.
155
+ def construct_owner_attributes(reflection)
156
+ if as = reflection.options[:as]
157
+ { "#{as}_id" => @owner.id,
158
+ "#{as}_type" => @owner.class.base_class.name.to_s }
159
+ else
160
+ { reflection.primary_key_name => @owner.id }
161
+ end
162
+ end
163
+
164
+ # Construct attributes for :through pointing to owner and associate.
165
+ def construct_join_attributes(associate)
166
+ join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
167
+ if @reflection.options[:source_type]
168
+ join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
169
+ end
170
+ join_attributes
171
+ end
172
+
173
+ # Associate attributes pointing to owner, quoted.
174
+ def construct_quoted_owner_attributes(reflection)
175
+ if as = reflection.options[:as]
176
+ { "#{as}_id" => @owner.quoted_id,
177
+ "#{as}_type" => reflection.klass.quote_value(
178
+ @owner.class.base_class.name.to_s,
179
+ reflection.klass.columns_hash["#{as}_type"]) }
180
+ else
181
+ { reflection.primary_key_name => @owner.quoted_id }
182
+ end
183
+ end
184
+
185
+ # Build SQL conditions from attributes, qualified by table name.
186
+ def construct_conditions
187
+ table_name = @reflection.through_reflection.table_name
188
+ conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
189
+ "#{table_name}.#{attr} = #{value}"
190
+ end
191
+ conditions << sql_conditions if sql_conditions
192
+ "(" + conditions.join(') AND (') + ")"
193
+ end
194
+
195
+ def construct_from
196
+ @reflection.table_name
197
+ end
198
+
199
+ def construct_select(custom_select = nil)
200
+ selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*"
201
+ end
202
+
203
+ def construct_joins(custom_joins = nil)
204
+ polymorphic_join = nil
205
+ if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
206
+ reflection_primary_key = @reflection.klass.primary_key
207
+ source_primary_key = @reflection.source_reflection.primary_key_name
208
+ if @reflection.options[:source_type]
209
+ polymorphic_join = "AND %s.%s = %s" % [
210
+ @reflection.through_reflection.table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
211
+ @owner.class.quote_value(@reflection.options[:source_type])
212
+ ]
213
+ end
214
+ else
215
+ reflection_primary_key = @reflection.source_reflection.primary_key_name
216
+ source_primary_key = @reflection.klass.primary_key
217
+ if @reflection.source_reflection.options[:as]
218
+ polymorphic_join = "AND %s.%s = %s" % [
219
+ @reflection.table_name, "#{@reflection.source_reflection.options[:as]}_type",
220
+ @owner.class.quote_value(@reflection.through_reflection.klass.name)
221
+ ]
222
+ end
223
+ end
224
+
225
+ "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
226
+ @reflection.through_reflection.table_name,
227
+ @reflection.table_name, reflection_primary_key,
228
+ @reflection.through_reflection.table_name, source_primary_key,
229
+ polymorphic_join
230
+ ]
231
+ end
232
+
233
+ def construct_scope
234
+ { :create => construct_owner_attributes(@reflection),
235
+ :find => { :from => construct_from,
236
+ :conditions => construct_conditions,
237
+ :joins => construct_joins,
238
+ :select => construct_select,
239
+ :order => @reflection.options[:order],
240
+ :limit => @reflection.options[:limit] } }
241
+ end
242
+
243
+ def construct_sql
244
+ case
245
+ when @reflection.options[:finder_sql]
246
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
247
+
248
+ @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
249
+ @finder_sql << " AND (#{conditions})" if conditions
250
+ end
251
+
252
+ if @reflection.options[:counter_sql]
253
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
254
+ elsif @reflection.options[:finder_sql]
255
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
256
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
257
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
258
+ else
259
+ @counter_sql = @finder_sql
260
+ end
261
+ end
262
+
263
+ def conditions
264
+ @conditions ||= [
265
+ (interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]),
266
+ (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions]),
267
+ (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.source_reflection.options[:conditions])) if @reflection.source_reflection.options[:conditions]),
268
+ ("#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.name.demodulize)}" unless @reflection.through_reflection.klass.descends_from_active_record?)
269
+ ].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions] && !@reflection.source_reflection.options[:conditions] && @reflection.through_reflection.klass.descends_from_active_record?)
270
+ end
271
+
272
+ alias_method :sql_conditions, :conditions
273
+
274
+ def has_cached_counter?
275
+ @owner.attribute_present?(cached_counter_attribute_name)
276
+ end
277
+
278
+ def cached_counter_attribute_name
279
+ "#{@reflection.name}_count"
280
+ end
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,96 @@
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) { |klass| klass.create(attrs) }
11
+ end
12
+
13
+ def create!(attrs = {}, replace_existing = true)
14
+ new_record(replace_existing) { |klass| klass.create!(attrs) }
15
+ end
16
+
17
+ def build(attrs = {}, replace_existing = true)
18
+ new_record(replace_existing) { |klass| klass.new(attrs) }
19
+ end
20
+
21
+ def replace(obj, dont_save = false)
22
+ load_target
23
+
24
+ unless @target.nil?
25
+ if dependent? && !dont_save && @target != obj
26
+ @target.destroy unless @target.new_record?
27
+ @owner.clear_association_cache
28
+ else
29
+ @target[@reflection.primary_key_name] = nil
30
+ @target.save unless @owner.new_record? || @target.new_record?
31
+ end
32
+ end
33
+
34
+ if obj.nil?
35
+ @target = nil
36
+ else
37
+ raise_on_type_mismatch(obj)
38
+ set_belongs_to_association_for(obj)
39
+ @target = (AssociationProxy === obj ? obj.target : obj)
40
+ end
41
+
42
+ @loaded = true
43
+
44
+ unless @owner.new_record? or obj.nil? or dont_save
45
+ return (obj.save ? self : false)
46
+ else
47
+ return (obj.nil? ? nil : self)
48
+ end
49
+ end
50
+
51
+ private
52
+ def find_target
53
+ @reflection.klass.find(:first,
54
+ :conditions => @finder_sql,
55
+ :order => @reflection.options[:order],
56
+ :include => @reflection.options[:include]
57
+ )
58
+ end
59
+
60
+ def construct_sql
61
+ case
62
+ when @reflection.options[:as]
63
+ @finder_sql =
64
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
65
+ "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
66
+ else
67
+ @finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
68
+ end
69
+ @finder_sql << " AND (#{conditions})" if conditions
70
+ end
71
+
72
+ def construct_scope
73
+ create_scoping = {}
74
+ set_belongs_to_association_for(create_scoping)
75
+ { :create => create_scoping }
76
+ end
77
+
78
+ def new_record(replace_existing)
79
+ # Make sure we load the target first, if we plan on replacing the existing
80
+ # instance. Otherwise, if the target has not previously been loaded
81
+ # elsewhere, the instance we create will get orphaned.
82
+ load_target if replace_existing
83
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { yield @reflection.klass }
84
+
85
+ if replace_existing
86
+ replace(record, true)
87
+ else
88
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
89
+ self.target = record
90
+ end
91
+
92
+ record
93
+ end
94
+ end
95
+ end
96
+ end