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
@@ -3,6 +3,71 @@ require 'set'
3
3
  module ActiveRecord
4
4
  module Associations
5
5
  class AssociationCollection < AssociationProxy #:nodoc:
6
+ def initialize(owner, reflection)
7
+ super
8
+ construct_sql
9
+ end
10
+
11
+ def find(*args)
12
+ options = args.extract_options!
13
+
14
+ # If using a custom finder_sql, scan the entire collection.
15
+ if @reflection.options[:finder_sql]
16
+ expects_array = args.first.kind_of?(Array)
17
+ ids = args.flatten.compact.uniq.map(&:to_i)
18
+
19
+ if ids.size == 1
20
+ id = ids.first
21
+ record = load_target.detect { |r| id == r.id }
22
+ expects_array ? [ record ] : record
23
+ else
24
+ load_target.select { |r| ids.include?(r.id) }
25
+ end
26
+ else
27
+ conditions = "#{@finder_sql}"
28
+ if sanitized_conditions = sanitize_sql(options[:conditions])
29
+ conditions << " AND (#{sanitized_conditions})"
30
+ end
31
+
32
+ options[:conditions] = conditions
33
+
34
+ if options[:order] && @reflection.options[:order]
35
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
36
+ elsif @reflection.options[:order]
37
+ options[:order] = @reflection.options[:order]
38
+ end
39
+
40
+ # Build options specific to association
41
+ construct_find_options!(options)
42
+
43
+ merge_options_from_reflection!(options)
44
+
45
+ # Pass through args exactly as we received them.
46
+ args << options
47
+ @reflection.klass.find(*args)
48
+ end
49
+ end
50
+
51
+ # Fetches the first one using SQL if possible.
52
+ def first(*args)
53
+ if fetch_first_or_last_using_find? args
54
+ find(:first, *args)
55
+ else
56
+ load_target unless loaded?
57
+ @target.first(*args)
58
+ end
59
+ end
60
+
61
+ # Fetches the last one using SQL if possible.
62
+ def last(*args)
63
+ if fetch_first_or_last_using_find? args
64
+ find(:last, *args)
65
+ else
66
+ load_target unless loaded?
67
+ @target.last(*args)
68
+ end
69
+ end
70
+
6
71
  def to_ary
7
72
  load_target
8
73
  @target.to_ary
@@ -13,6 +78,14 @@ module ActiveRecord
13
78
  @loaded = false
14
79
  end
15
80
 
81
+ def build(attributes = {})
82
+ if attributes.is_a?(Array)
83
+ attributes.collect { |attr| build(attr) }
84
+ else
85
+ build_record(attributes) { |record| set_belongs_to_association_for(record) }
86
+ end
87
+ end
88
+
16
89
  # Add +records+ to this association. Returns +self+ so method calls may be chained.
17
90
  # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
18
91
  def <<(*records)
@@ -22,10 +95,9 @@ module ActiveRecord
22
95
  @owner.transaction do
23
96
  flatten_deeper(records).each do |record|
24
97
  raise_on_type_mismatch(record)
25
- callback(:before_add, record)
26
- result &&= insert_record(record) unless @owner.new_record?
27
- @target << record
28
- callback(:after_add, record)
98
+ add_record_to_target_with_callbacks(record) do |r|
99
+ result &&= insert_record(record) unless @owner.new_record?
100
+ end
29
101
  end
30
102
  end
31
103
 
@@ -41,22 +113,27 @@ module ActiveRecord
41
113
  delete(@target)
42
114
  reset_target!
43
115
  end
44
-
116
+
45
117
  # Calculate sum using SQL, not Enumerable
46
- def sum(*args, &block)
47
- calculate(:sum, *args, &block)
118
+ def sum(*args)
119
+ if block_given?
120
+ calculate(:sum, *args) { |*block_args| yield(*block_args) }
121
+ else
122
+ calculate(:sum, *args)
123
+ end
48
124
  end
49
125
 
50
126
  # Remove +records+ from this association. Does not destroy +records+.
51
127
  def delete(*records)
52
128
  records = flatten_deeper(records)
53
129
  records.each { |record| raise_on_type_mismatch(record) }
54
- records.reject! { |record| @target.delete(record) if record.new_record? }
55
- return if records.empty?
56
130
 
57
131
  @owner.transaction do
58
132
  records.each { |record| callback(:before_remove, record) }
59
- delete_records(records)
133
+
134
+ old_records = records.reject {|r| r.new_record? }
135
+ delete_records(old_records) if old_records.any?
136
+
60
137
  records.each do |record|
61
138
  @target.delete(record)
62
139
  callback(:after_remove, record)
@@ -89,12 +166,18 @@ module ActiveRecord
89
166
  if attrs.is_a?(Array)
90
167
  attrs.collect { |attr| create(attr) }
91
168
  else
92
- create_record(attrs) { |record| record.save }
169
+ create_record(attrs) do |record|
170
+ yield(record) if block_given?
171
+ record.save
172
+ end
93
173
  end
94
174
  end
95
175
 
96
176
  def create!(attrs = {})
97
- create_record(attrs) { |record| record.save! }
177
+ create_record(attrs) do |record|
178
+ yield(record) if block_given?
179
+ record.save!
180
+ end
98
181
  end
99
182
 
100
183
  # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
@@ -121,9 +204,9 @@ module ActiveRecord
121
204
  size.zero?
122
205
  end
123
206
 
124
- def any?(&block)
207
+ def any?
125
208
  if block_given?
126
- method_missing(:any?, &block)
209
+ method_missing(:any?) { |*block_args| yield(*block_args) }
127
210
  else
128
211
  !empty?
129
212
  end
@@ -155,13 +238,53 @@ module ActiveRecord
155
238
  end
156
239
  end
157
240
 
241
+ def include?(record)
242
+ return false unless record.is_a?(@reflection.klass)
243
+ load_target if @reflection.options[:finder_sql] && !loaded?
244
+ return @target.include?(record) if loaded?
245
+ exists?(record)
246
+ end
158
247
 
159
248
  protected
160
- def method_missing(method, *args, &block)
249
+ def construct_find_options!(options)
250
+ end
251
+
252
+ def load_target
253
+ if !@owner.new_record? || foreign_key_present
254
+ begin
255
+ if !loaded?
256
+ if @target.is_a?(Array) && @target.any?
257
+ @target = find_target + @target.find_all {|t| t.new_record? }
258
+ else
259
+ @target = find_target
260
+ end
261
+ end
262
+ rescue ActiveRecord::RecordNotFound
263
+ reset
264
+ end
265
+ end
266
+
267
+ loaded if target
268
+ target
269
+ end
270
+
271
+ def method_missing(method, *args)
161
272
  if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
162
- super
163
- else
164
- @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.send(method, *args, &block) }
273
+ if block_given?
274
+ super { |*block_args| yield(*block_args) }
275
+ else
276
+ super
277
+ end
278
+ elsif @reflection.klass.scopes.include?(method)
279
+ @reflection.klass.scopes[method].call(self, *args)
280
+ else
281
+ with_scope(construct_scope) do
282
+ if block_given?
283
+ @reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
284
+ else
285
+ @reflection.klass.send(method, *args)
286
+ end
287
+ end
165
288
  end
166
289
  end
167
290
 
@@ -187,15 +310,25 @@ module ActiveRecord
187
310
 
188
311
  private
189
312
 
190
- def create_record(attrs, &block)
313
+ def create_record(attrs)
314
+ attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
191
315
  ensure_owner_is_not_new
192
316
  record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { @reflection.klass.new(attrs) }
193
- add_record_to_target_with_callbacks(record, &block)
317
+ if block_given?
318
+ add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
319
+ else
320
+ add_record_to_target_with_callbacks(record)
321
+ end
194
322
  end
195
323
 
196
- def build_record(attrs, &block)
324
+ def build_record(attrs)
325
+ attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
197
326
  record = @reflection.klass.new(attrs)
198
- add_record_to_target_with_callbacks(record, &block)
327
+ if block_given?
328
+ add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
329
+ else
330
+ add_record_to_target_with_callbacks(record)
331
+ end
199
332
  end
200
333
 
201
334
  def add_record_to_target_with_callbacks(record)
@@ -209,21 +342,10 @@ module ActiveRecord
209
342
 
210
343
  def callback(method, record)
211
344
  callbacks_for(method).each do |callback|
212
- case callback
213
- when Symbol
214
- @owner.send(callback, record)
215
- when Proc, Method
216
- callback.call(@owner, record)
217
- else
218
- if callback.respond_to?(method)
219
- callback.send(method, @owner, record)
220
- else
221
- raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
222
- end
223
- end
345
+ ActiveSupport::Callbacks::Callback.new(method, callback, record).call(@owner, record)
224
346
  end
225
347
  end
226
-
348
+
227
349
  def callbacks_for(callback_name)
228
350
  full_callback_name = "#{callback_name}_for_#{@reflection.name}"
229
351
  @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
@@ -234,7 +356,10 @@ module ActiveRecord
234
356
  raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
235
357
  end
236
358
  end
237
-
359
+
360
+ def fetch_first_or_last_using_find?(args)
361
+ args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer))
362
+ end
238
363
  end
239
364
  end
240
- end
365
+ end
@@ -1,11 +1,55 @@
1
1
  module ActiveRecord
2
2
  module Associations
3
+ # This is the root class of all association proxies:
4
+ #
5
+ # AssociationProxy
6
+ # BelongsToAssociation
7
+ # HasOneAssociation
8
+ # BelongsToPolymorphicAssociation
9
+ # AssociationCollection
10
+ # HasAndBelongsToManyAssociation
11
+ # HasManyAssociation
12
+ # HasManyThroughAssociation
13
+ # HasOneThroughAssociation
14
+ #
15
+ # Association proxies in Active Record are middlemen between the object that
16
+ # holds the association, known as the <tt>@owner</tt>, and the actual associated
17
+ # object, known as the <tt>@target</tt>. The kind of association any proxy is
18
+ # about is available in <tt>@reflection</tt>. That's an instance of the class
19
+ # ActiveRecord::Reflection::AssociationReflection.
20
+ #
21
+ # For example, given
22
+ #
23
+ # class Blog < ActiveRecord::Base
24
+ # has_many :posts
25
+ # end
26
+ #
27
+ # blog = Blog.find(:first)
28
+ #
29
+ # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
30
+ # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
31
+ # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
32
+ #
33
+ # This class has most of the basic instance methods removed, and delegates
34
+ # unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a
35
+ # corner case, it even removes the +class+ method and that's why you get
36
+ #
37
+ # blog.posts.class # => Array
38
+ #
39
+ # though the object behind <tt>blog.posts</tt> is not an Array, but an
40
+ # ActiveRecord::Associations::HasManyAssociation.
41
+ #
42
+ # The <tt>@target</tt> object is not loaded until needed. For example,
43
+ #
44
+ # blog.posts.count
45
+ #
46
+ # is computed directly through SQL and does not trigger by itself the
47
+ # instantiation of the actual post records.
3
48
  class AssociationProxy #:nodoc:
4
- attr_reader :reflection
5
49
  alias_method :proxy_respond_to?, :respond_to?
6
50
  alias_method :proxy_extend, :extend
7
51
  delegate :to_param, :to => :proxy_target
8
- instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_)/ }
52
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
9
53
 
10
54
  def initialize(owner, reflection)
11
55
  @owner, @reflection = owner, reflection
@@ -74,7 +118,7 @@ module ActiveRecord
74
118
  end
75
119
 
76
120
  def inspect
77
- reload unless loaded?
121
+ load_target
78
122
  @target.inspect
79
123
  end
80
124
 
@@ -115,17 +159,36 @@ module ActiveRecord
115
159
  :offset => @reflection.options[:offset],
116
160
  :joins => @reflection.options[:joins],
117
161
  :include => @reflection.options[:include],
118
- :select => @reflection.options[:select]
162
+ :select => @reflection.options[:select],
163
+ :readonly => @reflection.options[:readonly]
119
164
  )
120
165
  end
121
166
 
167
+ def with_scope(*args, &block)
168
+ @reflection.klass.send :with_scope, *args, &block
169
+ end
170
+
122
171
  private
123
- def method_missing(method, *args, &block)
172
+ def method_missing(method, *args)
124
173
  if load_target
125
- @target.send(method, *args, &block)
174
+ if block_given?
175
+ @target.send(method, *args) { |*block_args| yield(*block_args) }
176
+ else
177
+ @target.send(method, *args)
178
+ end
126
179
  end
127
180
  end
128
181
 
182
+ # Loads the target if needed and returns it.
183
+ #
184
+ # This method is abstract in the sense that it relies on +find_target+,
185
+ # which is expected to be provided by descendants.
186
+ #
187
+ # If the target is already loaded it is just returned. Thus, you can call
188
+ # +load_target+ unconditionally to get the target.
189
+ #
190
+ # ActiveRecord::RecordNotFound is rescued within the method, and it is
191
+ # not reraised. The proxy is reset and +nil+ is the return value.
129
192
  def load_target
130
193
  return nil unless defined?(@loaded)
131
194
 
@@ -147,7 +210,8 @@ module ActiveRecord
147
210
 
148
211
  def raise_on_type_mismatch(record)
149
212
  unless record.is_a?(@reflection.klass)
150
- raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.klass} expected, got #{record.class}"
213
+ message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
214
+ raise ActiveRecord::AssociationTypeMismatch, message
151
215
  end
152
216
  end
153
217
 
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  counter_cache_name = @reflection.counter_cache_column
14
14
 
15
15
  if record.nil?
16
- if counter_cache_name && @owner[counter_cache_name] && !@owner.new_record?
16
+ if counter_cache_name && !@owner.new_record?
17
17
  @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
18
18
  end
19
19
 
@@ -42,9 +42,11 @@ module ActiveRecord
42
42
  private
43
43
  def find_target
44
44
  @reflection.klass.find(
45
- @owner[@reflection.primary_key_name],
45
+ @owner[@reflection.primary_key_name],
46
+ :select => @reflection.options[:select],
46
47
  :conditions => conditions,
47
- :include => @reflection.options[:include]
48
+ :include => @reflection.options[:include],
49
+ :readonly => @reflection.options[:readonly]
48
50
  )
49
51
  end
50
52
 
@@ -7,10 +7,8 @@ module ActiveRecord
7
7
  else
8
8
  @target = (AssociationProxy === record ? record.target : record)
9
9
 
10
- unless record.new_record?
11
- @owner[@reflection.primary_key_name] = record.id
12
- @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
13
- end
10
+ @owner[@reflection.primary_key_name] = record.id
11
+ @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
14
12
 
15
13
  @updated = true
16
14
  end
@@ -29,12 +27,13 @@ module ActiveRecord
29
27
 
30
28
  if @reflection.options[:conditions]
31
29
  association_class.find(
32
- @owner[@reflection.primary_key_name],
30
+ @owner[@reflection.primary_key_name],
31
+ :select => @reflection.options[:select],
33
32
  :conditions => conditions,
34
33
  :include => @reflection.options[:include]
35
34
  )
36
35
  else
37
- association_class.find(@owner[@reflection.primary_key_name], :include => @reflection.options[:include])
36
+ association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include])
38
37
  end
39
38
  end
40
39