activerecord 2.0.5 → 2.1.0

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

Potentially problematic release.


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

Files changed (289) hide show
  1. data/CHANGELOG +168 -6
  2. data/README +27 -22
  3. data/RUNNING_UNIT_TESTS +7 -4
  4. data/Rakefile +22 -25
  5. data/lib/active_record.rb +8 -2
  6. data/lib/active_record/aggregations.rb +21 -12
  7. data/lib/active_record/association_preload.rb +277 -0
  8. data/lib/active_record/associations.rb +481 -295
  9. data/lib/active_record/associations/association_collection.rb +162 -37
  10. data/lib/active_record/associations/association_proxy.rb +71 -7
  11. data/lib/active_record/associations/belongs_to_association.rb +5 -3
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -6
  13. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -64
  14. data/lib/active_record/associations/has_many_association.rb +8 -73
  15. data/lib/active_record/associations/has_many_through_association.rb +68 -117
  16. data/lib/active_record/associations/has_one_association.rb +7 -5
  17. data/lib/active_record/associations/has_one_through_association.rb +28 -0
  18. data/lib/active_record/attribute_methods.rb +69 -19
  19. data/lib/active_record/base.rb +496 -275
  20. data/lib/active_record/calculations.rb +28 -21
  21. data/lib/active_record/callbacks.rb +9 -38
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
  24. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +232 -45
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +141 -27
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +9 -13
  28. data/lib/active_record/connection_adapters/mysql_adapter.rb +57 -24
  29. data/lib/active_record/connection_adapters/postgresql_adapter.rb +143 -42
  30. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  31. data/lib/active_record/connection_adapters/sqlite_adapter.rb +18 -10
  32. data/lib/active_record/dirty.rb +158 -0
  33. data/lib/active_record/fixtures.rb +121 -156
  34. data/lib/active_record/locking/optimistic.rb +14 -11
  35. data/lib/active_record/locking/pessimistic.rb +2 -2
  36. data/lib/active_record/migration.rb +157 -77
  37. data/lib/active_record/named_scope.rb +163 -0
  38. data/lib/active_record/observer.rb +19 -5
  39. data/lib/active_record/reflection.rb +34 -14
  40. data/lib/active_record/schema.rb +7 -14
  41. data/lib/active_record/schema_dumper.rb +4 -4
  42. data/lib/active_record/serialization.rb +5 -5
  43. data/lib/active_record/serializers/json_serializer.rb +37 -28
  44. data/lib/active_record/serializers/xml_serializer.rb +52 -29
  45. data/lib/active_record/test_case.rb +36 -0
  46. data/lib/active_record/timestamp.rb +4 -4
  47. data/lib/active_record/transactions.rb +3 -3
  48. data/lib/active_record/validations.rb +182 -248
  49. data/lib/active_record/version.rb +2 -2
  50. data/test/{fixtures → assets}/example.log +0 -0
  51. data/test/{fixtures → assets}/flowers.jpg +0 -0
  52. data/test/cases/aaa_create_tables_test.rb +24 -0
  53. data/test/cases/active_schema_test_mysql.rb +95 -0
  54. data/test/cases/active_schema_test_postgresql.rb +24 -0
  55. data/test/{adapter_test.rb → cases/adapter_test.rb} +15 -14
  56. data/test/{adapter_test_sqlserver.rb → cases/adapter_test_sqlserver.rb} +95 -95
  57. data/test/{aggregations_test.rb → cases/aggregations_test.rb} +20 -20
  58. data/test/{ar_schema_test.rb → cases/ar_schema_test.rb} +6 -6
  59. data/test/cases/associations/belongs_to_associations_test.rb +412 -0
  60. data/test/{associations → cases/associations}/callbacks_test.rb +24 -10
  61. data/test/{associations → cases/associations}/cascaded_eager_loading_test.rb +18 -17
  62. data/test/cases/associations/eager_load_nested_include_test.rb +83 -0
  63. data/test/{associations → cases/associations}/eager_singularization_test.rb +5 -5
  64. data/test/{associations → cases/associations}/eager_test.rb +216 -51
  65. data/test/{associations → cases/associations}/extension_test.rb +8 -8
  66. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +684 -0
  67. data/test/cases/associations/has_many_associations_test.rb +932 -0
  68. data/test/cases/associations/has_many_through_associations_test.rb +190 -0
  69. data/test/cases/associations/has_one_associations_test.rb +323 -0
  70. data/test/cases/associations/has_one_through_associations_test.rb +74 -0
  71. data/test/{associations → cases/associations}/inner_join_association_test.rb +20 -20
  72. data/test/{associations → cases/associations}/join_model_test.rb +175 -35
  73. data/test/cases/associations_test.rb +262 -0
  74. data/test/{attribute_methods_test.rb → cases/attribute_methods_test.rb} +103 -11
  75. data/test/{base_test.rb → cases/base_test.rb} +338 -191
  76. data/test/{binary_test.rb → cases/binary_test.rb} +6 -4
  77. data/test/{calculations_test.rb → cases/calculations_test.rb} +35 -23
  78. data/test/{callbacks_test.rb → cases/callbacks_test.rb} +7 -7
  79. data/test/{class_inheritable_attributes_test.rb → cases/class_inheritable_attributes_test.rb} +3 -3
  80. data/test/{column_alias_test.rb → cases/column_alias_test.rb} +3 -3
  81. data/test/{connection_test_firebird.rb → cases/connection_test_firebird.rb} +2 -2
  82. data/test/{connection_test_mysql.rb → cases/connection_test_mysql.rb} +2 -2
  83. data/test/{copy_table_test_sqlite.rb → cases/copy_table_test_sqlite.rb} +13 -13
  84. data/test/{datatype_test_postgresql.rb → cases/datatype_test_postgresql.rb} +8 -8
  85. data/test/{date_time_test.rb → cases/date_time_test.rb} +5 -5
  86. data/test/{default_test_firebird.rb → cases/default_test_firebird.rb} +3 -3
  87. data/test/{defaults_test.rb → cases/defaults_test.rb} +8 -6
  88. data/test/{deprecated_finder_test.rb → cases/deprecated_finder_test.rb} +3 -3
  89. data/test/cases/dirty_test.rb +163 -0
  90. data/test/cases/finder_respond_to_test.rb +76 -0
  91. data/test/{finder_test.rb → cases/finder_test.rb} +266 -33
  92. data/test/{fixtures_test.rb → cases/fixtures_test.rb} +88 -72
  93. data/test/cases/helper.rb +47 -0
  94. data/test/{inheritance_test.rb → cases/inheritance_test.rb} +61 -17
  95. data/test/cases/invalid_date_test.rb +24 -0
  96. data/test/{json_serialization_test.rb → cases/json_serialization_test.rb} +36 -11
  97. data/test/{lifecycle_test.rb → cases/lifecycle_test.rb} +16 -13
  98. data/test/{locking_test.rb → cases/locking_test.rb} +17 -10
  99. data/test/{method_scoping_test.rb → cases/method_scoping_test.rb} +75 -39
  100. data/test/{migration_test.rb → cases/migration_test.rb} +420 -80
  101. data/test/{migration_test_firebird.rb → cases/migration_test_firebird.rb} +3 -3
  102. data/test/{mixin_test.rb → cases/mixin_test.rb} +7 -6
  103. data/test/{modules_test.rb → cases/modules_test.rb} +11 -6
  104. data/test/{multiple_db_test.rb → cases/multiple_db_test.rb} +5 -5
  105. data/test/cases/named_scope_test.rb +157 -0
  106. data/test/{pk_test.rb → cases/pk_test.rb} +10 -10
  107. data/test/{query_cache_test.rb → cases/query_cache_test.rb} +12 -10
  108. data/test/{readonly_test.rb → cases/readonly_test.rb} +11 -11
  109. data/test/{reflection_test.rb → cases/reflection_test.rb} +15 -14
  110. data/test/{reserved_word_test_mysql.rb → cases/reserved_word_test_mysql.rb} +4 -5
  111. data/test/{schema_authorization_test_postgresql.rb → cases/schema_authorization_test_postgresql.rb} +5 -5
  112. data/test/cases/schema_dumper_test.rb +138 -0
  113. data/test/cases/schema_test_postgresql.rb +102 -0
  114. data/test/{serialization_test.rb → cases/serialization_test.rb} +7 -7
  115. data/test/{synonym_test_oracle.rb → cases/synonym_test_oracle.rb} +5 -5
  116. data/test/{table_name_test_sqlserver.rb → cases/table_name_test_sqlserver.rb} +3 -3
  117. data/test/{threaded_connections_test.rb → cases/threaded_connections_test.rb} +7 -7
  118. data/test/{transactions_test.rb → cases/transactions_test.rb} +31 -5
  119. data/test/{unconnected_test.rb → cases/unconnected_test.rb} +2 -2
  120. data/test/{validations_test.rb → cases/validations_test.rb} +141 -39
  121. data/test/{xml_serialization_test.rb → cases/xml_serialization_test.rb} +12 -12
  122. data/test/config.rb +5 -0
  123. data/test/connections/native_db2/connection.rb +1 -1
  124. data/test/connections/native_firebird/connection.rb +1 -1
  125. data/test/connections/native_frontbase/connection.rb +1 -1
  126. data/test/connections/native_mysql/connection.rb +1 -1
  127. data/test/connections/native_openbase/connection.rb +1 -1
  128. data/test/connections/native_oracle/connection.rb +1 -1
  129. data/test/connections/native_postgresql/connection.rb +1 -3
  130. data/test/connections/native_sqlite/connection.rb +2 -2
  131. data/test/connections/native_sqlite3/connection.rb +2 -2
  132. data/test/connections/native_sqlite3/in_memory_connection.rb +3 -3
  133. data/test/connections/native_sybase/connection.rb +1 -1
  134. data/test/fixtures/author_addresses.yml +5 -0
  135. data/test/fixtures/authors.yml +2 -0
  136. data/test/fixtures/clubs.yml +6 -0
  137. data/test/fixtures/jobs.yml +7 -0
  138. data/test/fixtures/members.yml +4 -0
  139. data/test/fixtures/memberships.yml +20 -0
  140. data/test/fixtures/owners.yml +7 -0
  141. data/test/fixtures/people.yml +4 -1
  142. data/test/fixtures/pets.yml +14 -0
  143. data/test/fixtures/posts.yml +1 -0
  144. data/test/fixtures/price_estimates.yml +7 -0
  145. data/test/fixtures/readers.yml +5 -0
  146. data/test/fixtures/references.yml +17 -0
  147. data/test/fixtures/sponsors.yml +9 -0
  148. data/test/fixtures/subscribers.yml +7 -0
  149. data/test/fixtures/subscriptions.yml +12 -0
  150. data/test/fixtures/taggings.yml +4 -1
  151. data/test/fixtures/topics.yml +22 -2
  152. data/test/fixtures/warehouse-things.yml +3 -0
  153. data/test/{fixtures/migrations_with_decimal → migrations/decimal}/1_give_me_big_numbers.rb +0 -0
  154. data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/1_people_have_last_names.rb +1 -1
  155. data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/2_we_need_reminders.rb +1 -1
  156. data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/3_foo.rb +0 -0
  157. data/test/{fixtures/migrations → migrations/duplicate}/3_innocent_jointable.rb +0 -0
  158. data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
  159. data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
  160. data/test/{fixtures/migrations_with_duplicate → migrations/interleaved/pass_1}/3_innocent_jointable.rb +0 -0
  161. data/test/{fixtures/migrations → migrations/interleaved/pass_2}/1_people_have_last_names.rb +1 -1
  162. data/test/{fixtures/migrations_with_missing_versions/4_innocent_jointable.rb → migrations/interleaved/pass_2/3_innocent_jointable.rb} +0 -0
  163. data/test/{fixtures/migrations_with_missing_versions → migrations/interleaved/pass_3}/1_people_have_last_names.rb +1 -1
  164. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
  165. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
  166. data/test/{fixtures/migrations_with_missing_versions → migrations/missing}/1000_people_have_middle_names.rb +1 -1
  167. data/test/migrations/missing/1_people_have_last_names.rb +9 -0
  168. data/test/{fixtures/migrations_with_missing_versions → migrations/missing}/3_we_need_reminders.rb +1 -1
  169. data/test/migrations/missing/4_innocent_jointable.rb +12 -0
  170. data/test/migrations/valid/1_people_have_last_names.rb +9 -0
  171. data/test/{fixtures/migrations → migrations/valid}/2_we_need_reminders.rb +1 -1
  172. data/test/migrations/valid/3_innocent_jointable.rb +12 -0
  173. data/test/{fixtures → models}/author.rb +28 -4
  174. data/test/{fixtures → models}/auto_id.rb +0 -0
  175. data/test/{fixtures → models}/binary.rb +0 -0
  176. data/test/{fixtures → models}/book.rb +0 -0
  177. data/test/{fixtures → models}/categorization.rb +0 -0
  178. data/test/{fixtures → models}/category.rb +8 -5
  179. data/test/{fixtures → models}/citation.rb +0 -0
  180. data/test/models/club.rb +7 -0
  181. data/test/{fixtures → models}/column_name.rb +0 -0
  182. data/test/{fixtures → models}/comment.rb +5 -3
  183. data/test/{fixtures → models}/company.rb +15 -6
  184. data/test/{fixtures → models}/company_in_module.rb +5 -3
  185. data/test/{fixtures → models}/computer.rb +0 -1
  186. data/test/{fixtures → models}/contact.rb +1 -1
  187. data/test/{fixtures → models}/course.rb +0 -0
  188. data/test/{fixtures → models}/customer.rb +8 -8
  189. data/test/{fixtures → models}/default.rb +0 -0
  190. data/test/{fixtures → models}/developer.rb +14 -10
  191. data/test/{fixtures → models}/edge.rb +0 -0
  192. data/test/{fixtures → models}/entrant.rb +0 -0
  193. data/test/models/guid.rb +2 -0
  194. data/test/{fixtures → models}/item.rb +0 -0
  195. data/test/models/job.rb +5 -0
  196. data/test/{fixtures → models}/joke.rb +0 -0
  197. data/test/{fixtures → models}/keyboard.rb +0 -0
  198. data/test/{fixtures → models}/legacy_thing.rb +0 -0
  199. data/test/{fixtures → models}/matey.rb +0 -0
  200. data/test/models/member.rb +9 -0
  201. data/test/models/membership.rb +9 -0
  202. data/test/{fixtures → models}/minimalistic.rb +0 -0
  203. data/test/{fixtures → models}/mixed_case_monkey.rb +0 -0
  204. data/test/{fixtures → models}/movie.rb +0 -0
  205. data/test/{fixtures → models}/order.rb +2 -2
  206. data/test/models/owner.rb +4 -0
  207. data/test/{fixtures → models}/parrot.rb +0 -0
  208. data/test/models/person.rb +10 -0
  209. data/test/models/pet.rb +4 -0
  210. data/test/models/pirate.rb +9 -0
  211. data/test/{fixtures → models}/post.rb +23 -2
  212. data/test/models/price_estimate.rb +3 -0
  213. data/test/{fixtures → models}/project.rb +1 -0
  214. data/test/{fixtures → models}/reader.rb +0 -0
  215. data/test/models/reference.rb +4 -0
  216. data/test/{fixtures → models}/reply.rb +7 -5
  217. data/test/{fixtures → models}/ship.rb +0 -0
  218. data/test/models/sponsor.rb +4 -0
  219. data/test/{fixtures → models}/subject.rb +0 -0
  220. data/test/{fixtures → models}/subscriber.rb +2 -0
  221. data/test/models/subscription.rb +4 -0
  222. data/test/{fixtures → models}/tag.rb +0 -0
  223. data/test/{fixtures → models}/tagging.rb +0 -0
  224. data/test/{fixtures → models}/task.rb +0 -0
  225. data/test/{fixtures → models}/topic.rb +32 -4
  226. data/test/{fixtures → models}/treasure.rb +2 -0
  227. data/test/{fixtures → models}/vertex.rb +0 -0
  228. data/test/models/warehouse_thing.rb +5 -0
  229. data/test/schema/mysql_specific_schema.rb +12 -0
  230. data/test/schema/postgresql_specific_schema.rb +103 -0
  231. data/test/schema/schema.rb +421 -0
  232. data/test/schema/schema2.rb +6 -0
  233. data/test/schema/sqlite_specific_schema.rb +25 -0
  234. data/test/schema/sqlserver_specific_schema.rb +5 -0
  235. metadata +192 -176
  236. data/test/aaa_create_tables_test.rb +0 -72
  237. data/test/abstract_unit.rb +0 -84
  238. data/test/active_schema_test_mysql.rb +0 -46
  239. data/test/all.sh +0 -8
  240. data/test/association_inheritance_reload.rb +0 -14
  241. data/test/associations_test.rb +0 -2177
  242. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +0 -1
  243. data/test/fixtures/bad_fixtures/attr_with_spaces +0 -1
  244. data/test/fixtures/bad_fixtures/blank_line +0 -3
  245. data/test/fixtures/bad_fixtures/duplicate_attributes +0 -3
  246. data/test/fixtures/bad_fixtures/missing_value +0 -1
  247. data/test/fixtures/db_definitions/db2.drop.sql +0 -33
  248. data/test/fixtures/db_definitions/db2.sql +0 -235
  249. data/test/fixtures/db_definitions/db22.drop.sql +0 -2
  250. data/test/fixtures/db_definitions/db22.sql +0 -5
  251. data/test/fixtures/db_definitions/firebird.drop.sql +0 -65
  252. data/test/fixtures/db_definitions/firebird.sql +0 -310
  253. data/test/fixtures/db_definitions/firebird2.drop.sql +0 -2
  254. data/test/fixtures/db_definitions/firebird2.sql +0 -6
  255. data/test/fixtures/db_definitions/frontbase.drop.sql +0 -33
  256. data/test/fixtures/db_definitions/frontbase.sql +0 -273
  257. data/test/fixtures/db_definitions/frontbase2.drop.sql +0 -1
  258. data/test/fixtures/db_definitions/frontbase2.sql +0 -4
  259. data/test/fixtures/db_definitions/openbase.drop.sql +0 -2
  260. data/test/fixtures/db_definitions/openbase.sql +0 -318
  261. data/test/fixtures/db_definitions/openbase2.drop.sql +0 -2
  262. data/test/fixtures/db_definitions/openbase2.sql +0 -7
  263. data/test/fixtures/db_definitions/oracle.drop.sql +0 -67
  264. data/test/fixtures/db_definitions/oracle.sql +0 -330
  265. data/test/fixtures/db_definitions/oracle2.drop.sql +0 -2
  266. data/test/fixtures/db_definitions/oracle2.sql +0 -6
  267. data/test/fixtures/db_definitions/postgresql.drop.sql +0 -44
  268. data/test/fixtures/db_definitions/postgresql.sql +0 -292
  269. data/test/fixtures/db_definitions/postgresql2.drop.sql +0 -2
  270. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  271. data/test/fixtures/db_definitions/schema.rb +0 -354
  272. data/test/fixtures/db_definitions/schema2.rb +0 -11
  273. data/test/fixtures/db_definitions/sqlite.drop.sql +0 -33
  274. data/test/fixtures/db_definitions/sqlite.sql +0 -219
  275. data/test/fixtures/db_definitions/sqlite2.drop.sql +0 -2
  276. data/test/fixtures/db_definitions/sqlite2.sql +0 -5
  277. data/test/fixtures/db_definitions/sybase.drop.sql +0 -35
  278. data/test/fixtures/db_definitions/sybase.sql +0 -222
  279. data/test/fixtures/db_definitions/sybase2.drop.sql +0 -4
  280. data/test/fixtures/db_definitions/sybase2.sql +0 -5
  281. data/test/fixtures/developers_projects/david_action_controller +0 -3
  282. data/test/fixtures/developers_projects/david_active_record +0 -3
  283. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  284. data/test/fixtures/person.rb +0 -4
  285. data/test/fixtures/pirate.rb +0 -5
  286. data/test/fixtures/subscribers/first +0 -2
  287. data/test/fixtures/subscribers/second +0 -2
  288. data/test/schema_dumper_test.rb +0 -131
  289. data/test/schema_test_postgresql.rb +0 -64
@@ -1,16 +1,6 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
4
- def initialize(owner, reflection)
5
- super
6
- construct_sql
7
- end
8
-
9
- def build(attributes = {})
10
- load_target
11
- build_record(attributes)
12
- end
13
-
14
4
  def create(attributes = {})
15
5
  create_record(attributes) { |record| insert_record(record) }
16
6
  end
@@ -19,53 +9,13 @@ module ActiveRecord
19
9
  create_record(attributes) { |record| insert_record(record, true) }
20
10
  end
21
11
 
22
- def find_first
23
- load_target.first
24
- end
25
-
26
- def find(*args)
27
- options = args.extract_options!
28
-
29
- # If using a custom finder_sql, scan the entire collection.
30
- if @reflection.options[:finder_sql]
31
- expects_array = args.first.kind_of?(Array)
32
- ids = args.flatten.compact.uniq
33
-
34
- if ids.size == 1
35
- id = ids.first.to_i
36
- record = load_target.detect { |r| id == r.id }
37
- expects_array ? [record] : record
38
- else
39
- load_target.select { |r| ids.include?(r.id) }
40
- end
41
- else
42
- conditions = "#{@finder_sql}"
43
-
44
- if sanitized_conditions = sanitize_sql(options[:conditions])
45
- conditions << " AND (#{sanitized_conditions})"
46
- end
47
-
48
- options[:conditions] = conditions
12
+ protected
13
+ def construct_find_options!(options)
49
14
  options[:joins] = @join_sql
50
15
  options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
51
-
52
- if options[:order] && @reflection.options[:order]
53
- options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
54
- elsif @reflection.options[:order]
55
- options[:order] = @reflection.options[:order]
56
- end
57
-
58
- merge_options_from_reflection!(options)
59
-
60
- options[:select] ||= (@reflection.options[:select] || '*')
61
-
62
- # Pass through args exactly as we received them.
63
- args << options
64
- @reflection.klass.find(*args)
16
+ options[:select] ||= (@reflection.options[:select] || '*')
65
17
  end
66
- end
67
-
68
- protected
18
+
69
19
  def count_records
70
20
  load_target.size
71
21
  end
@@ -85,10 +35,10 @@ module ActiveRecord
85
35
  columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
86
36
 
87
37
  attributes = columns.inject({}) do |attrs, column|
88
- case column.name
89
- when @reflection.primary_key_name
38
+ case column.name.to_s
39
+ when @reflection.primary_key_name.to_s
90
40
  attrs[column.name] = @owner.quoted_id
91
- when @reflection.association_foreign_key
41
+ when @reflection.association_foreign_key.to_s
92
42
  attrs[column.name] = record.quoted_id
93
43
  else
94
44
  if record.has_attribute?(column.name)
@@ -100,7 +50,7 @@ module ActiveRecord
100
50
  end
101
51
 
102
52
  sql =
103
- "INSERT INTO #{@reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
53
+ "INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
104
54
  "VALUES (#{attributes.values.join(', ')})"
105
55
 
106
56
  @owner.connection.insert(sql)
@@ -125,11 +75,11 @@ module ActiveRecord
125
75
  if @reflection.options[:finder_sql]
126
76
  @finder_sql = @reflection.options[:finder_sql]
127
77
  else
128
- @finder_sql = "#{@reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
78
+ @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
129
79
  @finder_sql << " AND (#{conditions})" if conditions
130
80
  end
131
81
 
132
- @join_sql = "INNER JOIN #{@reflection.options[:join_table]} ON #{@reflection.klass.table_name}.#{@reflection.klass.primary_key} = #{@reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
82
+ @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}"
133
83
  end
134
84
 
135
85
  def construct_scope
@@ -148,15 +98,13 @@ module ActiveRecord
148
98
  end
149
99
 
150
100
  private
151
- def create_record(attributes)
101
+ def create_record(attributes, &block)
152
102
  # Can't use Base.create because the foreign key may be a protected attribute.
153
103
  ensure_owner_is_not_new
154
104
  if attributes.is_a?(Array)
155
105
  attributes.collect { |attr| create(attr) }
156
106
  else
157
- record = build(attributes)
158
- yield(record)
159
- record
107
+ build_record(attributes, &block)
160
108
  end
161
109
  end
162
110
  end
@@ -1,19 +1,6 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
3
  class HasManyAssociation < AssociationCollection #:nodoc:
4
- def initialize(owner, reflection)
5
- super
6
- construct_sql
7
- end
8
-
9
- def build(attributes = {})
10
- if attributes.is_a?(Array)
11
- attributes.collect { |attr| build(attr) }
12
- else
13
- build_record(attributes) { |record| set_belongs_to_association_for(record) }
14
- end
15
- end
16
-
17
4
  # Count the number of associated records. All arguments are optional.
18
5
  def count(*args)
19
6
  if @reflection.options[:counter_sql]
@@ -22,7 +9,7 @@ module ActiveRecord
22
9
  @reflection.klass.count_by_sql(@finder_sql)
23
10
  else
24
11
  column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
25
- options[:conditions] = options[:conditions].nil? ?
12
+ options[:conditions] = options[:conditions].blank? ?
26
13
  @finder_sql :
27
14
  @finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
28
15
  options[:include] ||= @reflection.options[:include]
@@ -31,62 +18,7 @@ module ActiveRecord
31
18
  end
32
19
  end
33
20
 
34
- def find(*args)
35
- options = args.extract_options!
36
-
37
- # If using a custom finder_sql, scan the entire collection.
38
- if @reflection.options[:finder_sql]
39
- expects_array = args.first.kind_of?(Array)
40
- ids = args.flatten.compact.uniq.map(&:to_i)
41
-
42
- if ids.size == 1
43
- id = ids.first
44
- record = load_target.detect { |r| id == r.id }
45
- expects_array ? [ record ] : record
46
- else
47
- load_target.select { |r| ids.include?(r.id) }
48
- end
49
- else
50
- conditions = "#{@finder_sql}"
51
- if sanitized_conditions = sanitize_sql(options[:conditions])
52
- conditions << " AND (#{sanitized_conditions})"
53
- end
54
- options[:conditions] = conditions
55
-
56
- if options[:order] && @reflection.options[:order]
57
- options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
58
- elsif @reflection.options[:order]
59
- options[:order] = @reflection.options[:order]
60
- end
61
-
62
- merge_options_from_reflection!(options)
63
-
64
- # Pass through args exactly as we received them.
65
- args << options
66
- @reflection.klass.find(*args)
67
- end
68
- end
69
-
70
21
  protected
71
- def load_target
72
- if !@owner.new_record? || foreign_key_present
73
- begin
74
- if !loaded?
75
- if @target.is_a?(Array) && @target.any?
76
- @target = (find_target + @target).uniq
77
- else
78
- @target = find_target
79
- end
80
- end
81
- rescue ActiveRecord::RecordNotFound
82
- reset
83
- end
84
- end
85
-
86
- loaded if target
87
- target
88
- end
89
-
90
22
  def count_records
91
23
  count = if has_cached_counter?
92
24
  @owner.send(:read_attribute, cached_counter_attribute_name)
@@ -144,12 +76,12 @@ module ActiveRecord
144
76
 
145
77
  when @reflection.options[:as]
146
78
  @finder_sql =
147
- "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
148
- "#{@reflection.klass.table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
79
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
80
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
149
81
  @finder_sql << " AND (#{conditions})" if conditions
150
82
 
151
83
  else
152
- @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
84
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
153
85
  @finder_sql << " AND (#{conditions})" if conditions
154
86
  end
155
87
 
@@ -167,7 +99,10 @@ module ActiveRecord
167
99
  def construct_scope
168
100
  create_scoping = {}
169
101
  set_belongs_to_association_for(create_scoping)
170
- { :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] }, :create => create_scoping }
102
+ {
103
+ :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] },
104
+ :create => create_scoping
105
+ }
171
106
  end
172
107
  end
173
108
  end
@@ -1,104 +1,23 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
- class HasManyThroughAssociation < AssociationProxy #:nodoc:
3
+ class HasManyThroughAssociation < HasManyAssociation #:nodoc:
4
4
  def initialize(owner, reflection)
5
- super
6
5
  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
6
+ super
66
7
  end
67
8
 
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?
9
+ alias_method :new, :build
85
10
 
86
- @owner.send(through.name).proxy_target.delete(klass.delete_all(construct_join_attributes(associate)))
87
- @target.delete(associate)
88
- end
11
+ def create!(attrs = nil)
12
+ @reflection.klass.transaction do
13
+ self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! } : @reflection.klass.create!)
14
+ object
89
15
  end
90
-
91
- self
92
16
  end
93
17
 
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)
18
+ def create(attrs = nil)
100
19
  @reflection.klass.transaction do
101
- self << (object = @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! })
20
+ self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create } : @reflection.klass.create)
102
21
  object
103
22
  end
104
23
  end
@@ -111,33 +30,47 @@ module ActiveRecord
111
30
  return @target.size if loaded?
112
31
  return count
113
32
  end
114
-
115
- # Calculate sum using SQL, not Enumerable
116
- def sum(*args, &block)
117
- calculate(:sum, *args, &block)
118
- end
119
33
 
120
34
  def count(*args)
121
35
  column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
122
36
  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
37
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL statement.
38
+ column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
125
39
  options.merge!(:distinct => true)
126
40
  end
127
41
  @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
128
42
  end
129
43
 
130
44
  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) }
45
+ def construct_find_options!(options)
46
+ options[:select] = construct_select(options[:select])
47
+ options[:from] ||= construct_from
48
+ options[:joins] = construct_joins(options[:joins])
49
+ options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
50
+ end
51
+
52
+ def insert_record(record, force=true)
53
+ if record.new_record?
54
+ if force
55
+ record.save!
56
+ else
57
+ return false unless record.save
58
+ end
59
+ end
60
+ klass = @reflection.through_reflection.klass
61
+ @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { klass.create! }
62
+ end
63
+
64
+ # TODO - add dependent option support
65
+ def delete_records(records)
66
+ klass = @reflection.through_reflection.klass
67
+ records.each do |associate|
68
+ klass.delete_all(construct_join_attributes(associate))
136
69
  end
137
70
  end
138
71
 
139
72
  def find_target
140
- records = @reflection.klass.find(:all,
73
+ @reflection.klass.find(:all,
141
74
  :select => construct_select,
142
75
  :conditions => construct_conditions,
143
76
  :from => construct_from,
@@ -145,11 +78,9 @@ module ActiveRecord
145
78
  :order => @reflection.options[:order],
146
79
  :limit => @reflection.options[:limit],
147
80
  :group => @reflection.options[:group],
81
+ :readonly => @reflection.options[:readonly],
148
82
  :include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
149
83
  )
150
-
151
- records.uniq! if @reflection.options[:uniq]
152
- records
153
84
  end
154
85
 
155
86
  # Construct attributes for associate pointing to owner.
@@ -164,6 +95,8 @@ module ActiveRecord
164
95
 
165
96
  # Construct attributes for :through pointing to owner and associate.
166
97
  def construct_join_attributes(associate)
98
+ # TODO: revist this to allow it for deletion, supposing dependent option is supported
99
+ raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
167
100
  join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
168
101
  if @reflection.options[:source_type]
169
102
  join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
@@ -185,7 +118,7 @@ module ActiveRecord
185
118
 
186
119
  # Build SQL conditions from attributes, qualified by table name.
187
120
  def construct_conditions
188
- table_name = @reflection.through_reflection.table_name
121
+ table_name = @reflection.through_reflection.quoted_table_name
189
122
  conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
190
123
  "#{table_name}.#{attr} = #{value}"
191
124
  end
@@ -194,21 +127,22 @@ module ActiveRecord
194
127
  end
195
128
 
196
129
  def construct_from
197
- @reflection.table_name
130
+ @reflection.quoted_table_name
198
131
  end
199
132
 
200
133
  def construct_select(custom_select = nil)
201
- selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*"
134
+ distinct = "DISTINCT " if @reflection.options[:uniq]
135
+ selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
202
136
  end
203
137
 
204
138
  def construct_joins(custom_joins = nil)
205
139
  polymorphic_join = nil
206
- if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
140
+ if @reflection.source_reflection.macro == :belongs_to
207
141
  reflection_primary_key = @reflection.klass.primary_key
208
142
  source_primary_key = @reflection.source_reflection.primary_key_name
209
143
  if @reflection.options[:source_type]
210
144
  polymorphic_join = "AND %s.%s = %s" % [
211
- @reflection.through_reflection.table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
145
+ @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
212
146
  @owner.class.quote_value(@reflection.options[:source_type])
213
147
  ]
214
148
  end
@@ -217,7 +151,7 @@ module ActiveRecord
217
151
  source_primary_key = @reflection.klass.primary_key
218
152
  if @reflection.source_reflection.options[:as]
219
153
  polymorphic_join = "AND %s.%s = %s" % [
220
- @reflection.table_name, "#{@reflection.source_reflection.options[:as]}_type",
154
+ @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
221
155
  @owner.class.quote_value(@reflection.through_reflection.klass.name)
222
156
  ]
223
157
  end
@@ -236,9 +170,12 @@ module ActiveRecord
236
170
  :find => { :from => construct_from,
237
171
  :conditions => construct_conditions,
238
172
  :joins => construct_joins,
173
+ :include => @reflection.options[:include],
239
174
  :select => construct_select,
240
175
  :order => @reflection.options[:order],
241
- :limit => @reflection.options[:limit] } }
176
+ :limit => @reflection.options[:limit],
177
+ :readonly => @reflection.options[:readonly],
178
+ } }
242
179
  end
243
180
 
244
181
  def construct_sql
@@ -246,8 +183,10 @@ module ActiveRecord
246
183
  when @reflection.options[:finder_sql]
247
184
  @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
248
185
 
249
- @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
186
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
250
187
  @finder_sql << " AND (#{conditions})" if conditions
188
+ else
189
+ @finder_sql = construct_conditions
251
190
  end
252
191
 
253
192
  if @reflection.options[:counter_sql]
@@ -268,25 +207,37 @@ module ActiveRecord
268
207
 
269
208
  def build_conditions
270
209
  association_conditions = @reflection.options[:conditions]
271
- through_conditions = @reflection.through_reflection.options[:conditions]
210
+ through_conditions = build_through_conditions
272
211
  source_conditions = @reflection.source_reflection.options[:conditions]
273
212
  uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
274
213
 
275
214
  if association_conditions || through_conditions || source_conditions || uses_sti
276
215
  all = []
277
216
 
278
- [association_conditions, through_conditions, source_conditions].each do |conditions|
217
+ [association_conditions, source_conditions].each do |conditions|
279
218
  all << interpolate_sql(sanitize_sql(conditions)) if conditions
280
219
  end
281
220
 
221
+ all << through_conditions if through_conditions
282
222
  all << build_sti_condition if uses_sti
283
223
 
284
224
  all.map { |sql| "(#{sql})" } * ' AND '
285
225
  end
286
226
  end
287
227
 
228
+ def build_through_conditions
229
+ conditions = @reflection.through_reflection.options[:conditions]
230
+ if conditions.is_a?(Hash)
231
+ interpolate_sql(sanitize_sql(conditions)).gsub(
232
+ @reflection.quoted_table_name,
233
+ @reflection.through_reflection.quoted_table_name)
234
+ elsif conditions
235
+ interpolate_sql(sanitize_sql(conditions))
236
+ end
237
+ end
238
+
288
239
  def build_sti_condition
289
- "#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.name.demodulize)}"
240
+ "#{@reflection.through_reflection.quoted_table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.sti_name)}"
290
241
  end
291
242
 
292
243
  alias_method :sql_conditions, :conditions