activerecord 2.3.18 → 3.0.0.beta

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 (378) hide show
  1. data/CHANGELOG +105 -34
  2. data/examples/performance.rb +3 -39
  3. data/examples/simple.rb +14 -0
  4. data/lib/active_record.rb +81 -47
  5. data/lib/active_record/aggregations.rb +1 -3
  6. data/lib/active_record/association_preload.rb +39 -54
  7. data/lib/active_record/associations.rb +262 -419
  8. data/lib/active_record/associations/association_collection.rb +85 -100
  9. data/lib/active_record/associations/association_proxy.rb +20 -18
  10. data/lib/active_record/associations/belongs_to_association.rb +8 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -35
  12. data/lib/active_record/associations/has_many_association.rb +11 -19
  13. data/lib/active_record/associations/has_many_through_association.rb +30 -183
  14. data/lib/active_record/associations/has_one_association.rb +10 -10
  15. data/lib/active_record/associations/has_one_through_association.rb +13 -11
  16. data/lib/active_record/associations/through_association_scope.rb +153 -0
  17. data/lib/active_record/attribute_methods.rb +17 -370
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +87 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +44 -0
  21. data/lib/active_record/attribute_methods/query.rb +37 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +60 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/autosave_association.rb +20 -41
  26. data/lib/active_record/base.rb +357 -1180
  27. data/lib/active_record/batches.rb +10 -16
  28. data/lib/active_record/callbacks.rb +66 -126
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +17 -13
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +5 -25
  31. data/lib/active_record/connection_adapters/abstract/database_statements.rb +4 -43
  32. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -2
  33. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -4
  34. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  35. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +18 -72
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +16 -49
  37. data/lib/active_record/connection_adapters/mysql_adapter.rb +15 -27
  38. data/lib/active_record/connection_adapters/postgresql_adapter.rb +84 -46
  39. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +9 -3
  40. data/lib/active_record/connection_adapters/sqlite_adapter.rb +34 -65
  41. data/lib/active_record/fixtures.rb +21 -25
  42. data/lib/active_record/locale/en.yml +9 -27
  43. data/lib/active_record/locking/optimistic.rb +16 -48
  44. data/lib/active_record/migration.rb +59 -46
  45. data/lib/active_record/named_scope.rb +85 -92
  46. data/lib/active_record/nested_attributes.rb +18 -24
  47. data/lib/active_record/observer.rb +18 -94
  48. data/lib/active_record/railtie.rb +83 -0
  49. data/lib/active_record/railties/controller_runtime.rb +38 -0
  50. data/lib/active_record/railties/databases.rake +477 -0
  51. data/lib/active_record/railties/subscriber.rb +27 -0
  52. data/lib/active_record/reflection.rb +2 -15
  53. data/lib/active_record/relation.rb +339 -0
  54. data/lib/active_record/relation/calculations.rb +259 -0
  55. data/lib/active_record/relation/finder_methods.rb +315 -0
  56. data/lib/active_record/relation/predicate_builder.rb +46 -0
  57. data/lib/active_record/relation/query_methods.rb +218 -0
  58. data/lib/active_record/relation/spawn_methods.rb +102 -0
  59. data/lib/active_record/schema_dumper.rb +10 -6
  60. data/lib/active_record/serialization.rb +31 -74
  61. data/lib/active_record/serializers/xml_serializer.rb +33 -121
  62. data/lib/active_record/session_store.rb +1 -9
  63. data/lib/active_record/test_case.rb +1 -3
  64. data/lib/active_record/timestamp.rb +7 -5
  65. data/lib/active_record/transactions.rb +9 -9
  66. data/lib/active_record/validations.rb +51 -1100
  67. data/lib/active_record/validations/associated.rb +47 -0
  68. data/lib/active_record/validations/uniqueness.rb +181 -0
  69. data/lib/active_record/version.rb +3 -3
  70. data/lib/generators/active_record.rb +30 -0
  71. data/lib/generators/active_record/migration/migration_generator.rb +25 -0
  72. data/lib/generators/active_record/migration/templates/migration.rb +11 -0
  73. data/lib/generators/active_record/model/model_generator.rb +33 -0
  74. data/lib/generators/active_record/model/templates/migration.rb +16 -0
  75. data/lib/generators/active_record/model/templates/model.rb +5 -0
  76. data/lib/generators/active_record/observer/observer_generator.rb +15 -0
  77. data/lib/generators/active_record/observer/templates/observer.rb +2 -0
  78. data/lib/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  79. data/lib/generators/active_record/session_migration/templates/migration.rb +16 -0
  80. metadata +67 -325
  81. data/RUNNING_UNIT_TESTS +0 -36
  82. data/Rakefile +0 -268
  83. data/install.rb +0 -30
  84. data/lib/active_record/calculations.rb +0 -321
  85. data/lib/active_record/connection_adapters/abstract/database_limits.rb +0 -57
  86. data/lib/active_record/dirty.rb +0 -183
  87. data/lib/active_record/serializers/json_serializer.rb +0 -91
  88. data/lib/activerecord.rb +0 -2
  89. data/test/assets/example.log +0 -1
  90. data/test/assets/flowers.jpg +0 -0
  91. data/test/cases/aaa_create_tables_test.rb +0 -24
  92. data/test/cases/active_schema_test_mysql.rb +0 -122
  93. data/test/cases/active_schema_test_postgresql.rb +0 -24
  94. data/test/cases/adapter_test.rb +0 -144
  95. data/test/cases/aggregations_test.rb +0 -167
  96. data/test/cases/ar_schema_test.rb +0 -32
  97. data/test/cases/associations/belongs_to_associations_test.rb +0 -438
  98. data/test/cases/associations/callbacks_test.rb +0 -161
  99. data/test/cases/associations/cascaded_eager_loading_test.rb +0 -131
  100. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +0 -36
  101. data/test/cases/associations/eager_load_nested_include_test.rb +0 -131
  102. data/test/cases/associations/eager_load_nested_polymorphic_include.rb +0 -19
  103. data/test/cases/associations/eager_singularization_test.rb +0 -145
  104. data/test/cases/associations/eager_test.rb +0 -852
  105. data/test/cases/associations/extension_test.rb +0 -62
  106. data/test/cases/associations/habtm_join_table_test.rb +0 -56
  107. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +0 -827
  108. data/test/cases/associations/has_many_associations_test.rb +0 -1273
  109. data/test/cases/associations/has_many_through_associations_test.rb +0 -360
  110. data/test/cases/associations/has_one_associations_test.rb +0 -330
  111. data/test/cases/associations/has_one_through_associations_test.rb +0 -209
  112. data/test/cases/associations/inner_join_association_test.rb +0 -93
  113. data/test/cases/associations/inverse_associations_test.rb +0 -566
  114. data/test/cases/associations/join_model_test.rb +0 -712
  115. data/test/cases/associations_test.rb +0 -282
  116. data/test/cases/attribute_methods_test.rb +0 -305
  117. data/test/cases/autosave_association_test.rb +0 -1218
  118. data/test/cases/base_test.rb +0 -2166
  119. data/test/cases/batches_test.rb +0 -81
  120. data/test/cases/binary_test.rb +0 -30
  121. data/test/cases/calculations_test.rb +0 -360
  122. data/test/cases/callbacks_observers_test.rb +0 -38
  123. data/test/cases/callbacks_test.rb +0 -438
  124. data/test/cases/class_inheritable_attributes_test.rb +0 -32
  125. data/test/cases/column_alias_test.rb +0 -17
  126. data/test/cases/column_definition_test.rb +0 -70
  127. data/test/cases/connection_pool_test.rb +0 -25
  128. data/test/cases/connection_test_firebird.rb +0 -8
  129. data/test/cases/connection_test_mysql.rb +0 -65
  130. data/test/cases/copy_table_test_sqlite.rb +0 -80
  131. data/test/cases/counter_cache_test.rb +0 -84
  132. data/test/cases/database_statements_test.rb +0 -12
  133. data/test/cases/datatype_test_postgresql.rb +0 -204
  134. data/test/cases/date_time_test.rb +0 -37
  135. data/test/cases/default_test_firebird.rb +0 -16
  136. data/test/cases/defaults_test.rb +0 -111
  137. data/test/cases/deprecated_finder_test.rb +0 -30
  138. data/test/cases/dirty_test.rb +0 -316
  139. data/test/cases/finder_respond_to_test.rb +0 -76
  140. data/test/cases/finder_test.rb +0 -1098
  141. data/test/cases/fixtures_test.rb +0 -661
  142. data/test/cases/helper.rb +0 -68
  143. data/test/cases/i18n_test.rb +0 -46
  144. data/test/cases/inheritance_test.rb +0 -262
  145. data/test/cases/invalid_date_test.rb +0 -24
  146. data/test/cases/json_serialization_test.rb +0 -219
  147. data/test/cases/lifecycle_test.rb +0 -193
  148. data/test/cases/locking_test.rb +0 -350
  149. data/test/cases/method_scoping_test.rb +0 -704
  150. data/test/cases/migration_test.rb +0 -1649
  151. data/test/cases/migration_test_firebird.rb +0 -124
  152. data/test/cases/mixin_test.rb +0 -96
  153. data/test/cases/modules_test.rb +0 -109
  154. data/test/cases/multiple_db_test.rb +0 -85
  155. data/test/cases/named_scope_test.rb +0 -372
  156. data/test/cases/nested_attributes_test.rb +0 -840
  157. data/test/cases/pk_test.rb +0 -119
  158. data/test/cases/pooled_connections_test.rb +0 -103
  159. data/test/cases/query_cache_test.rb +0 -129
  160. data/test/cases/readonly_test.rb +0 -107
  161. data/test/cases/reflection_test.rb +0 -234
  162. data/test/cases/reload_models_test.rb +0 -22
  163. data/test/cases/repair_helper.rb +0 -50
  164. data/test/cases/reserved_word_test_mysql.rb +0 -176
  165. data/test/cases/sanitize_test.rb +0 -25
  166. data/test/cases/schema_authorization_test_postgresql.rb +0 -75
  167. data/test/cases/schema_dumper_test.rb +0 -211
  168. data/test/cases/schema_test_postgresql.rb +0 -178
  169. data/test/cases/serialization_test.rb +0 -47
  170. data/test/cases/sp_test_mysql.rb +0 -16
  171. data/test/cases/synonym_test_oracle.rb +0 -17
  172. data/test/cases/timestamp_test.rb +0 -75
  173. data/test/cases/transactions_test.rb +0 -543
  174. data/test/cases/unconnected_test.rb +0 -32
  175. data/test/cases/validations_i18n_test.rb +0 -925
  176. data/test/cases/validations_test.rb +0 -1684
  177. data/test/cases/xml_serialization_test.rb +0 -240
  178. data/test/cases/yaml_serialization_test.rb +0 -11
  179. data/test/config.rb +0 -5
  180. data/test/connections/jdbc_jdbcderby/connection.rb +0 -18
  181. data/test/connections/jdbc_jdbch2/connection.rb +0 -18
  182. data/test/connections/jdbc_jdbchsqldb/connection.rb +0 -18
  183. data/test/connections/jdbc_jdbcmysql/connection.rb +0 -26
  184. data/test/connections/jdbc_jdbcpostgresql/connection.rb +0 -26
  185. data/test/connections/jdbc_jdbcsqlite3/connection.rb +0 -25
  186. data/test/connections/native_db2/connection.rb +0 -25
  187. data/test/connections/native_firebird/connection.rb +0 -26
  188. data/test/connections/native_frontbase/connection.rb +0 -27
  189. data/test/connections/native_mysql/connection.rb +0 -25
  190. data/test/connections/native_openbase/connection.rb +0 -21
  191. data/test/connections/native_oracle/connection.rb +0 -27
  192. data/test/connections/native_postgresql/connection.rb +0 -21
  193. data/test/connections/native_sqlite/connection.rb +0 -25
  194. data/test/connections/native_sqlite3/connection.rb +0 -25
  195. data/test/connections/native_sqlite3/in_memory_connection.rb +0 -18
  196. data/test/connections/native_sybase/connection.rb +0 -23
  197. data/test/fixtures/accounts.yml +0 -29
  198. data/test/fixtures/all/developers.yml +0 -0
  199. data/test/fixtures/all/people.csv +0 -0
  200. data/test/fixtures/all/tasks.yml +0 -0
  201. data/test/fixtures/author_addresses.yml +0 -5
  202. data/test/fixtures/author_favorites.yml +0 -4
  203. data/test/fixtures/authors.yml +0 -9
  204. data/test/fixtures/binaries.yml +0 -132
  205. data/test/fixtures/books.yml +0 -7
  206. data/test/fixtures/categories.yml +0 -14
  207. data/test/fixtures/categories/special_categories.yml +0 -9
  208. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +0 -4
  209. data/test/fixtures/categories_ordered.yml +0 -7
  210. data/test/fixtures/categories_posts.yml +0 -23
  211. data/test/fixtures/categorizations.yml +0 -17
  212. data/test/fixtures/clubs.yml +0 -6
  213. data/test/fixtures/comments.yml +0 -59
  214. data/test/fixtures/companies.yml +0 -56
  215. data/test/fixtures/computers.yml +0 -4
  216. data/test/fixtures/courses.yml +0 -7
  217. data/test/fixtures/customers.yml +0 -26
  218. data/test/fixtures/developers.yml +0 -21
  219. data/test/fixtures/developers_projects.yml +0 -17
  220. data/test/fixtures/edges.yml +0 -6
  221. data/test/fixtures/entrants.yml +0 -14
  222. data/test/fixtures/faces.yml +0 -11
  223. data/test/fixtures/fk_test_has_fk.yml +0 -3
  224. data/test/fixtures/fk_test_has_pk.yml +0 -2
  225. data/test/fixtures/funny_jokes.yml +0 -10
  226. data/test/fixtures/interests.yml +0 -33
  227. data/test/fixtures/items.yml +0 -4
  228. data/test/fixtures/jobs.yml +0 -7
  229. data/test/fixtures/legacy_things.yml +0 -3
  230. data/test/fixtures/mateys.yml +0 -4
  231. data/test/fixtures/member_types.yml +0 -6
  232. data/test/fixtures/members.yml +0 -6
  233. data/test/fixtures/memberships.yml +0 -20
  234. data/test/fixtures/men.yml +0 -5
  235. data/test/fixtures/minimalistics.yml +0 -2
  236. data/test/fixtures/mixed_case_monkeys.yml +0 -6
  237. data/test/fixtures/mixins.yml +0 -29
  238. data/test/fixtures/movies.yml +0 -7
  239. data/test/fixtures/naked/csv/accounts.csv +0 -1
  240. data/test/fixtures/naked/yml/accounts.yml +0 -1
  241. data/test/fixtures/naked/yml/companies.yml +0 -1
  242. data/test/fixtures/naked/yml/courses.yml +0 -1
  243. data/test/fixtures/organizations.yml +0 -5
  244. data/test/fixtures/owners.yml +0 -7
  245. data/test/fixtures/parrots.yml +0 -27
  246. data/test/fixtures/parrots_pirates.yml +0 -7
  247. data/test/fixtures/people.yml +0 -15
  248. data/test/fixtures/pets.yml +0 -14
  249. data/test/fixtures/pirates.yml +0 -9
  250. data/test/fixtures/polymorphic_designs.yml +0 -19
  251. data/test/fixtures/polymorphic_prices.yml +0 -19
  252. data/test/fixtures/posts.yml +0 -52
  253. data/test/fixtures/price_estimates.yml +0 -7
  254. data/test/fixtures/projects.yml +0 -7
  255. data/test/fixtures/readers.yml +0 -9
  256. data/test/fixtures/references.yml +0 -17
  257. data/test/fixtures/reserved_words/distinct.yml +0 -5
  258. data/test/fixtures/reserved_words/distincts_selects.yml +0 -11
  259. data/test/fixtures/reserved_words/group.yml +0 -14
  260. data/test/fixtures/reserved_words/select.yml +0 -8
  261. data/test/fixtures/reserved_words/values.yml +0 -7
  262. data/test/fixtures/ships.yml +0 -5
  263. data/test/fixtures/sponsors.yml +0 -9
  264. data/test/fixtures/subscribers.yml +0 -7
  265. data/test/fixtures/subscriptions.yml +0 -12
  266. data/test/fixtures/taggings.yml +0 -28
  267. data/test/fixtures/tags.yml +0 -7
  268. data/test/fixtures/tasks.yml +0 -7
  269. data/test/fixtures/tees.yml +0 -4
  270. data/test/fixtures/ties.yml +0 -4
  271. data/test/fixtures/topics.yml +0 -42
  272. data/test/fixtures/toys.yml +0 -4
  273. data/test/fixtures/treasures.yml +0 -10
  274. data/test/fixtures/vertices.yml +0 -4
  275. data/test/fixtures/warehouse-things.yml +0 -3
  276. data/test/fixtures/zines.yml +0 -5
  277. data/test/migrations/broken/100_migration_that_raises_exception.rb +0 -10
  278. data/test/migrations/decimal/1_give_me_big_numbers.rb +0 -15
  279. data/test/migrations/duplicate/1_people_have_last_names.rb +0 -9
  280. data/test/migrations/duplicate/2_we_need_reminders.rb +0 -12
  281. data/test/migrations/duplicate/3_foo.rb +0 -7
  282. data/test/migrations/duplicate/3_innocent_jointable.rb +0 -12
  283. data/test/migrations/duplicate_names/20080507052938_chunky.rb +0 -7
  284. data/test/migrations/duplicate_names/20080507053028_chunky.rb +0 -7
  285. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +0 -12
  286. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +0 -9
  287. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +0 -12
  288. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +0 -9
  289. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +0 -8
  290. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +0 -12
  291. data/test/migrations/missing/1000_people_have_middle_names.rb +0 -9
  292. data/test/migrations/missing/1_people_have_last_names.rb +0 -9
  293. data/test/migrations/missing/3_we_need_reminders.rb +0 -12
  294. data/test/migrations/missing/4_innocent_jointable.rb +0 -12
  295. data/test/migrations/valid/1_people_have_last_names.rb +0 -9
  296. data/test/migrations/valid/2_we_need_reminders.rb +0 -12
  297. data/test/migrations/valid/3_innocent_jointable.rb +0 -12
  298. data/test/models/author.rb +0 -151
  299. data/test/models/auto_id.rb +0 -4
  300. data/test/models/binary.rb +0 -2
  301. data/test/models/bird.rb +0 -9
  302. data/test/models/book.rb +0 -4
  303. data/test/models/categorization.rb +0 -5
  304. data/test/models/category.rb +0 -34
  305. data/test/models/citation.rb +0 -6
  306. data/test/models/club.rb +0 -13
  307. data/test/models/column_name.rb +0 -3
  308. data/test/models/comment.rb +0 -29
  309. data/test/models/company.rb +0 -173
  310. data/test/models/company_in_module.rb +0 -78
  311. data/test/models/computer.rb +0 -3
  312. data/test/models/contact.rb +0 -16
  313. data/test/models/contract.rb +0 -5
  314. data/test/models/course.rb +0 -3
  315. data/test/models/customer.rb +0 -73
  316. data/test/models/default.rb +0 -2
  317. data/test/models/developer.rb +0 -101
  318. data/test/models/edge.rb +0 -5
  319. data/test/models/entrant.rb +0 -3
  320. data/test/models/essay.rb +0 -3
  321. data/test/models/event.rb +0 -3
  322. data/test/models/event_author.rb +0 -8
  323. data/test/models/face.rb +0 -7
  324. data/test/models/guid.rb +0 -2
  325. data/test/models/interest.rb +0 -5
  326. data/test/models/invoice.rb +0 -4
  327. data/test/models/item.rb +0 -7
  328. data/test/models/job.rb +0 -5
  329. data/test/models/joke.rb +0 -3
  330. data/test/models/keyboard.rb +0 -3
  331. data/test/models/legacy_thing.rb +0 -3
  332. data/test/models/line_item.rb +0 -3
  333. data/test/models/man.rb +0 -9
  334. data/test/models/matey.rb +0 -4
  335. data/test/models/member.rb +0 -12
  336. data/test/models/member_detail.rb +0 -5
  337. data/test/models/member_type.rb +0 -3
  338. data/test/models/membership.rb +0 -9
  339. data/test/models/minimalistic.rb +0 -2
  340. data/test/models/mixed_case_monkey.rb +0 -3
  341. data/test/models/movie.rb +0 -5
  342. data/test/models/order.rb +0 -4
  343. data/test/models/organization.rb +0 -6
  344. data/test/models/owner.rb +0 -5
  345. data/test/models/parrot.rb +0 -22
  346. data/test/models/person.rb +0 -16
  347. data/test/models/pet.rb +0 -5
  348. data/test/models/pirate.rb +0 -80
  349. data/test/models/polymorphic_design.rb +0 -3
  350. data/test/models/polymorphic_price.rb +0 -3
  351. data/test/models/post.rb +0 -102
  352. data/test/models/price_estimate.rb +0 -3
  353. data/test/models/project.rb +0 -30
  354. data/test/models/reader.rb +0 -4
  355. data/test/models/reference.rb +0 -4
  356. data/test/models/reply.rb +0 -46
  357. data/test/models/ship.rb +0 -19
  358. data/test/models/ship_part.rb +0 -7
  359. data/test/models/sponsor.rb +0 -4
  360. data/test/models/subject.rb +0 -4
  361. data/test/models/subscriber.rb +0 -8
  362. data/test/models/subscription.rb +0 -4
  363. data/test/models/tag.rb +0 -7
  364. data/test/models/tagging.rb +0 -10
  365. data/test/models/task.rb +0 -3
  366. data/test/models/tee.rb +0 -4
  367. data/test/models/tie.rb +0 -4
  368. data/test/models/topic.rb +0 -80
  369. data/test/models/toy.rb +0 -6
  370. data/test/models/treasure.rb +0 -8
  371. data/test/models/vertex.rb +0 -9
  372. data/test/models/warehouse_thing.rb +0 -5
  373. data/test/models/zine.rb +0 -3
  374. data/test/schema/mysql_specific_schema.rb +0 -31
  375. data/test/schema/postgresql_specific_schema.rb +0 -114
  376. data/test/schema/schema.rb +0 -550
  377. data/test/schema/schema2.rb +0 -6
  378. data/test/schema/sqlite_specific_schema.rb +0 -25
@@ -0,0 +1,87 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Dirty
4
+ extend ActiveSupport::Concern
5
+ include ActiveModel::Dirty
6
+
7
+ included do
8
+ alias_method_chain :save, :dirty
9
+ alias_method_chain :save!, :dirty
10
+ alias_method_chain :update, :dirty
11
+ alias_method_chain :reload, :dirty
12
+
13
+ superclass_delegating_accessor :partial_updates
14
+ self.partial_updates = true
15
+ end
16
+
17
+ # Attempts to +save+ the record and clears changed attributes if successful.
18
+ def save_with_dirty(*args) #:nodoc:
19
+ if status = save_without_dirty(*args)
20
+ @previously_changed = changes
21
+ changed_attributes.clear
22
+ end
23
+ status
24
+ end
25
+
26
+ # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
27
+ def save_with_dirty!(*args) #:nodoc:
28
+ save_without_dirty!(*args).tap do
29
+ @previously_changed = changes
30
+ changed_attributes.clear
31
+ end
32
+ end
33
+
34
+ # <tt>reload</tt> the record and clears changed attributes.
35
+ def reload_with_dirty(*args) #:nodoc:
36
+ reload_without_dirty(*args).tap do
37
+ previously_changed_attributes.clear
38
+ changed_attributes.clear
39
+ end
40
+ end
41
+
42
+ private
43
+ # Wrap write_attribute to remember original attribute value.
44
+ def write_attribute(attr, value)
45
+ attr = attr.to_s
46
+
47
+ # The attribute already has an unsaved change.
48
+ if changed_attributes.include?(attr)
49
+ old = changed_attributes[attr]
50
+ changed_attributes.delete(attr) unless field_changed?(attr, old, value)
51
+ else
52
+ old = clone_attribute_value(:read_attribute, attr)
53
+ changed_attributes[attr] = old if field_changed?(attr, old, value)
54
+ end
55
+
56
+ # Carry on.
57
+ super(attr, value)
58
+ end
59
+
60
+ def update_with_dirty
61
+ if partial_updates?
62
+ # Serialized attributes should always be written in case they've been
63
+ # changed in place.
64
+ update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
65
+ else
66
+ update_without_dirty
67
+ end
68
+ end
69
+
70
+ def field_changed?(attr, old, value)
71
+ if column = column_for_attribute(attr)
72
+ if column.number? && column.null && (old.nil? || old == 0) && value.blank?
73
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
74
+ # Hence we don't record it as a change if the value changes from nil to ''.
75
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
76
+ # be typecast back to 0 (''.to_i => 0)
77
+ value = nil
78
+ else
79
+ value = column.type_cast(value)
80
+ end
81
+ end
82
+
83
+ old != value
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,44 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module PrimaryKey
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
8
+ # primary_key_prefix_type setting, though.
9
+ def primary_key
10
+ reset_primary_key
11
+ end
12
+
13
+ def reset_primary_key #:nodoc:
14
+ key = get_primary_key(base_class.name)
15
+ set_primary_key(key)
16
+ key
17
+ end
18
+
19
+ def get_primary_key(base_name) #:nodoc:
20
+ key = 'id'
21
+ case primary_key_prefix_type
22
+ when :table_name
23
+ key = base_name.to_s.foreign_key(false)
24
+ when :table_name_with_underscore
25
+ key = base_name.to_s.foreign_key
26
+ end
27
+ key
28
+ end
29
+
30
+ # Sets the name of the primary key column to use to the given value,
31
+ # or (if the value is nil or false) to the value returned by the given
32
+ # block.
33
+ #
34
+ # class Project < ActiveRecord::Base
35
+ # set_primary_key "sysid"
36
+ # end
37
+ def set_primary_key(value = nil, &block)
38
+ define_attr_method :primary_key, value, &block
39
+ end
40
+ alias :primary_key= :set_primary_key
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,37 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Query
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute_method_suffix "?"
8
+ end
9
+
10
+ def query_attribute(attr_name)
11
+ unless value = read_attribute(attr_name)
12
+ false
13
+ else
14
+ column = self.class.columns_hash[attr_name]
15
+ if column.nil?
16
+ if Numeric === value || value !~ /[^0-9]/
17
+ !value.to_i.zero?
18
+ else
19
+ return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
20
+ !value.blank?
21
+ end
22
+ elsif column.number?
23
+ !value.zero?
24
+ else
25
+ !value.blank?
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+ # Handle *? for method_missing.
32
+ def attribute?(attribute_name)
33
+ query_attribute(attribute_name)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,116 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Read
4
+ extend ActiveSupport::Concern
5
+
6
+ ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
7
+
8
+ included do
9
+ attribute_method_suffix ""
10
+
11
+ cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
12
+ self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
13
+
14
+ # Undefine id so it can be used as an attribute name
15
+ undef_method(:id) if method_defined?(:id)
16
+ end
17
+
18
+ module ClassMethods
19
+ # +cache_attributes+ allows you to declare which converted attribute values should
20
+ # be cached. Usually caching only pays off for attributes with expensive conversion
21
+ # methods, like time related columns (e.g. +created_at+, +updated_at+).
22
+ def cache_attributes(*attribute_names)
23
+ attribute_names.each {|attr| cached_attributes << attr.to_s}
24
+ end
25
+
26
+ # Returns the attributes which are cached. By default time related columns
27
+ # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
28
+ def cached_attributes
29
+ @cached_attributes ||=
30
+ columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set
31
+ end
32
+
33
+ # Returns +true+ if the provided attribute is being cached.
34
+ def cache_attribute?(attr_name)
35
+ cached_attributes.include?(attr_name)
36
+ end
37
+
38
+ protected
39
+ def define_method_attribute(attr_name)
40
+ if self.serialized_attributes[attr_name]
41
+ define_read_method_for_serialized_attribute(attr_name)
42
+ else
43
+ define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
44
+ end
45
+
46
+ if attr_name == primary_key && attr_name != "id"
47
+ define_read_method(:id, attr_name, columns_hash[attr_name])
48
+ end
49
+ end
50
+
51
+ private
52
+ # Define read method for serialized attribute.
53
+ def define_read_method_for_serialized_attribute(attr_name)
54
+ generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__)
55
+ end
56
+
57
+ # Define an attribute reader method. Cope with nil column.
58
+ def define_read_method(symbol, attr_name, column)
59
+ cast_code = column.type_cast_code('v') if column
60
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
61
+
62
+ unless attr_name.to_s == self.primary_key.to_s
63
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
64
+ end
65
+
66
+ if cache_attribute?(attr_name)
67
+ access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
68
+ end
69
+ generated_attribute_methods.module_eval("def #{symbol}; #{access_code}; end", __FILE__, __LINE__)
70
+ end
71
+ end
72
+
73
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
74
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
75
+ def read_attribute(attr_name)
76
+ attr_name = attr_name.to_s
77
+ attr_name = self.class.primary_key if attr_name == 'id'
78
+ if !(value = @attributes[attr_name]).nil?
79
+ if column = column_for_attribute(attr_name)
80
+ if unserializable_attribute?(attr_name, column)
81
+ unserialize_attribute(attr_name)
82
+ else
83
+ column.type_cast(value)
84
+ end
85
+ else
86
+ value
87
+ end
88
+ else
89
+ nil
90
+ end
91
+ end
92
+
93
+ # Returns true if the attribute is of a text column and marked for serialization.
94
+ def unserializable_attribute?(attr_name, column)
95
+ column.text? && self.class.serialized_attributes[attr_name]
96
+ end
97
+
98
+ # Returns the unserialized object of the attribute.
99
+ def unserialize_attribute(attr_name)
100
+ unserialized_object = object_from_yaml(@attributes[attr_name])
101
+
102
+ if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
103
+ @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
104
+ else
105
+ raise SerializationTypeMismatch,
106
+ "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
107
+ end
108
+ end
109
+
110
+ private
111
+ def attribute(attribute_name)
112
+ read_attribute(attribute_name)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,60 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module TimeZoneConversion
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ cattr_accessor :time_zone_aware_attributes, :instance_writer => false
8
+ self.time_zone_aware_attributes = false
9
+
10
+ class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
11
+ self.skip_time_zone_conversion_for_attributes = []
12
+ end
13
+
14
+ module ClassMethods
15
+ protected
16
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
17
+ # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
18
+ def define_method_attribute(attr_name)
19
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
20
+ method_body = <<-EOV
21
+ def #{attr_name}(reload = false)
22
+ cached = @attributes_cache['#{attr_name}']
23
+ return cached if cached && !reload
24
+ time = read_attribute('#{attr_name}')
25
+ @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
26
+ end
27
+ EOV
28
+ generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
35
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
36
+ def define_method_attribute=(attr_name)
37
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
38
+ method_body = <<-EOV
39
+ def #{attr_name}=(time)
40
+ unless time.acts_like?(:time)
41
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
42
+ end
43
+ time = time.in_time_zone rescue nil if time
44
+ write_attribute(:#{attr_name}, time)
45
+ end
46
+ EOV
47
+ generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+ private
54
+ def create_time_zone_conversion_attribute?(name, column)
55
+ time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module Write
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute_method_suffix "="
8
+ end
9
+
10
+ module ClassMethods
11
+ protected
12
+ def define_method_attribute=(attr_name)
13
+ generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
14
+ end
15
+ end
16
+
17
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
18
+ # columns are turned into +nil+.
19
+ def write_attribute(attr_name, value)
20
+ attr_name = attr_name.to_s
21
+ attr_name = self.class.primary_key if attr_name == 'id'
22
+ @attributes_cache.delete(attr_name)
23
+ if (column = column_for_attribute(attr_name)) && column.number?
24
+ @attributes[attr_name] = convert_number_column_value(value)
25
+ else
26
+ @attributes[attr_name] = value
27
+ end
28
+ end
29
+
30
+ private
31
+ # Handle *= for method_missing.
32
+ def attribute=(attribute_name, value)
33
+ write_attribute(attribute_name, value)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -116,25 +116,24 @@ module ActiveRecord
116
116
  # post = Post.find(1)
117
117
  # post.author.name = ''
118
118
  # post.save # => false
119
- # post.errors # => #<ActiveRecord::Errors:0x174498c @errors={"author_name"=>["can't be blank"]}, @base=#<Post ...>>
119
+ # post.errors # => #<ActiveRecord::Errors:0x174498c @errors={"author.name"=>["can't be blank"]}, @base=#<Post ...>>
120
120
  #
121
121
  # No validations will be performed on the associated models when validations
122
122
  # are skipped for the parent:
123
123
  #
124
124
  # post = Post.find(1)
125
125
  # post.author.name = ''
126
- # post.save(false) # => true
126
+ # post.save(:validate => false) # => true
127
127
  module AutosaveAssociation
128
+ extend ActiveSupport::Concern
129
+
128
130
  ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
129
131
 
130
- def self.included(base)
131
- base.class_eval do
132
- base.extend(ClassMethods)
133
- alias_method_chain :reload, :autosave_associations
132
+ included do
133
+ alias_method_chain :reload, :autosave_associations
134
134
 
135
- ASSOCIATION_TYPES.each do |type|
136
- base.send("valid_keys_for_#{type}_association") << :autosave
137
- end
135
+ ASSOCIATION_TYPES.each do |type|
136
+ send("valid_keys_for_#{type}_association") << :autosave
138
137
  end
139
138
  end
140
139
 
@@ -146,12 +145,12 @@ module ActiveRecord
146
145
  # add_autosave_association_callbacks(reflect_on_association(name))
147
146
  # end
148
147
  ASSOCIATION_TYPES.each do |type|
149
- module_eval <<-CODE, __FILE__, __LINE__ + 1
148
+ module_eval %{
150
149
  def #{type}(name, options = {})
151
150
  super
152
151
  add_autosave_association_callbacks(reflect_on_association(name))
153
152
  end
154
- CODE
153
+ }
155
154
  end
156
155
 
157
156
  # Adds a validate and save callback for the association as specified by
@@ -217,12 +216,6 @@ module ActiveRecord
217
216
  @marked_for_destruction
218
217
  end
219
218
 
220
- # Returns whether or not this record has been changed in any way (including whether
221
- # any of its nested autosave associations are likewise changed)
222
- def changed_for_autosave?
223
- new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
224
- end
225
-
226
219
  private
227
220
 
228
221
  # Returns the record for an association collection that should be validated
@@ -232,27 +225,12 @@ module ActiveRecord
232
225
  if new_record
233
226
  association
234
227
  elsif autosave
235
- association.target.select { |record| record.changed_for_autosave? }
228
+ association.target.find_all { |record| record.new_record? || record.changed? || record.marked_for_destruction? }
236
229
  else
237
- association.target.select { |record| record.new_record? }
238
- end
239
- end
240
-
241
- # go through nested autosave associations that are loaded in memory (without loading
242
- # any new ones), and return true if is changed for autosave
243
- def nested_records_changed_for_autosave?
244
- self.class.reflect_on_all_autosave_associations.each do |reflection|
245
- if association = association_instance_get(reflection.name)
246
- if [:belongs_to, :has_one].include?(reflection.macro)
247
- return true if association.target && association.target.changed_for_autosave?
248
- else
249
- association.target.each {|record| return true if record.changed_for_autosave? }
250
- end
251
- end
230
+ association.target.find_all { |record| record.new_record? }
252
231
  end
253
- false
254
232
  end
255
-
233
+
256
234
  # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
257
235
  # turned on for the association specified by +reflection+.
258
236
  def validate_single_association(reflection)
@@ -280,9 +258,10 @@ module ActiveRecord
280
258
 
281
259
  unless valid = association.valid?
282
260
  if reflection.options[:autosave]
283
- association.errors.each_error do |attribute, error|
261
+ association.errors.each do |attribute, message|
284
262
  attribute = "#{reflection.name}.#{attribute}"
285
- errors.add(attribute, error.dup) unless errors.on(attribute)
263
+ errors[attribute] << message
264
+ errors[attribute].uniq!
286
265
  end
287
266
  else
288
267
  errors.add(reflection.name)
@@ -323,7 +302,7 @@ module ActiveRecord
323
302
  association.send(:insert_record, record)
324
303
  end
325
304
  elsif autosave
326
- saved = record.save(false)
305
+ saved = record.save(:validate => false)
327
306
  end
328
307
 
329
308
  raise ActiveRecord::Rollback if saved == false
@@ -353,7 +332,7 @@ module ActiveRecord
353
332
  key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
354
333
  if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
355
334
  association[reflection.primary_key_name] = key
356
- saved = association.save(!autosave)
335
+ saved = association.save(:validate => !autosave)
357
336
  raise ActiveRecord::Rollback if !saved && autosave
358
337
  saved
359
338
  end
@@ -376,7 +355,7 @@ module ActiveRecord
376
355
  if autosave && association.marked_for_destruction?
377
356
  association.destroy
378
357
  elsif autosave != false
379
- saved = association.save(!autosave) if association.new_record? || autosave
358
+ saved = association.save(:validate => !autosave) if association.new_record? || autosave
380
359
 
381
360
  if association.updated?
382
361
  association_id = association.send(reflection.options[:primary_key] || :id)
@@ -392,4 +371,4 @@ module ActiveRecord
392
371
  end
393
372
  end
394
373
  end
395
- end
374
+ end