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
@@ -51,9 +51,11 @@ module ActiveRecord
51
51
  private
52
52
  def find_target
53
53
  @reflection.klass.find(:first,
54
- :conditions => @finder_sql,
54
+ :conditions => @finder_sql,
55
+ :select => @reflection.options[:select],
55
56
  :order => @reflection.options[:order],
56
- :include => @reflection.options[:include]
57
+ :include => @reflection.options[:include],
58
+ :readonly => @reflection.options[:readonly]
57
59
  )
58
60
  end
59
61
 
@@ -61,10 +63,10 @@ module ActiveRecord
61
63
  case
62
64
  when @reflection.options[:as]
63
65
  @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
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
67
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
66
68
  else
67
- @finder_sql = "#{@reflection.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
69
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
68
70
  end
69
71
  @finder_sql << " AND (#{conditions})" if conditions
70
72
  end
@@ -0,0 +1,28 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class HasOneThroughAssociation < HasManyThroughAssociation
4
+
5
+ def create_through_record(new_value) #nodoc:
6
+ klass = @reflection.through_reflection.klass
7
+
8
+ current_object = @owner.send(@reflection.through_reflection.name)
9
+
10
+ if current_object
11
+ klass.destroy(current_object)
12
+ @owner.clear_association_cache
13
+ end
14
+
15
+ @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value)))
16
+ end
17
+
18
+ private
19
+ def find(*args)
20
+ super(args.merge(:limit => 1))
21
+ end
22
+
23
+ def find_target
24
+ super.first
25
+ end
26
+ end
27
+ end
28
+ end
@@ -8,20 +8,28 @@ module ActiveRecord
8
8
  base.attribute_method_suffix(*DEFAULT_SUFFIXES)
9
9
  base.cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
10
10
  base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
11
+ base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false
12
+ base.time_zone_aware_attributes = false
13
+ base.cattr_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
14
+ base.skip_time_zone_conversion_for_attributes = []
11
15
  end
12
16
 
13
17
  # Declare and check for suffixed attribute methods.
14
18
  module ClassMethods
15
- # Declare a method available for all attributes with the given suffix.
16
- # Uses method_missing and respond_to? to rewrite the method
19
+ # Declares a method available for all attributes with the given suffix.
20
+ # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method
21
+ #
17
22
  # #{attr}#{suffix}(*args, &block)
23
+ #
18
24
  # to
25
+ #
19
26
  # attribute#{suffix}(#{attr}, *args, &block)
20
27
  #
21
- # An attribute#{suffix} instance method must exist and accept at least
22
- # the attr argument.
28
+ # An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
29
+ # the +attr+ argument.
23
30
  #
24
31
  # For example:
32
+ #
25
33
  # class Person < ActiveRecord::Base
26
34
  # attribute_method_suffix '_changed?'
27
35
  #
@@ -56,21 +64,27 @@ module ActiveRecord
56
64
  !generated_methods.empty?
57
65
  end
58
66
 
59
- # generates all the attribute related methods for columns in the database
60
- # accessors, mutators and query methods
67
+ # Generates all the attribute related methods for columns in the database
68
+ # accessors, mutators and query methods.
61
69
  def define_attribute_methods
62
70
  return if generated_methods?
63
71
  columns_hash.each do |name, column|
64
72
  unless instance_method_already_implemented?(name)
65
73
  if self.serialized_attributes[name]
66
74
  define_read_method_for_serialized_attribute(name)
75
+ elsif create_time_zone_conversion_attribute?(name, column)
76
+ define_read_method_for_time_zone_conversion(name)
67
77
  else
68
78
  define_read_method(name.to_sym, name, column)
69
79
  end
70
80
  end
71
81
 
72
82
  unless instance_method_already_implemented?("#{name}=")
73
- define_write_method(name.to_sym)
83
+ if create_time_zone_conversion_attribute?(name, column)
84
+ define_write_method_for_time_zone_conversion(name)
85
+ else
86
+ define_write_method(name.to_sym)
87
+ end
74
88
  end
75
89
 
76
90
  unless instance_method_already_implemented?("#{name}?")
@@ -79,8 +93,9 @@ module ActiveRecord
79
93
  end
80
94
  end
81
95
 
82
- # Check to see if the method is defined in the model or any of its subclasses that also derive from ActiveRecord.
83
- # Raise DangerousAttributeError if the method is defined by ActiveRecord though.
96
+ # Checks whether the method is defined in the model or any of its subclasses
97
+ # that also derive from Active Record. Raises DangerousAttributeError if the
98
+ # method is defined by Active Record though.
84
99
  def instance_method_already_implemented?(method_name)
85
100
  method_name = method_name.to_s
86
101
  return true if method_name =~ /^id(=$|\?$|$)/
@@ -94,17 +109,19 @@ module ActiveRecord
94
109
 
95
110
  # +cache_attributes+ allows you to declare which converted attribute values should
96
111
  # be cached. Usually caching only pays off for attributes with expensive conversion
97
- # methods, like date columns (e.g. created_at, updated_at).
112
+ # methods, like time related columns (e.g. +created_at+, +updated_at+).
98
113
  def cache_attributes(*attribute_names)
99
114
  attribute_names.each {|attr| cached_attributes << attr.to_s}
100
115
  end
101
116
 
102
- # returns the attributes where
117
+ # Returns the attributes which are cached. By default time related columns
118
+ # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
103
119
  def cached_attributes
104
120
  @cached_attributes ||=
105
121
  columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map(&:name).to_set
106
122
  end
107
123
 
124
+ # Returns +true+ if the provided attribute is being cached.
108
125
  def cache_attribute?(attr_name)
109
126
  cached_attributes.include?(attr_name)
110
127
  end
@@ -121,6 +138,10 @@ module ActiveRecord
121
138
  @@attribute_method_suffixes ||= []
122
139
  end
123
140
 
141
+ def create_time_zone_conversion_attribute?(name, column)
142
+ time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
143
+ end
144
+
124
145
  # Define an attribute reader method. Cope with nil column.
125
146
  def define_read_method(symbol, attr_name, column)
126
147
  cast_code = column.type_cast_code('v') if column
@@ -140,8 +161,22 @@ module ActiveRecord
140
161
  def define_read_method_for_serialized_attribute(attr_name)
141
162
  evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
142
163
  end
164
+
165
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
166
+ # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
167
+ def define_read_method_for_time_zone_conversion(attr_name)
168
+ method_body = <<-EOV
169
+ def #{attr_name}(reload = false)
170
+ cached = @attributes_cache['#{attr_name}']
171
+ return cached if cached && !reload
172
+ time = read_attribute('#{attr_name}')
173
+ @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
174
+ end
175
+ EOV
176
+ evaluate_attribute_method attr_name, method_body
177
+ end
143
178
 
144
- # Define an attribute ? method.
179
+ # Defines a predicate method <tt>attr_name?</tt>.
145
180
  def define_question_method(attr_name)
146
181
  evaluate_attribute_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end", "#{attr_name}?"
147
182
  end
@@ -149,6 +184,21 @@ module ActiveRecord
149
184
  def define_write_method(attr_name)
150
185
  evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}="
151
186
  end
187
+
188
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
189
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
190
+ def define_write_method_for_time_zone_conversion(attr_name)
191
+ method_body = <<-EOV
192
+ def #{attr_name}=(time)
193
+ unless time.acts_like?(:time)
194
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
195
+ end
196
+ time = time.in_time_zone rescue nil if time
197
+ write_attribute(:#{attr_name}, time)
198
+ end
199
+ EOV
200
+ evaluate_attribute_method attr_name, method_body, "#{attr_name}="
201
+ end
152
202
 
153
203
  # Evaluate the definition for an attribute related method
154
204
  def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name)
@@ -171,14 +221,14 @@ module ActiveRecord
171
221
  end # ClassMethods
172
222
 
173
223
 
174
- # Allows access to the object attributes, which are held in the @attributes hash, as though they
224
+ # Allows access to the object attributes, which are held in the <tt>@attributes</tt> hash, as though they
175
225
  # were first-class methods. So a Person class with a name attribute can use Person#name and
176
226
  # Person#name= and never directly use the attributes hash -- except for multiple assigns with
177
227
  # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
178
- # the completed attribute is not nil or 0.
228
+ # the completed attribute is not +nil+ or 0.
179
229
  #
180
230
  # It's also possible to instantiate related objects, so a Client class belonging to the clients
181
- # table with a master_id foreign key can instantiate master through Client#master.
231
+ # table with a +master_id+ foreign key can instantiate master through Client#master.
182
232
  def method_missing(method_id, *args, &block)
183
233
  method_name = method_id.to_s
184
234
 
@@ -249,7 +299,7 @@ module ActiveRecord
249
299
 
250
300
 
251
301
  # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
252
- # columns are turned into nil.
302
+ # columns are turned into +nil+.
253
303
  def write_attribute(attr_name, value)
254
304
  attr_name = attr_name.to_s
255
305
  @attributes_cache.delete(attr_name)
@@ -280,8 +330,9 @@ module ActiveRecord
280
330
  end
281
331
  end
282
332
 
283
- # A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
284
- # person.respond_to?("name?") which will all return true.
333
+ # A Person object with a name attribute can ask <tt>person.respond_to?("name")</tt>,
334
+ # <tt>person.respond_to?("name=")</tt>, and <tt>person.respond_to?("name?")</tt>
335
+ # which will all return +true+.
285
336
  alias :respond_to_without_attributes? :respond_to?
286
337
  def respond_to?(method, include_priv = false)
287
338
  method_name = method.to_s
@@ -303,7 +354,6 @@ module ActiveRecord
303
354
  end
304
355
  super
305
356
  end
306
-
307
357
 
308
358
  private
309
359
 
@@ -2,7 +2,7 @@ require 'yaml'
2
2
  require 'set'
3
3
 
4
4
  module ActiveRecord #:nodoc:
5
- # Generic ActiveRecord exception class.
5
+ # Generic Active Record exception class.
6
6
  class ActiveRecordError < StandardError
7
7
  end
8
8
 
@@ -11,22 +11,18 @@ module ActiveRecord #:nodoc:
11
11
  class SubclassNotFound < ActiveRecordError #:nodoc:
12
12
  end
13
13
 
14
- # Raised when object assigned to association is of incorrect type.
14
+ # Raised when an object assigned to an association has an incorrect type.
15
15
  #
16
- # Example:
17
- #
18
- # class Ticket < ActiveRecord::Base
19
- # has_many :patches
20
- # end
21
- #
22
- # class Patch < ActiveRecord::Base
23
- # belongs_to :ticket
24
- # end
16
+ # class Ticket < ActiveRecord::Base
17
+ # has_many :patches
18
+ # end
25
19
  #
26
- # and somewhere in the code:
20
+ # class Patch < ActiveRecord::Base
21
+ # belongs_to :ticket
22
+ # end
27
23
  #
28
- # @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
29
- # @ticket.save
24
+ # # Comments are not patches, this assignment raises AssociationTypeMismatch.
25
+ # @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
30
26
  class AssociationTypeMismatch < ActiveRecordError
31
27
  end
32
28
 
@@ -34,19 +30,19 @@ module ActiveRecord #:nodoc:
34
30
  class SerializationTypeMismatch < ActiveRecordError
35
31
  end
36
32
 
37
- # Raised when adapter not specified on connection (or configuration file config/database.yml misses adapter field).
33
+ # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt> misses adapter field).
38
34
  class AdapterNotSpecified < ActiveRecordError
39
35
  end
40
36
 
41
- # Raised when ActiveRecord cannot find database adapter specified in config/database.yml or programmatically.
37
+ # Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
42
38
  class AdapterNotFound < ActiveRecordError
43
39
  end
44
40
 
45
- # Raised when connection to the database could not been established (for example when connection= is given a nil object).
41
+ # Raised when connection to the database could not been established (for example when <tt>connection=</tt> is given a nil object).
46
42
  class ConnectionNotEstablished < ActiveRecordError
47
43
  end
48
44
 
49
- # Raised when ActiveRecord cannot find record by given id or set of ids.
45
+ # Raised when Active Record cannot find record by given id or set of ids.
50
46
  class RecordNotFound < ActiveRecordError
51
47
  end
52
48
 
@@ -59,14 +55,14 @@ module ActiveRecord #:nodoc:
59
55
  class StatementInvalid < ActiveRecordError
60
56
  end
61
57
 
62
- # Raised when number of bind variables in statement given to :condition key (for example, when using +find+ method)
58
+ # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method)
63
59
  # does not match number of expected variables.
64
60
  #
65
- # Example:
61
+ # For example, in
66
62
  #
67
- # Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362]
63
+ # Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362]
68
64
  #
69
- # in example above two placeholders are given but only one variable to fill them.
65
+ # two placeholders are given but only one variable to fill them.
70
66
  class PreparedStatementInvalid < ActiveRecordError
71
67
  end
72
68
 
@@ -74,7 +70,7 @@ module ActiveRecord #:nodoc:
74
70
  # instantiation, for example, when two users edit the same wiki page and one starts editing and saves
75
71
  # the page before the other.
76
72
  #
77
- # Read more about optimistic locking in +ActiveRecord::Locking+ module RDoc.
73
+ # Read more about optimistic locking in ActiveRecord::Locking module RDoc.
78
74
  class StaleObjectError < ActiveRecordError
79
75
  end
80
76
 
@@ -87,22 +83,24 @@ module ActiveRecord #:nodoc:
87
83
  class ReadOnlyRecord < ActiveRecordError
88
84
  end
89
85
 
90
- # Used by ActiveRecord transaction mechanism to distinguish rollback from other exceptional situations.
86
+ # Used by Active Record transaction mechanism to distinguish rollback from other exceptional situations.
91
87
  # You can use it to roll your transaction back explicitly in the block passed to +transaction+ method.
92
88
  class Rollback < ActiveRecordError
93
89
  end
94
90
 
95
- # Raised when attribute has a name reserved by ActiveRecord (when attribute has name of one of ActiveRecord instance methods).
91
+ # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
96
92
  class DangerousAttributeError < ActiveRecordError
97
93
  end
98
94
 
99
- # Raised when you've tried to access a column which wasn't
100
- # loaded by your finder. Typically this is because :select
101
- # has been specified
95
+ # Raised when you've tried to access a column which wasn't loaded by your finder.
96
+ # Typically this is because <tt>:select</tt> has been specified.
102
97
  class MissingAttributeError < NoMethodError
103
98
  end
104
99
 
105
- class AttributeAssignmentError < ActiveRecordError #:nodoc:
100
+ # Raised when an error occured while doing a mass assignment to an attribute through the
101
+ # <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
102
+ # offending attribute.
103
+ class AttributeAssignmentError < ActiveRecordError
106
104
  attr_reader :exception, :attribute
107
105
  def initialize(message, exception, attribute)
108
106
  @exception = exception
@@ -111,7 +109,10 @@ module ActiveRecord #:nodoc:
111
109
  end
112
110
  end
113
111
 
114
- class MultiparameterAssignmentErrors < ActiveRecordError #:nodoc:
112
+ # Raised when there are multiple errors while doing a mass assignment through the +attributes+
113
+ # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
114
+ # objects, each corresponding to the error while assigning to an attribute.
115
+ class MultiparameterAssignmentErrors < ActiveRecordError
115
116
  attr_reader :errors
116
117
  def initialize(errors)
117
118
  @errors = errors
@@ -191,18 +192,22 @@ module ActiveRecord #:nodoc:
191
192
  #
192
193
  # Student.find(:all, :conditions => { :grade => 9..12 })
193
194
  #
195
+ # An array may be used in the hash to use the SQL IN operator:
196
+ #
197
+ # Student.find(:all, :conditions => { :grade => [9,11,12] })
198
+ #
194
199
  # == Overwriting default accessors
195
200
  #
196
201
  # All column values are automatically available through basic accessors on the Active Record object, but sometimes you
197
202
  # want to specialize this behavior. This can be done by overwriting the default accessors (using the same
198
- # name as the attribute) and calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
203
+ # name as the attribute) and calling <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually change things.
199
204
  # Example:
200
205
  #
201
206
  # class Song < ActiveRecord::Base
202
207
  # # Uses an integer of seconds to hold the length of the song
203
208
  #
204
209
  # def length=(minutes)
205
- # write_attribute(:length, minutes * 60)
210
+ # write_attribute(:length, minutes.to_i * 60)
206
211
  # end
207
212
  #
208
213
  # def length
@@ -210,8 +215,8 @@ module ActiveRecord #:nodoc:
210
215
  # end
211
216
  # end
212
217
  #
213
- # You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, value) and
214
- # read_attribute(:attribute) as a shorter form.
218
+ # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt> instead of <tt>write_attribute(:attribute, value)</tt> and
219
+ # <tt>read_attribute(:attribute)</tt> as a shorter form.
215
220
  #
216
221
  # == Attribute query methods
217
222
  #
@@ -230,8 +235,8 @@ module ActiveRecord #:nodoc:
230
235
  # == Accessing attributes before they have been typecasted
231
236
  #
232
237
  # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
233
- # That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
234
- # has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
238
+ # That can be done by using the <tt><attribute>_before_type_cast</tt> accessors that all attributes have. For example, if your Account model
239
+ # has a <tt>balance</tt> attribute, you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
235
240
  #
236
241
  # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
237
242
  # the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
@@ -240,8 +245,8 @@ module ActiveRecord #:nodoc:
240
245
  # == Dynamic attribute-based finders
241
246
  #
242
247
  # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
243
- # appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like Person.find_by_user_name,
244
- # Person.find_all_by_last_name, Payment.find_by_transaction_id. So instead of writing
248
+ # appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
249
+ # <tt>Person.find_all_by_last_name</tt>, and <tt>Payment.find_by_transaction_id</tt>. So instead of writing
245
250
  # <tt>Person.find(:first, :conditions => ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
246
251
  # And instead of writing <tt>Person.find(:all, :conditions => ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
247
252
  #
@@ -250,12 +255,12 @@ module ActiveRecord #:nodoc:
250
255
  # <tt>Person.find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
251
256
  # <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
252
257
  #
253
- # It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
254
- # is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is
255
- # actually Person.find_by_user_name(user_name, options). So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
258
+ # It's even possible to use all the additional parameters to find. For example, the full interface for <tt>Payment.find_all_by_amount</tt>
259
+ # is actually <tt>Payment.find_all_by_amount(amount, options)</tt>. And the full interface to <tt>Person.find_by_user_name</tt> is
260
+ # actually <tt>Person.find_by_user_name(user_name, options)</tt>. So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
256
261
  #
257
262
  # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
258
- # <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Example:
263
+ # <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won't be set unless they are given in a block. For example:
259
264
  #
260
265
  # # No 'Summer' tag exists
261
266
  # Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
@@ -263,7 +268,10 @@ module ActiveRecord #:nodoc:
263
268
  # # Now the 'Summer' tag does exist
264
269
  # Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
265
270
  #
266
- # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Example:
271
+ # # Now 'Bob' exist and is an 'admin'
272
+ # User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
273
+ #
274
+ # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be setted unless they are given in a block. For example:
267
275
  #
268
276
  # # No 'Winter' tag exists
269
277
  # winter = Tag.find_or_initialize_by_name("Winter")
@@ -308,8 +316,8 @@ module ActiveRecord #:nodoc:
308
316
  # class Client < Company; end
309
317
  # class PriorityClient < Client; end
310
318
  #
311
- # When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = "Firm". You can then
312
- # fetch this row again using Company.find(:first, "name = '37signals'") and it will return a Firm object.
319
+ # When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in the companies table with type = "Firm". You can then
320
+ # fetch this row again using <tt>Company.find(:first, "name = '37signals'")</tt> and it will return a Firm object.
313
321
  #
314
322
  # If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
315
323
  # like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
@@ -321,34 +329,34 @@ module ActiveRecord #:nodoc:
321
329
  #
322
330
  # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
323
331
  # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
324
- # For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection
325
- # and Course *and all its subclasses* will use this connection instead.
332
+ # For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
333
+ # and Course and all of its subclasses will use this connection instead.
326
334
  #
327
335
  # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
328
336
  # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
329
337
  #
330
338
  # == Exceptions
331
339
  #
332
- # * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
333
- # * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include an
340
+ # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
341
+ # * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
334
342
  # <tt>:adapter</tt> key.
335
- # * +AdapterNotFound+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a non-existent adapter
343
+ # * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a non-existent adapter
336
344
  # (or a bad spelling of an existing one).
337
- # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
338
- # * +SerializationTypeMismatch+ -- the serialized object wasn't of the class specified as the second parameter.
339
- # * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
340
- # * +RecordNotFound+ -- no record responded to the find* method.
341
- # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
342
- # * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the message.
343
- # Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions.
344
- # * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
345
- # +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
345
+ # * AssociationTypeMismatch - The object assigned to the association wasn't of the type specified in the association definition.
346
+ # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
347
+ # * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt> before querying.
348
+ # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
349
+ # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
350
+ # nothing was found, please check its documentation for further details.
351
+ # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
352
+ # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
353
+ # <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of AttributeAssignmentError
346
354
  # objects that should be inspected to determine which attributes triggered the errors.
347
- # * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
355
+ # * AttributeAssignmentError - An error occurred while doing a mass assignment through the <tt>attributes=</tt> method.
348
356
  # You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
349
357
  #
350
358
  # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
351
- # So it's possible to assign a logger to the class through Base.logger= which will then be used by all
359
+ # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
352
360
  # instances in the current object space.
353
361
  class Base
354
362
  # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
@@ -399,7 +407,7 @@ module ActiveRecord #:nodoc:
399
407
  @@table_name_suffix = ""
400
408
 
401
409
  # Indicates whether table names should be the pluralized versions of the corresponding class names.
402
- # If true, the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
410
+ # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
403
411
  # See table_name for the full rules on table/class naming. This is true, by default.
404
412
  cattr_accessor :pluralize_table_names, :instance_writer => false
405
413
  @@pluralize_table_names = true
@@ -416,7 +424,9 @@ module ActiveRecord #:nodoc:
416
424
  @@default_timezone = :local
417
425
 
418
426
  # Determines whether to use a connection for each thread, or a single shared connection for all threads.
419
- # Defaults to false. Set to true if you're writing a threaded application.
427
+ # Defaults to false. If you're writing a threaded application, set to true
428
+ # and periodically call verify_active_connections! to clear out connections
429
+ # assigned to stale threads.
420
430
  cattr_accessor :allow_concurrency, :instance_writer => false
421
431
  @@allow_concurrency = false
422
432
 
@@ -429,37 +439,51 @@ module ActiveRecord #:nodoc:
429
439
  cattr_accessor :schema_format , :instance_writer => false
430
440
  @@schema_format = :ruby
431
441
 
442
+ # Determine whether to store the full constant name including namespace when using STI
443
+ superclass_delegating_accessor :store_full_sti_class
444
+ self.store_full_sti_class = false
445
+
432
446
  class << self # Class methods
433
- # Find operates with three different retrieval approaches:
447
+ # Find operates with four different retrieval approaches:
434
448
  #
435
- # * Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
449
+ # * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
436
450
  # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
437
- # * Find first: This will return the first record matched by the options used. These options can either be specific
438
- # conditions or merely an order. If no record can be matched, nil is returned.
439
- # * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
440
- #
441
- # All approaches accept an options hash as their last parameter. The options are:
442
- #
443
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
444
- # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
445
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
446
- # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
447
- # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
448
- # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
449
- # or named associations in the same form used for the :include option, which will perform an INNER JOIN on the associated table(s).
451
+ # * Find first - This will return the first record matched by the options used. These options can either be specific
452
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
453
+ # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
454
+ # * Find last - This will return the last record matched by the options used. These options can either be specific
455
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
456
+ # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
457
+ # * Find all - This will return all the records matched by the options used.
458
+ # If no records are found, an empty array is returned. Use
459
+ # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
460
+ #
461
+ # All approaches accept an options hash as their last parameter.
462
+ #
463
+ # ==== Attributes
464
+ #
465
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or <tt>[ "user_name = ?", username ]</tt>. See conditions in the intro.
466
+ # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
467
+ # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
468
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
469
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
470
+ # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
471
+ # or named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s).
450
472
  # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
451
- # Pass :readonly => false to override.
452
- # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
473
+ # Pass <tt>:readonly => false</tt> to override.
474
+ # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
453
475
  # to already defined associations. See eager loading under Associations.
454
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
476
+ # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
455
477
  # include the joined columns.
456
- # * <tt>:from</tt>: By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
478
+ # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
457
479
  # of a database view).
458
- # * <tt>:readonly</tt>: Mark the returned records read-only so they cannot be saved or updated.
459
- # * <tt>:lock</tt>: An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
460
- # :lock => true gives connection's default exclusive lock, usually "FOR UPDATE".
480
+ # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
481
+ # * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
482
+ # <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
483
+ #
484
+ # ==== Examples
461
485
  #
462
- # Examples for find by id:
486
+ # # find by id
463
487
  # Person.find(1) # returns the object for ID = 1
464
488
  # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
465
489
  # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
@@ -467,26 +491,35 @@ module ActiveRecord #:nodoc:
467
491
  # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
468
492
  #
469
493
  # Note that returned records may not be in the same order as the ids you
470
- # provide since database rows are unordered. Give an explicit :order
494
+ # provide since database rows are unordered. Give an explicit <tt>:order</tt>
471
495
  # to ensure the results are sorted.
472
496
  #
473
- # Examples for find first:
497
+ # ==== Examples
498
+ #
499
+ # # find first
474
500
  # Person.find(:first) # returns the first object fetched by SELECT * FROM people
475
501
  # Person.find(:first, :conditions => [ "user_name = ?", user_name])
476
502
  # Person.find(:first, :order => "created_on DESC", :offset => 5)
477
503
  #
478
- # Examples for find all:
504
+ # # find last
505
+ # Person.find(:last) # returns the last object fetched by SELECT * FROM people
506
+ # Person.find(:last, :conditions => [ "user_name = ?", user_name])
507
+ # Person.find(:last, :order => "created_on DESC", :offset => 5)
508
+ #
509
+ # # find all
479
510
  # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
480
511
  # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
512
+ # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
481
513
  # Person.find(:all, :offset => 10, :limit => 10)
482
514
  # Person.find(:all, :include => [ :account, :friends ])
483
515
  # Person.find(:all, :group => "category")
484
516
  #
485
- # Example for find with a lock. Imagine two concurrent transactions:
486
- # each will read person.visits == 2, add 1 to it, and save, resulting
487
- # in two saves of person.visits = 3. By locking the row, the second
517
+ # Example for find with a lock: Imagine two concurrent transactions:
518
+ # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
519
+ # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
488
520
  # transaction has to wait until the first is finished; we get the
489
- # expected person.visits == 4.
521
+ # expected <tt>person.visits == 4</tt>.
522
+ #
490
523
  # Person.transaction do
491
524
  # person = Person.find(1, :lock => true)
492
525
  # person.visits += 1
@@ -499,13 +532,31 @@ module ActiveRecord #:nodoc:
499
532
 
500
533
  case args.first
501
534
  when :first then find_initial(options)
535
+ when :last then find_last(options)
502
536
  when :all then find_every(options)
503
537
  else find_from_ids(args, options)
504
538
  end
505
539
  end
506
540
 
507
- #
508
- # Executes a custom sql query against your database and returns all the results. The results will
541
+ # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
542
+ # same arguments to this method as you can to <tt>find(:first)</tt>.
543
+ def first(*args)
544
+ find(:first, *args)
545
+ end
546
+
547
+ # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
548
+ # same arguments to this method as you can to <tt>find(:last)</tt>.
549
+ def last(*args)
550
+ find(:last, *args)
551
+ end
552
+
553
+ # This is an alias for find(:all). You can pass in all the same arguments to this method as you can
554
+ # to find(:all)
555
+ def all(*args)
556
+ find(:all, *args)
557
+ end
558
+
559
+ # Executes a custom SQL query against your database and returns all the results. The results will
509
560
  # be returned as an array with columns requested encapsulated as attributes of the model you call
510
561
  # this method from. If you call +Product.find_by_sql+ then the results will be returned in a Product
511
562
  # object with the attributes you specified in the SQL query.
@@ -514,13 +565,13 @@ module ActiveRecord #:nodoc:
514
565
  # SELECT will be attributes of the model, whether or not they are columns of the corresponding
515
566
  # table.
516
567
  #
517
- # The +sql+ parameter is a full sql query as a string. It will be called as is, there will be
568
+ # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
518
569
  # no database agnostic conversions performed. This should be a last resort because using, for example,
519
570
  # MySQL specific terms will lock you to using that particular database engine or require you to
520
571
  # change your call if you switch engines
521
572
  #
522
573
  # ==== Examples
523
- # # A simple sql query spanning multiple tables
574
+ # # A simple SQL query spanning multiple tables
524
575
  # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
525
576
  # > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
526
577
  #
@@ -550,10 +601,10 @@ module ActiveRecord #:nodoc:
550
601
  def exists?(id_or_conditions)
551
602
  connection.select_all(
552
603
  construct_finder_sql(
553
- :select => "#{quoted_table_name}.#{primary_key}",
554
- :conditions => expand_id_conditions(id_or_conditions),
604
+ :select => "#{quoted_table_name}.#{primary_key}",
605
+ :conditions => expand_id_conditions(id_or_conditions),
555
606
  :limit => 1
556
- ),
607
+ ),
557
608
  "#{name} Exists"
558
609
  ).size > 0
559
610
  end
@@ -567,13 +618,25 @@ module ActiveRecord #:nodoc:
567
618
  # ==== Examples
568
619
  # # Create a single new object
569
620
  # User.create(:first_name => 'Jamie')
621
+ #
570
622
  # # Create an Array of new objects
571
- # User.create([{:first_name => 'Jamie'}, {:first_name => 'Jeremy'}])
572
- def create(attributes = nil)
623
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
624
+ #
625
+ # # Create a single object and pass it into a block to set other attributes.
626
+ # User.create(:first_name => 'Jamie') do |u|
627
+ # u.is_admin = false
628
+ # end
629
+ #
630
+ # # Creating an Array of new objects using a block, where the block is executed for each object:
631
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
632
+ # u.is_admin = false
633
+ # end
634
+ def create(attributes = nil, &block)
573
635
  if attributes.is_a?(Array)
574
- attributes.collect { |attr| create(attr) }
636
+ attributes.collect { |attr| create(attr, &block) }
575
637
  else
576
638
  object = new(attributes)
639
+ yield(object) if block_given?
577
640
  object.save
578
641
  object
579
642
  end
@@ -582,18 +645,18 @@ module ActiveRecord #:nodoc:
582
645
  # Updates an object (or multiple objects) and saves it to the database, if validations pass.
583
646
  # The resulting object is returned whether the object was saved successfully to the database or not.
584
647
  #
585
- # ==== Options
648
+ # ==== Attributes
586
649
  #
587
- # +id+ This should be the id or an array of ids to be updated
588
- # +attributes+ This should be a Hash of attributes to be set on the object, or an array of Hashes.
650
+ # * +id+ - This should be the id or an array of ids to be updated.
651
+ # * +attributes+ - This should be a Hash of attributes to be set on the object, or an array of Hashes.
589
652
  #
590
653
  # ==== Examples
591
654
  #
592
655
  # # Updating one record:
593
- # Person.update(15, {:user_name => 'Samuel', :group => 'expert'})
656
+ # Person.update(15, { :user_name => 'Samuel', :group => 'expert' })
594
657
  #
595
658
  # # Updating multiple records:
596
- # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
659
+ # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
597
660
  # Person.update(people.keys, people.values)
598
661
  def update(id, attributes)
599
662
  if id.is_a?(Array)
@@ -612,9 +675,9 @@ module ActiveRecord #:nodoc:
612
675
  #
613
676
  # Objects are _not_ instantiated with this method.
614
677
  #
615
- # ==== Options
678
+ # ==== Attributes
616
679
  #
617
- # +id+ Can be either an Integer or an Array of Integers
680
+ # * +id+ - Can be either an Integer or an Array of Integers.
618
681
  #
619
682
  # ==== Examples
620
683
  #
@@ -635,9 +698,9 @@ module ActiveRecord #:nodoc:
635
698
  # This essentially finds the object (or multiple objects) with the given id, creates a new object
636
699
  # from the attributes, and then calls destroy on it.
637
700
  #
638
- # ==== Options
701
+ # ==== Attributes
639
702
  #
640
- # +id+ Can be either an Integer or an Array of Integers
703
+ # * +id+ - Can be either an Integer or an Array of Integers.
641
704
  #
642
705
  # ==== Examples
643
706
  #
@@ -658,12 +721,12 @@ module ActiveRecord #:nodoc:
658
721
  # Updates all records with details given if they match a set of conditions supplied, limits and order can
659
722
  # also be supplied.
660
723
  #
661
- # ==== Options
724
+ # ==== Attributes
662
725
  #
663
- # +updates+ A String of column and value pairs that will be set on any records that match conditions
664
- # +conditions+ An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
665
- # See conditions in the intro for more info.
666
- # +options+ Additional options are :limit and/or :order, see the examples for usage.
726
+ # * +updates+ - A String of column and value pairs that will be set on any records that match conditions.
727
+ # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
728
+ # See conditions in the intro for more info.
729
+ # * +options+ - Additional options are <tt>:limit</tt> and/or <tt>:order</tt>, see the examples for usage.
667
730
  #
668
731
  # ==== Examples
669
732
  #
@@ -677,7 +740,7 @@ module ActiveRecord #:nodoc:
677
740
  # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'",
678
741
  # :order => 'created_at', :limit => 5 )
679
742
  def update_all(updates, conditions = nil, options = {})
680
- sql = "UPDATE #{table_name} SET #{sanitize_sql_for_assignment(updates)} "
743
+ sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} "
681
744
  scope = scope(:find)
682
745
  add_conditions!(sql, conditions, scope)
683
746
  add_order!(sql, options[:order], nil)
@@ -690,9 +753,9 @@ module ActiveRecord #:nodoc:
690
753
  # many records. If you want to simply delete records without worrying about dependent associations or
691
754
  # callbacks, use the much faster +delete_all+ method instead.
692
755
  #
693
- # ==== Options
756
+ # ==== Attributes
694
757
  #
695
- # +conditions+ Conditions are specified the same way as with +find+ method.
758
+ # * +conditions+ - Conditions are specified the same way as with +find+ method.
696
759
  #
697
760
  # ==== Example
698
761
  #
@@ -708,9 +771,9 @@ module ActiveRecord #:nodoc:
708
771
  # calling the destroy method and invoking callbacks. This is a single SQL query, much more efficient
709
772
  # than destroy_all.
710
773
  #
711
- # ==== Options
774
+ # ==== Attributes
712
775
  #
713
- # +conditions+ Conditions are specified the same way as with +find+ method.
776
+ # * +conditions+ - Conditions are specified the same way as with +find+ method.
714
777
  #
715
778
  # ==== Example
716
779
  #
@@ -728,9 +791,9 @@ module ActiveRecord #:nodoc:
728
791
  # The use of this method should be restricted to complicated SQL queries that can't be executed
729
792
  # using the ActiveRecord::Calculations class methods. Look into those before using this.
730
793
  #
731
- # ==== Options
794
+ # ==== Attributes
732
795
  #
733
- # +sql+: An SQL statement which should return a count query from the database, see the example below
796
+ # * +sql+ - An SQL statement which should return a count query from the database, see the example below.
734
797
  #
735
798
  # ==== Examples
736
799
  #
@@ -746,12 +809,11 @@ module ActiveRecord #:nodoc:
746
809
  # with the given ID, altering the given hash of counters by the amount
747
810
  # given by the corresponding value:
748
811
  #
749
- # ==== Options
812
+ # ==== Attributes
750
813
  #
751
- # +id+ The id of the object you wish to update a counter on
752
- # +counters+ An Array of Hashes containing the names of the fields
753
- # to update as keys and the amount to update the field by as
754
- # values
814
+ # * +id+ - The id of the object you wish to update a counter on.
815
+ # * +counters+ - An Array of Hashes containing the names of the fields
816
+ # to update as keys and the amount to update the field by as values.
755
817
  #
756
818
  # ==== Examples
757
819
  #
@@ -777,10 +839,10 @@ module ActiveRecord #:nodoc:
777
839
  # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
778
840
  # shown it would have to run an SQL query to find how many posts and comments there are.
779
841
  #
780
- # ==== Options
842
+ # ==== Attributes
781
843
  #
782
- # +counter_name+ The name of the field that should be incremented
783
- # +id+ The id of the object that should be incremented
844
+ # * +counter_name+ - The name of the field that should be incremented.
845
+ # * +id+ - The id of the object that should be incremented.
784
846
  #
785
847
  # ==== Examples
786
848
  #
@@ -794,10 +856,10 @@ module ActiveRecord #:nodoc:
794
856
  #
795
857
  # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
796
858
  #
797
- # ==== Options
859
+ # ==== Attributes
798
860
  #
799
- # +counter_name+ The name of the field that should be decremented
800
- # +id+ The id of the object that should be decremented
861
+ # * +counter_name+ - The name of the field that should be decremented.
862
+ # * +id+ - The id of the object that should be decremented.
801
863
  #
802
864
  # ==== Examples
803
865
  #
@@ -808,9 +870,15 @@ module ActiveRecord #:nodoc:
808
870
  end
809
871
 
810
872
 
811
- # Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
812
- # <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
813
- # methods to do assignment. This is meant to protect sensitive attributes from being overwritten by URL/form hackers. Example:
873
+ # Attributes named in this macro are protected from mass-assignment,
874
+ # such as <tt>new(attributes)</tt>,
875
+ # <tt>update_attributes(attributes)</tt>, or
876
+ # <tt>attributes=(attributes)</tt>.
877
+ #
878
+ # Mass-assignment to these attributes will simply be ignored, to assign
879
+ # to them you can use direct writer methods. This is meant to protect
880
+ # sensitive attributes from being overwritten by malicious users
881
+ # tampering with URLs or forms.
814
882
  #
815
883
  # class Customer < ActiveRecord::Base
816
884
  # attr_protected :credit_rating
@@ -824,7 +892,8 @@ module ActiveRecord #:nodoc:
824
892
  # customer.credit_rating = "Average"
825
893
  # customer.credit_rating # => "Average"
826
894
  #
827
- # To start from an all-closed default and enable attributes as needed, have a look at attr_accessible.
895
+ # To start from an all-closed default and enable attributes as needed,
896
+ # have a look at +attr_accessible+.
828
897
  def attr_protected(*attributes)
829
898
  write_inheritable_attribute("attr_protected", Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
830
899
  end
@@ -834,19 +903,18 @@ module ActiveRecord #:nodoc:
834
903
  read_inheritable_attribute("attr_protected")
835
904
  end
836
905
 
837
- # Similar to the attr_protected macro, this protects attributes of your model from mass-assignment,
838
- # such as <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>
839
- # however, it does it in the opposite way. This locks all attributes and only allows access to the
840
- # attributes specified. Assignment to attributes not in this list will be ignored and need to be set
841
- # using the direct writer methods instead. This is meant to protect sensitive attributes from being
842
- # overwritten by URL/form hackers. If you'd rather start from an all-open default and restrict
843
- # attributes as needed, have a look at attr_protected.
844
- #
845
- # ==== Options
906
+ # Specifies a white list of model attributes that can be set via
907
+ # mass-assignment, such as <tt>new(attributes)</tt>,
908
+ # <tt>update_attributes(attributes)</tt>, or
909
+ # <tt>attributes=(attributes)</tt>
846
910
  #
847
- # <tt>*attributes</tt> A comma separated list of symbols that represent columns _not_ to be protected
848
- #
849
- # ==== Examples
911
+ # This is the opposite of the +attr_protected+ macro: Mass-assignment
912
+ # will only set attributes in this list, to assign to the rest of
913
+ # attributes you can use direct writer methods. This is meant to protect
914
+ # sensitive attributes from being overwritten by malicious users
915
+ # tampering with URLs or forms. If you'd rather start from an all-open
916
+ # default and restrict attributes as needed, have a look at
917
+ # +attr_protected+.
850
918
  #
851
919
  # class Customer < ActiveRecord::Base
852
920
  # attr_accessible :name, :nickname
@@ -881,12 +949,12 @@ module ActiveRecord #:nodoc:
881
949
  # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
882
950
  # then specify the name of that attribute using this method and it will be handled automatically.
883
951
  # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
884
- # class on retrieval or +SerializationTypeMismatch+ will be raised.
952
+ # class on retrieval or SerializationTypeMismatch will be raised.
885
953
  #
886
- # ==== Options
954
+ # ==== Attributes
887
955
  #
888
- # +attr_name+ The field name that should be serialized
889
- # +class_name+ Optional, class name that the object type should be equal to
956
+ # * +attr_name+ - The field name that should be serialized.
957
+ # * +class_name+ - Optional, class name that the object type should be equal to.
890
958
  #
891
959
  # ==== Example
892
960
  # # Serialize a preferences attribute
@@ -904,12 +972,14 @@ module ActiveRecord #:nodoc:
904
972
 
905
973
 
906
974
  # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
907
- # directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
975
+ # directly from ActiveRecord::Base. So if the hierarchy looks like: Reply < Message < ActiveRecord::Base, then Message is used
908
976
  # to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class
909
977
  # in Active Support, which knows almost all common English inflections. You can add new inflections in config/initializers/inflections.rb.
910
978
  #
911
979
  # Nested classes are given table names prefixed by the singular form of
912
- # the parent's table name. Enclosing modules are not considered. Examples:
980
+ # the parent's table name. Enclosing modules are not considered.
981
+ #
982
+ # ==== Examples
913
983
  #
914
984
  # class Invoice < ActiveRecord::Base; end;
915
985
  # file class table_name
@@ -923,8 +993,8 @@ module ActiveRecord #:nodoc:
923
993
  # file class table_name
924
994
  # invoice/lineitem.rb Invoice::Lineitem lineitems
925
995
  #
926
- # Additionally, the class-level table_name_prefix is prepended and the
927
- # table_name_suffix is appended. So if you have "myapp_" as a prefix,
996
+ # Additionally, the class-level +table_name_prefix+ is prepended and the
997
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
928
998
  # the table name guess for an Invoice class becomes "myapp_invoices".
929
999
  # Invoice::Lineitem becomes "myapp_invoice_lineitems".
930
1000
  #
@@ -975,9 +1045,9 @@ module ActiveRecord #:nodoc:
975
1045
  key = 'id'
976
1046
  case primary_key_prefix_type
977
1047
  when :table_name
978
- key = Inflector.foreign_key(base_name, false)
1048
+ key = base_name.to_s.foreign_key(false)
979
1049
  when :table_name_with_underscore
980
- key = Inflector.foreign_key(base_name)
1050
+ key = base_name.to_s.foreign_key
981
1051
  end
982
1052
  key
983
1053
  end
@@ -1003,8 +1073,6 @@ module ActiveRecord #:nodoc:
1003
1073
  # Sets the table name to use to the given value, or (if the value
1004
1074
  # is nil or false) to the value returned by the given block.
1005
1075
  #
1006
- # Example:
1007
- #
1008
1076
  # class Project < ActiveRecord::Base
1009
1077
  # set_table_name "project"
1010
1078
  # end
@@ -1017,8 +1085,6 @@ module ActiveRecord #:nodoc:
1017
1085
  # or (if the value is nil or false) to the value returned by the given
1018
1086
  # block.
1019
1087
  #
1020
- # Example:
1021
- #
1022
1088
  # class Project < ActiveRecord::Base
1023
1089
  # set_primary_key "sysid"
1024
1090
  # end
@@ -1031,8 +1097,6 @@ module ActiveRecord #:nodoc:
1031
1097
  # or (if the value # is nil or false) to the value returned by the
1032
1098
  # given block.
1033
1099
  #
1034
- # Example:
1035
- #
1036
1100
  # class Project < ActiveRecord::Base
1037
1101
  # set_inheritance_column do
1038
1102
  # original_inheritance_column + "_id"
@@ -1054,8 +1118,6 @@ module ActiveRecord #:nodoc:
1054
1118
  # If a sequence name is not explicitly set when using PostgreSQL, it
1055
1119
  # will discover the sequence corresponding to your primary key for you.
1056
1120
  #
1057
- # Example:
1058
- #
1059
1121
  # class Project < ActiveRecord::Base
1060
1122
  # set_sequence_name "projectseq" # default would have been "project_seq"
1061
1123
  # end
@@ -1074,18 +1136,7 @@ module ActiveRecord #:nodoc:
1074
1136
 
1075
1137
  # Indicates whether the table associated with this class exists
1076
1138
  def table_exists?
1077
- if connection.respond_to?(:tables)
1078
- connection.tables.include? table_name
1079
- else
1080
- # if the connection adapter hasn't implemented tables, there are two crude tests that can be
1081
- # used - see if getting column info raises an error, or if the number of columns returned is zero
1082
- begin
1083
- reset_column_information
1084
- columns.size > 0
1085
- rescue ActiveRecord::StatementInvalid
1086
- false
1087
- end
1088
- end
1139
+ connection.table_exists?(table_name)
1089
1140
  end
1090
1141
 
1091
1142
  # Returns an array of column objects for the table associated with this class.
@@ -1226,7 +1277,7 @@ module ActiveRecord #:nodoc:
1226
1277
  class_of_active_record_descendant(self)
1227
1278
  end
1228
1279
 
1229
- # Set this to true if this is an abstract class (see #abstract_class?).
1280
+ # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
1230
1281
  attr_accessor :abstract_class
1231
1282
 
1232
1283
  # Returns whether this class is a base AR class. If A is a base class and
@@ -1235,16 +1286,63 @@ module ActiveRecord #:nodoc:
1235
1286
  defined?(@abstract_class) && @abstract_class == true
1236
1287
  end
1237
1288
 
1289
+ def respond_to?(method_id, include_private = false)
1290
+ if match = matches_dynamic_finder?(method_id) || matches_dynamic_finder_with_initialize_or_create?(method_id)
1291
+ return true if all_attributes_exists?(extract_attribute_names_from_match(match))
1292
+ end
1293
+ super
1294
+ end
1295
+
1296
+ def sti_name
1297
+ store_full_sti_class ? name : name.demodulize
1298
+ end
1299
+
1238
1300
  private
1239
1301
  def find_initial(options)
1240
- options.update(:limit => 1) unless options[:include]
1302
+ options.update(:limit => 1)
1241
1303
  find_every(options).first
1242
1304
  end
1243
1305
 
1306
+ def find_last(options)
1307
+ order = options[:order]
1308
+
1309
+ if order
1310
+ order = reverse_sql_order(order)
1311
+ elsif !scoped?(:find, :order)
1312
+ order = "#{table_name}.#{primary_key} DESC"
1313
+ end
1314
+
1315
+ if scoped?(:find, :order)
1316
+ scoped_order = reverse_sql_order(scope(:find, :order))
1317
+ scoped_methods.select { |s| s[:find].update(:order => scoped_order) }
1318
+ end
1319
+
1320
+ find_initial(options.merge({ :order => order }))
1321
+ end
1322
+
1323
+ def reverse_sql_order(order_query)
1324
+ reversed_query = order_query.split(/,/).each { |s|
1325
+ if s.match(/\s(asc|ASC)$/)
1326
+ s.gsub!(/\s(asc|ASC)$/, ' DESC')
1327
+ elsif s.match(/\s(desc|DESC)$/)
1328
+ s.gsub!(/\s(desc|DESC)$/, ' ASC')
1329
+ elsif !s.match(/\s(asc|ASC|desc|DESC)$/)
1330
+ s.concat(' DESC')
1331
+ end
1332
+ }.join(',')
1333
+ end
1334
+
1244
1335
  def find_every(options)
1245
- records = scoped?(:find, :include) || options[:include] ?
1246
- find_with_associations(options) :
1247
- find_by_sql(construct_finder_sql(options))
1336
+ include_associations = merge_includes(scope(:find, :include), options[:include])
1337
+
1338
+ if include_associations.any? && references_eager_loaded_tables?(options)
1339
+ records = find_with_associations(options)
1340
+ else
1341
+ records = find_by_sql(construct_finder_sql(options))
1342
+ if include_associations.any?
1343
+ preload_associations(records, include_associations)
1344
+ end
1345
+ end
1248
1346
 
1249
1347
  records.each { |record| record.readonly! } if options[:readonly]
1250
1348
 
@@ -1358,12 +1456,16 @@ module ActiveRecord #:nodoc:
1358
1456
  # Nest the type name in the same module as this class.
1359
1457
  # Bar is "MyApp::Business::Bar" relative to MyApp::Business::Foo
1360
1458
  def type_name_with_module(type_name)
1361
- (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
1459
+ if store_full_sti_class
1460
+ type_name
1461
+ else
1462
+ (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
1463
+ end
1362
1464
  end
1363
1465
 
1364
1466
  def construct_finder_sql(options)
1365
1467
  scope = scope(:find)
1366
- sql = "SELECT #{(scope && scope[:select]) || options[:select] || (options[:joins] && quoted_table_name + '.*') || '*'} "
1468
+ sql = "SELECT #{options[:select] || (scope && scope[:select]) || (options[:joins] && quoted_table_name + '.*') || '*'} "
1367
1469
  sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
1368
1470
 
1369
1471
  add_joins!(sql, options, scope)
@@ -1382,6 +1484,20 @@ module ActiveRecord #:nodoc:
1382
1484
  (safe_to_array(first) + safe_to_array(second)).uniq
1383
1485
  end
1384
1486
 
1487
+ # Merges conditions so that the result is a valid +condition+
1488
+ def merge_conditions(*conditions)
1489
+ segments = []
1490
+
1491
+ conditions.each do |condition|
1492
+ unless condition.blank?
1493
+ sql = sanitize_sql(condition)
1494
+ segments << sql unless sql.blank?
1495
+ end
1496
+ end
1497
+
1498
+ "(#{segments.join(') AND (')})" unless segments.empty?
1499
+ end
1500
+
1385
1501
  # Object#to_a is deprecated, though it does have the desired behavior
1386
1502
  def safe_to_array(o)
1387
1503
  case o
@@ -1416,7 +1532,7 @@ module ActiveRecord #:nodoc:
1416
1532
  end
1417
1533
  end
1418
1534
 
1419
- # The optional scope argument is for the current :find scope.
1535
+ # The optional scope argument is for the current <tt>:find</tt> scope.
1420
1536
  def add_limit!(sql, options, scope = :auto)
1421
1537
  scope = scope(:find) if :auto == scope
1422
1538
 
@@ -1428,43 +1544,43 @@ module ActiveRecord #:nodoc:
1428
1544
  connection.add_limit_offset!(sql, options)
1429
1545
  end
1430
1546
 
1431
- # The optional scope argument is for the current :find scope.
1432
- # The :lock option has precedence over a scoped :lock.
1547
+ # The optional scope argument is for the current <tt>:find</tt> scope.
1548
+ # The <tt>:lock</tt> option has precedence over a scoped <tt>:lock</tt>.
1433
1549
  def add_lock!(sql, options, scope = :auto)
1434
1550
  scope = scope(:find) if :auto == scope
1435
1551
  options = options.reverse_merge(:lock => scope[:lock]) if scope
1436
1552
  connection.add_lock!(sql, options)
1437
1553
  end
1438
1554
 
1439
- # The optional scope argument is for the current :find scope.
1555
+ # The optional scope argument is for the current <tt>:find</tt> scope.
1440
1556
  def add_joins!(sql, options, scope = :auto)
1441
1557
  scope = scope(:find) if :auto == scope
1442
- join = (scope && scope[:joins]) || options[:joins]
1443
- case join
1444
- when Symbol, Hash, Array
1445
- join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
1446
- sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
1447
- else
1448
- sql << " #{join} "
1558
+ [(scope && scope[:joins]), options[:joins]].each do |join|
1559
+ case join
1560
+ when Symbol, Hash, Array
1561
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
1562
+ sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
1563
+ else
1564
+ sql << " #{join} "
1565
+ end
1449
1566
  end
1450
1567
  end
1451
1568
 
1452
1569
  # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
1453
- # The optional scope argument is for the current :find scope.
1570
+ # The optional scope argument is for the current <tt>:find</tt> scope.
1454
1571
  def add_conditions!(sql, conditions, scope = :auto)
1455
1572
  scope = scope(:find) if :auto == scope
1456
- segments = []
1457
- segments << sanitize_sql(scope[:conditions]) if scope && !scope[:conditions].blank?
1458
- segments << sanitize_sql(conditions) unless conditions.blank?
1459
- segments << type_condition if finder_needs_type_condition?
1460
- segments.delete_if{|s| s.blank?}
1461
- sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
1573
+ conditions = [conditions]
1574
+ conditions << scope[:conditions] if scope
1575
+ conditions << type_condition if finder_needs_type_condition?
1576
+ merged_conditions = merge_conditions(*conditions)
1577
+ sql << "WHERE #{merged_conditions} " unless merged_conditions.blank?
1462
1578
  end
1463
1579
 
1464
1580
  def type_condition
1465
1581
  quoted_inheritance_column = connection.quote_column_name(inheritance_column)
1466
- type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{name.demodulize}' ") do |condition, subclass|
1467
- condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.name.demodulize}' "
1582
+ type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
1583
+ condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
1468
1584
  end
1469
1585
 
1470
1586
  " (#{type_condition}) "
@@ -1472,8 +1588,8 @@ module ActiveRecord #:nodoc:
1472
1588
 
1473
1589
  # Guesses the table name, but does not decorate it with prefix and suffix information.
1474
1590
  def undecorated_table_name(class_name = base_class.name)
1475
- table_name = Inflector.underscore(Inflector.demodulize(class_name))
1476
- table_name = Inflector.pluralize(table_name) if pluralize_table_names
1591
+ table_name = class_name.to_s.demodulize.underscore
1592
+ table_name = table_name.pluralize if pluralize_table_names
1477
1593
  table_name
1478
1594
  end
1479
1595
 
@@ -1490,7 +1606,7 @@ module ActiveRecord #:nodoc:
1490
1606
  # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
1491
1607
  # attempts to use it do not run through method_missing.
1492
1608
  def method_missing(method_id, *arguments)
1493
- if match = /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
1609
+ if match = matches_dynamic_finder?(method_id)
1494
1610
  finder = determine_finder(match)
1495
1611
 
1496
1612
  attribute_names = extract_attribute_names_from_match(match)
@@ -1514,14 +1630,17 @@ module ActiveRecord #:nodoc:
1514
1630
  end
1515
1631
  }, __FILE__, __LINE__
1516
1632
  send(method_id, *arguments)
1517
- elsif match = /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
1633
+ elsif match = matches_dynamic_finder_with_initialize_or_create?(method_id)
1518
1634
  instantiator = determine_instantiator(match)
1519
1635
  attribute_names = extract_attribute_names_from_match(match)
1520
1636
  super unless all_attributes_exists?(attribute_names)
1521
1637
 
1522
1638
  self.class_eval %{
1523
1639
  def self.#{method_id}(*args)
1640
+ guard_protected_attributes = false
1641
+
1524
1642
  if args[0].is_a?(Hash)
1643
+ guard_protected_attributes = true
1525
1644
  attributes = args[0].with_indifferent_access
1526
1645
  find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
1527
1646
  else
@@ -1532,8 +1651,10 @@ module ActiveRecord #:nodoc:
1532
1651
  set_readonly_option!(options)
1533
1652
 
1534
1653
  record = find_initial(options)
1535
- if record.nil?
1536
- record = self.new { |r| r.send(:attributes=, attributes, false) }
1654
+
1655
+ if record.nil?
1656
+ record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
1657
+ #{'yield(record) if block_given?'}
1537
1658
  #{'record.save' if instantiator == :create}
1538
1659
  record
1539
1660
  else
@@ -1547,6 +1668,14 @@ module ActiveRecord #:nodoc:
1547
1668
  end
1548
1669
  end
1549
1670
 
1671
+ def matches_dynamic_finder?(method_id)
1672
+ /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
1673
+ end
1674
+
1675
+ def matches_dynamic_finder_with_initialize_or_create?(method_id)
1676
+ /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
1677
+ end
1678
+
1550
1679
  def determine_finder(match)
1551
1680
  match.captures.first == 'all_by' ? :find_every : :find_initial
1552
1681
  end
@@ -1565,7 +1694,23 @@ module ActiveRecord #:nodoc:
1565
1694
  attributes
1566
1695
  end
1567
1696
 
1697
+ # Similar in purpose to +expand_hash_conditions_for_aggregates+.
1698
+ def expand_attribute_names_for_aggregates(attribute_names)
1699
+ expanded_attribute_names = []
1700
+ attribute_names.each do |attribute_name|
1701
+ unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
1702
+ aggregate_mapping(aggregation).each do |field_attr, aggregate_attr|
1703
+ expanded_attribute_names << field_attr
1704
+ end
1705
+ else
1706
+ expanded_attribute_names << attribute_name
1707
+ end
1708
+ end
1709
+ expanded_attribute_names
1710
+ end
1711
+
1568
1712
  def all_attributes_exists?(attribute_names)
1713
+ attribute_names = expand_attribute_names_for_aggregates(attribute_names)
1569
1714
  attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
1570
1715
  end
1571
1716
 
@@ -1587,8 +1732,8 @@ module ActiveRecord #:nodoc:
1587
1732
  end
1588
1733
 
1589
1734
 
1590
- # Defines an "attribute" method (like #inheritance_column or
1591
- # #table_name). A new (class) method will be created with the
1735
+ # Defines an "attribute" method (like +inheritance_column+ or
1736
+ # +table_name+). A new (class) method will be created with the
1592
1737
  # given name. If a value is specified, the new method will
1593
1738
  # return that value (as a string). Otherwise, the given block
1594
1739
  # will be used to compute the value of the method.
@@ -1619,8 +1764,8 @@ module ActiveRecord #:nodoc:
1619
1764
 
1620
1765
  protected
1621
1766
  # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
1622
- # method_name may be :find or :create. :find parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
1623
- # <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options. :create parameters are an attributes hash.
1767
+ # method_name may be <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
1768
+ # <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options. <tt>:create</tt> parameters are an attributes hash.
1624
1769
  #
1625
1770
  # class Article < ActiveRecord::Base
1626
1771
  # def self.create_with_scope
@@ -1633,12 +1778,12 @@ module ActiveRecord #:nodoc:
1633
1778
  # end
1634
1779
  #
1635
1780
  # In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
1636
- # :conditions and :include options in :find, which are merged.
1781
+ # <tt>:conditions</tt> and <tt>:include</tt> options in <tt>:find</tt>, which are merged.
1637
1782
  #
1638
1783
  # class Article < ActiveRecord::Base
1639
1784
  # def self.find_with_scope
1640
1785
  # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
1641
- # with_scope(:find => { :limit => 10})
1786
+ # with_scope(:find => { :limit => 10 })
1642
1787
  # find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
1643
1788
  # end
1644
1789
  # with_scope(:find => { :conditions => "author_id = 3" })
@@ -1684,7 +1829,7 @@ module ActiveRecord #:nodoc:
1684
1829
  (hash[method].keys + params.keys).uniq.each do |key|
1685
1830
  merge = hash[method][key] && params[key] # merge if both scopes have the same key
1686
1831
  if key == :conditions && merge
1687
- hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ")
1832
+ hash[method][key] = merge_conditions(params[key], hash[method][key])
1688
1833
  elsif key == :include && merge
1689
1834
  hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
1690
1835
  else
@@ -1765,7 +1910,7 @@ module ActiveRecord #:nodoc:
1765
1910
  end
1766
1911
  end
1767
1912
 
1768
- # Returns the class descending directly from ActiveRecord in the inheritance hierarchy.
1913
+ # Returns the class descending directly from Active Record in the inheritance hierarchy.
1769
1914
  def class_of_active_record_descendant(klass)
1770
1915
  if klass.superclass == Base || klass.superclass.abstract_class?
1771
1916
  klass
@@ -1776,17 +1921,19 @@ module ActiveRecord #:nodoc:
1776
1921
  end
1777
1922
  end
1778
1923
 
1779
- # Returns the name of the class descending directly from ActiveRecord in the inheritance hierarchy.
1924
+ # Returns the name of the class descending directly from Active Record in the inheritance hierarchy.
1780
1925
  def class_name_of_active_record_descendant(klass) #:nodoc:
1781
1926
  klass.base_class.name
1782
1927
  end
1783
1928
 
1784
- # Accepts an array, hash, or string of sql conditions and sanitizes
1929
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
1785
1930
  # them into a valid SQL fragment for a WHERE clause.
1786
1931
  # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
1787
1932
  # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
1788
1933
  # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
1789
1934
  def sanitize_sql_for_conditions(condition)
1935
+ return nil if condition.blank?
1936
+
1790
1937
  case condition
1791
1938
  when Array; sanitize_sql_array(condition)
1792
1939
  when Hash; sanitize_sql_hash_for_conditions(condition)
@@ -1795,7 +1942,7 @@ module ActiveRecord #:nodoc:
1795
1942
  end
1796
1943
  alias_method :sanitize_sql, :sanitize_sql_for_conditions
1797
1944
 
1798
- # Accepts an array, hash, or string of sql conditions and sanitizes
1945
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
1799
1946
  # them into a valid SQL fragment for a SET clause.
1800
1947
  # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
1801
1948
  def sanitize_sql_for_assignment(assignments)
@@ -1806,6 +1953,41 @@ module ActiveRecord #:nodoc:
1806
1953
  end
1807
1954
  end
1808
1955
 
1956
+ def aggregate_mapping(reflection)
1957
+ mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
1958
+ mapping.first.is_a?(Array) ? mapping : [mapping]
1959
+ end
1960
+
1961
+ # Accepts a hash of SQL conditions and replaces those attributes
1962
+ # that correspond to a +composed_of+ relationship with their expanded
1963
+ # aggregate attribute values.
1964
+ # Given:
1965
+ # class Person < ActiveRecord::Base
1966
+ # composed_of :address, :class_name => "Address",
1967
+ # :mapping => [%w(address_street street), %w(address_city city)]
1968
+ # end
1969
+ # Then:
1970
+ # { :address => Address.new("813 abc st.", "chicago") }
1971
+ # # => { :address_street => "813 abc st.", :address_city => "chicago" }
1972
+ def expand_hash_conditions_for_aggregates(attrs)
1973
+ expanded_attrs = {}
1974
+ attrs.each do |attr, value|
1975
+ unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
1976
+ mapping = aggregate_mapping(aggregation)
1977
+ mapping.each do |field_attr, aggregate_attr|
1978
+ if mapping.size == 1 && !value.respond_to?(aggregate_attr)
1979
+ expanded_attrs[field_attr] = value
1980
+ else
1981
+ expanded_attrs[field_attr] = value.send(aggregate_attr)
1982
+ end
1983
+ end
1984
+ else
1985
+ expanded_attrs[attr] = value
1986
+ end
1987
+ end
1988
+ expanded_attrs
1989
+ end
1990
+
1809
1991
  # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
1810
1992
  # { :name => "foo'bar", :group_id => 4 }
1811
1993
  # # => "name='foo''bar' and group_id= 4"
@@ -1815,7 +1997,12 @@ module ActiveRecord #:nodoc:
1815
1997
  # # => "age BETWEEN 13 AND 18"
1816
1998
  # { 'other_records.id' => 7 }
1817
1999
  # # => "`other_records`.`id` = 7"
2000
+ # And for value objects on a composed_of relationship:
2001
+ # { :address => Address.new("123 abc st.", "chicago") }
2002
+ # # => "address_street='123 abc st.' and address_city='chicago'"
1818
2003
  def sanitize_sql_hash_for_conditions(attrs)
2004
+ attrs = expand_hash_conditions_for_aggregates(attrs)
2005
+
1819
2006
  conditions = attrs.map do |attr, value|
1820
2007
  attr = attr.to_s
1821
2008
 
@@ -1838,13 +2025,13 @@ module ActiveRecord #:nodoc:
1838
2025
  # { :status => nil, :group_id => 1 }
1839
2026
  # # => "status = NULL , group_id = 1"
1840
2027
  def sanitize_sql_hash_for_assignment(attrs)
1841
- conditions = attrs.map do |attr, value|
2028
+ attrs.map do |attr, value|
1842
2029
  "#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
1843
2030
  end.join(', ')
1844
2031
  end
1845
2032
 
1846
2033
  # Accepts an array of conditions. The array has each value
1847
- # sanitized and interpolated into the sql statement.
2034
+ # sanitized and interpolated into the SQL statement.
1848
2035
  # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
1849
2036
  def sanitize_sql_array(ary)
1850
2037
  statement, *values = ary
@@ -1866,7 +2053,7 @@ module ActiveRecord #:nodoc:
1866
2053
  end
1867
2054
 
1868
2055
  def replace_named_bind_variables(statement, bind_vars) #:nodoc:
1869
- statement.gsub(/:(\w+)/) do
2056
+ statement.gsub(/:([a-zA-Z]\w*)/) do
1870
2057
  match = $1.to_sym
1871
2058
  if bind_vars.include?(match)
1872
2059
  quote_bound_value(bind_vars[match])
@@ -1965,6 +2152,24 @@ module ActiveRecord #:nodoc:
1965
2152
  (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
1966
2153
  end
1967
2154
 
2155
+ # Returns a cache key that can be used to identify this record.
2156
+ #
2157
+ # ==== Examples
2158
+ #
2159
+ # Product.new.cache_key # => "products/new"
2160
+ # Product.find(5).cache_key # => "products/5" (updated_at not available)
2161
+ # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
2162
+ def cache_key
2163
+ case
2164
+ when new_record?
2165
+ "#{self.class.name.tableize}/new"
2166
+ when self[:updated_at]
2167
+ "#{self.class.name.tableize}/#{id}-#{updated_at.to_s(:number)}"
2168
+ else
2169
+ "#{self.class.name.tableize}/#{id}"
2170
+ end
2171
+ end
2172
+
1968
2173
  def id_before_type_cast #:nodoc:
1969
2174
  read_attribute_before_type_cast(self.class.primary_key)
1970
2175
  end
@@ -1985,6 +2190,12 @@ module ActiveRecord #:nodoc:
1985
2190
 
1986
2191
  # * No record exists: Creates a new record with values matching those of the object attributes.
1987
2192
  # * A record does exist: Updates the record with values matching those of the object attributes.
2193
+ #
2194
+ # Note: If your model specifies any validations then the method declaration dynamically
2195
+ # changes to:
2196
+ # save(perform_validation=true)
2197
+ # Calling save(false) saves the model without running validations.
2198
+ # See ActiveRecord::Validations for more information.
1988
2199
  def save
1989
2200
  create_or_update
1990
2201
  end
@@ -2014,16 +2225,16 @@ module ActiveRecord #:nodoc:
2014
2225
  # The extent of a "deep" clone is application-specific and is therefore
2015
2226
  # left to the application to implement according to its need.
2016
2227
  def clone
2017
- attrs = self.attributes_before_type_cast
2228
+ attrs = clone_attributes(:read_attribute_before_type_cast)
2018
2229
  attrs.delete(self.class.primary_key)
2019
2230
  record = self.class.new
2020
2231
  record.send :instance_variable_set, '@attributes', attrs
2021
2232
  record
2022
2233
  end
2023
2234
 
2024
- # Returns an instance of the specified klass with the attributes of the current record. This is mostly useful in relation to
2235
+ # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
2025
2236
  # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
2026
- # identification in Action Pack to allow, say, Client < Company to do something like render :partial => @client.becomes(Company)
2237
+ # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
2027
2238
  # to render that instance using the companies/company partial instead of clients/client.
2028
2239
  #
2029
2240
  # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
@@ -2057,37 +2268,53 @@ module ActiveRecord #:nodoc:
2057
2268
  save!
2058
2269
  end
2059
2270
 
2060
- # Initializes the +attribute+ to zero if nil and adds the value passed as +by+ (default is one). Only makes sense for number-based attributes. Returns self.
2271
+ # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
2272
+ # The increment is performed directly on the underlying attribute, no setter is invoked.
2273
+ # Only makes sense for number-based attributes. Returns +self+.
2061
2274
  def increment(attribute, by = 1)
2062
2275
  self[attribute] ||= 0
2063
2276
  self[attribute] += by
2064
2277
  self
2065
2278
  end
2066
2279
 
2067
- # Increments the +attribute+ and saves the record.
2280
+ # Wrapper around +increment+ that saves the record. This method differs from
2281
+ # its non-bang version in that it passes through the attribute setter.
2282
+ # Saving is not subjected to validation checks. Returns +true+ if the
2283
+ # record could be saved.
2068
2284
  def increment!(attribute, by = 1)
2069
2285
  increment(attribute, by).update_attribute(attribute, self[attribute])
2070
2286
  end
2071
2287
 
2072
- # Initializes the +attribute+ to zero if nil and subtracts the value passed as +by+ (default is one). Only makes sense for number-based attributes. Returns self.
2288
+ # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
2289
+ # The decrement is performed directly on the underlying attribute, no setter is invoked.
2290
+ # Only makes sense for number-based attributes. Returns +self+.
2073
2291
  def decrement(attribute, by = 1)
2074
2292
  self[attribute] ||= 0
2075
2293
  self[attribute] -= by
2076
2294
  self
2077
2295
  end
2078
2296
 
2079
- # Decrements the +attribute+ and saves the record.
2297
+ # Wrapper around +decrement+ that saves the record. This method differs from
2298
+ # its non-bang version in that it passes through the attribute setter.
2299
+ # Saving is not subjected to validation checks. Returns +true+ if the
2300
+ # record could be saved.
2080
2301
  def decrement!(attribute, by = 1)
2081
2302
  decrement(attribute, by).update_attribute(attribute, self[attribute])
2082
2303
  end
2083
2304
 
2084
- # Turns an +attribute+ that's currently true into false and vice versa. Returns self.
2305
+ # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
2306
+ # if the predicate returns +true+ the attribute will become +false+. This
2307
+ # method toggles directly the underlying value without calling any setter.
2308
+ # Returns +self+.
2085
2309
  def toggle(attribute)
2086
2310
  self[attribute] = !send("#{attribute}?")
2087
2311
  self
2088
2312
  end
2089
2313
 
2090
- # Toggles the +attribute+ and saves the record.
2314
+ # Wrapper around +toggle+ that saves the record. This method differs from
2315
+ # its non-bang version in that it passes through the attribute setter.
2316
+ # Saving is not subjected to validation checks. Returns +true+ if the
2317
+ # record could be saved.
2091
2318
  def toggle!(attribute)
2092
2319
  toggle(attribute).update_attribute(attribute, self[attribute])
2093
2320
  end
@@ -2138,31 +2365,20 @@ module ActiveRecord #:nodoc:
2138
2365
  end
2139
2366
 
2140
2367
 
2141
- # Returns a hash of all the attributes with their names as keys and clones of their objects as values.
2142
- def attributes(options = nil)
2143
- attributes = clone_attributes :read_attribute
2144
-
2145
- if options.nil?
2146
- attributes
2147
- else
2148
- ActiveSupport::Deprecation.warn "Passing options to Base#attributes is deprecated and will be removed in Rails 2.1. Please use Hash#slice or Hash#except instead"
2149
- if except = options[:except]
2150
- except = Array(except).collect { |attribute| attribute.to_s }
2151
- except.each { |attribute_name| attributes.delete(attribute_name) }
2152
- attributes
2153
- elsif only = options[:only]
2154
- only = Array(only).collect { |attribute| attribute.to_s }
2155
- attributes.delete_if { |key, value| !only.include?(key) }
2156
- attributes
2157
- else
2158
- raise ArgumentError, "Options does not specify :except or :only (#{options.keys.inspect})"
2159
- end
2368
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
2369
+ def attributes
2370
+ self.attribute_names.inject({}) do |attrs, name|
2371
+ attrs[name] = read_attribute(name)
2372
+ attrs
2160
2373
  end
2161
2374
  end
2162
2375
 
2163
- # Returns a hash of cloned attributes before typecasting and deserialization.
2376
+ # Returns a hash of attributes before typecasting and deserialization.
2164
2377
  def attributes_before_type_cast
2165
- clone_attributes :read_attribute_before_type_cast
2378
+ self.attribute_names.inject({}) do |attrs, name|
2379
+ attrs[name] = read_attribute_before_type_cast(name)
2380
+ attrs
2381
+ end
2166
2382
  end
2167
2383
 
2168
2384
  # Format attributes nicely for inspect.
@@ -2259,8 +2475,8 @@ module ActiveRecord #:nodoc:
2259
2475
 
2260
2476
  # Updates the associated record with values matching those of the instance attributes.
2261
2477
  # Returns the number of affected rows.
2262
- def update
2263
- quoted_attributes = attributes_with_quotes(false, false)
2478
+ def update(attribute_names = @attributes.keys)
2479
+ quoted_attributes = attributes_with_quotes(false, false, attribute_names)
2264
2480
  return 0 if quoted_attributes.empty?
2265
2481
  connection.update(
2266
2482
  "UPDATE #{self.class.quoted_table_name} " +
@@ -2294,13 +2510,13 @@ module ActiveRecord #:nodoc:
2294
2510
  id
2295
2511
  end
2296
2512
 
2297
- # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendent.
2298
- # Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
2299
- # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
2513
+ # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendent.
2514
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
2515
+ # set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. No such attribute would be set for objects of the
2300
2516
  # Message class in that example.
2301
2517
  def ensure_proper_type
2302
2518
  unless self.class.descends_from_active_record?
2303
- write_attribute(self.class.inheritance_column, Inflector.demodulize(self.class.name))
2519
+ write_attribute(self.class.inheritance_column, self.class.sti_name)
2304
2520
  end
2305
2521
  end
2306
2522
 
@@ -2352,12 +2568,13 @@ module ActiveRecord #:nodoc:
2352
2568
 
2353
2569
  # Returns a copy of the attributes hash where all the values have been safely quoted for use in
2354
2570
  # an SQL statement.
2355
- def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true)
2356
- quoted = attributes.inject({}) do |result, (name, value)|
2571
+ def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
2572
+ quoted = {}
2573
+ connection = self.class.connection
2574
+ attribute_names.each do |name|
2357
2575
  if column = column_for_attribute(name)
2358
- result[name] = quote_value(value, column) unless !include_primary_key && column.primary
2576
+ quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && column.primary
2359
2577
  end
2360
- result
2361
2578
  end
2362
2579
  include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
2363
2580
  end
@@ -2367,7 +2584,7 @@ module ActiveRecord #:nodoc:
2367
2584
  self.class.connection.quote(value, column)
2368
2585
  end
2369
2586
 
2370
- # Interpolate custom sql string in instance context.
2587
+ # Interpolate custom SQL string in instance context.
2371
2588
  # Optional record argument is meant for custom insert_sql.
2372
2589
  def interpolate_sql(sql, record = nil)
2373
2590
  instance_eval("%@#{sql.gsub('@', '\@')}@")
@@ -2396,9 +2613,12 @@ module ActiveRecord #:nodoc:
2396
2613
  )
2397
2614
  end
2398
2615
 
2399
- # Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself.
2400
- def instantiate_time_object(*values)
2401
- @@default_timezone == :utc ? Time.utc(*values) : Time.local(*values)
2616
+ def instantiate_time_object(name, values)
2617
+ if self.class.time_zone_aware_attributes && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym)
2618
+ Time.zone.local(*values)
2619
+ else
2620
+ Time.time_with_datetime_fallback(@@default_timezone, *values)
2621
+ end
2402
2622
  end
2403
2623
 
2404
2624
  def execute_callstack_for_multiparameter_attributes(callstack)
@@ -2410,12 +2630,12 @@ module ActiveRecord #:nodoc:
2410
2630
  else
2411
2631
  begin
2412
2632
  value = if Time == klass
2413
- instantiate_time_object(*values)
2633
+ instantiate_time_object(name, values)
2414
2634
  elsif Date == klass
2415
2635
  begin
2416
2636
  Date.new(*values)
2417
2637
  rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
2418
- instantiate_time_object(*values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
2638
+ instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
2419
2639
  end
2420
2640
  else
2421
2641
  klass.new(*values)
@@ -2463,8 +2683,9 @@ module ActiveRecord #:nodoc:
2463
2683
  end
2464
2684
 
2465
2685
  def quoted_column_names(attributes = attributes_with_quotes)
2686
+ connection = self.class.connection
2466
2687
  attributes.keys.collect do |column_name|
2467
- self.class.connection.quote_column_name(column_name)
2688
+ connection.quote_column_name(column_name)
2468
2689
  end
2469
2690
  end
2470
2691