activerecord 2.3.18 → 3.2.22

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 (454) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1014 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +222 -0
  5. data/examples/performance.rb +100 -126
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +93 -99
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +247 -0
  10. data/lib/active_record/associations/association_scope.rb +134 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +54 -61
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +17 -59
  13. data/lib/active_record/associations/builder/association.rb +55 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +88 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  17. data/lib/active_record/associations/builder/has_many.rb +71 -0
  18. data/lib/active_record/associations/builder/has_one.rb +62 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +580 -0
  21. data/lib/active_record/associations/collection_proxy.rb +133 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +39 -119
  23. data/lib/active_record/associations/has_many_association.rb +60 -79
  24. data/lib/active_record/associations/has_many_through_association.rb +127 -206
  25. data/lib/active_record/associations/has_one_association.rb +55 -114
  26. data/lib/active_record/associations/has_one_through_association.rb +25 -26
  27. data/lib/active_record/associations/join_dependency/join_association.rb +159 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +214 -0
  31. data/lib/active_record/associations/join_helper.rb +55 -0
  32. data/lib/active_record/associations/preloader/association.rb +125 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/preloader.rb +181 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +693 -1337
  46. data/lib/active_record/attribute_assignment.rb +221 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  48. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  49. data/lib/active_record/attribute_methods/dirty.rb +111 -0
  50. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  51. data/lib/active_record/attribute_methods/query.rb +39 -0
  52. data/lib/active_record/attribute_methods/read.rb +136 -0
  53. data/lib/active_record/attribute_methods/serialization.rb +120 -0
  54. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
  55. data/lib/active_record/attribute_methods/write.rb +70 -0
  56. data/lib/active_record/attribute_methods.rb +211 -339
  57. data/lib/active_record/autosave_association.rb +179 -149
  58. data/lib/active_record/base.rb +401 -2907
  59. data/lib/active_record/callbacks.rb +91 -176
  60. data/lib/active_record/coders/yaml_column.rb +41 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +236 -119
  62. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +110 -58
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +175 -74
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -35
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +71 -21
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +81 -311
  68. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +194 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +130 -83
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
  71. data/lib/active_record/connection_adapters/column.rb +296 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +280 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +272 -493
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +650 -405
  75. data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
  76. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +30 -9
  77. data/lib/active_record/connection_adapters/sqlite_adapter.rb +276 -147
  78. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  79. data/lib/active_record/counter_cache.rb +123 -0
  80. data/lib/active_record/dynamic_finder_match.rb +41 -14
  81. data/lib/active_record/dynamic_matchers.rb +84 -0
  82. data/lib/active_record/dynamic_scope_match.rb +13 -15
  83. data/lib/active_record/errors.rb +195 -0
  84. data/lib/active_record/explain.rb +86 -0
  85. data/lib/active_record/explain_subscriber.rb +25 -0
  86. data/lib/active_record/fixtures/file.rb +65 -0
  87. data/lib/active_record/fixtures.rb +695 -770
  88. data/lib/active_record/identity_map.rb +162 -0
  89. data/lib/active_record/inheritance.rb +174 -0
  90. data/lib/active_record/integration.rb +60 -0
  91. data/lib/active_record/locale/en.yml +9 -27
  92. data/lib/active_record/locking/optimistic.rb +76 -73
  93. data/lib/active_record/locking/pessimistic.rb +32 -10
  94. data/lib/active_record/log_subscriber.rb +72 -0
  95. data/lib/active_record/migration/command_recorder.rb +105 -0
  96. data/lib/active_record/migration.rb +415 -205
  97. data/lib/active_record/model_schema.rb +368 -0
  98. data/lib/active_record/nested_attributes.rb +153 -63
  99. data/lib/active_record/observer.rb +27 -103
  100. data/lib/active_record/persistence.rb +376 -0
  101. data/lib/active_record/query_cache.rb +49 -8
  102. data/lib/active_record/querying.rb +58 -0
  103. data/lib/active_record/railtie.rb +131 -0
  104. data/lib/active_record/railties/console_sandbox.rb +6 -0
  105. data/lib/active_record/railties/controller_runtime.rb +49 -0
  106. data/lib/active_record/railties/databases.rake +659 -0
  107. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  108. data/lib/active_record/readonly_attributes.rb +26 -0
  109. data/lib/active_record/reflection.rb +269 -120
  110. data/lib/active_record/relation/batches.rb +90 -0
  111. data/lib/active_record/relation/calculations.rb +372 -0
  112. data/lib/active_record/relation/delegation.rb +49 -0
  113. data/lib/active_record/relation/finder_methods.rb +402 -0
  114. data/lib/active_record/relation/predicate_builder.rb +63 -0
  115. data/lib/active_record/relation/query_methods.rb +417 -0
  116. data/lib/active_record/relation/spawn_methods.rb +180 -0
  117. data/lib/active_record/relation.rb +537 -0
  118. data/lib/active_record/result.rb +40 -0
  119. data/lib/active_record/sanitization.rb +194 -0
  120. data/lib/active_record/schema.rb +9 -6
  121. data/lib/active_record/schema_dumper.rb +55 -32
  122. data/lib/active_record/scoping/default.rb +142 -0
  123. data/lib/active_record/scoping/named.rb +200 -0
  124. data/lib/active_record/scoping.rb +152 -0
  125. data/lib/active_record/serialization.rb +8 -91
  126. data/lib/active_record/serializers/xml_serializer.rb +43 -197
  127. data/lib/active_record/session_store.rb +129 -103
  128. data/lib/active_record/store.rb +52 -0
  129. data/lib/active_record/test_case.rb +30 -23
  130. data/lib/active_record/timestamp.rb +95 -52
  131. data/lib/active_record/transactions.rb +212 -66
  132. data/lib/active_record/translation.rb +22 -0
  133. data/lib/active_record/validations/associated.rb +43 -0
  134. data/lib/active_record/validations/uniqueness.rb +180 -0
  135. data/lib/active_record/validations.rb +43 -1106
  136. data/lib/active_record/version.rb +5 -4
  137. data/lib/active_record.rb +121 -48
  138. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  139. data/lib/rails/generators/active_record/migration/templates/migration.rb +34 -0
  140. data/lib/rails/generators/active_record/migration.rb +15 -0
  141. data/lib/rails/generators/active_record/model/model_generator.rb +47 -0
  142. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  143. data/lib/rails/generators/active_record/model/templates/model.rb +12 -0
  144. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  145. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  146. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  147. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  148. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  149. data/lib/rails/generators/active_record.rb +25 -0
  150. metadata +187 -363
  151. data/CHANGELOG +0 -5904
  152. data/README +0 -351
  153. data/RUNNING_UNIT_TESTS +0 -36
  154. data/Rakefile +0 -268
  155. data/install.rb +0 -30
  156. data/lib/active_record/association_preload.rb +0 -406
  157. data/lib/active_record/associations/association_collection.rb +0 -533
  158. data/lib/active_record/associations/association_proxy.rb +0 -288
  159. data/lib/active_record/batches.rb +0 -85
  160. data/lib/active_record/calculations.rb +0 -321
  161. data/lib/active_record/dirty.rb +0 -183
  162. data/lib/active_record/named_scope.rb +0 -197
  163. data/lib/active_record/serializers/json_serializer.rb +0 -91
  164. data/lib/activerecord.rb +0 -2
  165. data/test/assets/example.log +0 -1
  166. data/test/assets/flowers.jpg +0 -0
  167. data/test/cases/aaa_create_tables_test.rb +0 -24
  168. data/test/cases/active_schema_test_mysql.rb +0 -122
  169. data/test/cases/active_schema_test_postgresql.rb +0 -24
  170. data/test/cases/adapter_test.rb +0 -144
  171. data/test/cases/aggregations_test.rb +0 -167
  172. data/test/cases/ar_schema_test.rb +0 -32
  173. data/test/cases/associations/belongs_to_associations_test.rb +0 -438
  174. data/test/cases/associations/callbacks_test.rb +0 -161
  175. data/test/cases/associations/cascaded_eager_loading_test.rb +0 -131
  176. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +0 -36
  177. data/test/cases/associations/eager_load_nested_include_test.rb +0 -131
  178. data/test/cases/associations/eager_load_nested_polymorphic_include.rb +0 -19
  179. data/test/cases/associations/eager_singularization_test.rb +0 -145
  180. data/test/cases/associations/eager_test.rb +0 -852
  181. data/test/cases/associations/extension_test.rb +0 -62
  182. data/test/cases/associations/habtm_join_table_test.rb +0 -56
  183. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +0 -827
  184. data/test/cases/associations/has_many_associations_test.rb +0 -1273
  185. data/test/cases/associations/has_many_through_associations_test.rb +0 -360
  186. data/test/cases/associations/has_one_associations_test.rb +0 -330
  187. data/test/cases/associations/has_one_through_associations_test.rb +0 -209
  188. data/test/cases/associations/inner_join_association_test.rb +0 -93
  189. data/test/cases/associations/inverse_associations_test.rb +0 -566
  190. data/test/cases/associations/join_model_test.rb +0 -712
  191. data/test/cases/associations_test.rb +0 -282
  192. data/test/cases/attribute_methods_test.rb +0 -305
  193. data/test/cases/autosave_association_test.rb +0 -1218
  194. data/test/cases/base_test.rb +0 -2166
  195. data/test/cases/batches_test.rb +0 -81
  196. data/test/cases/binary_test.rb +0 -30
  197. data/test/cases/calculations_test.rb +0 -360
  198. data/test/cases/callbacks_observers_test.rb +0 -38
  199. data/test/cases/callbacks_test.rb +0 -438
  200. data/test/cases/class_inheritable_attributes_test.rb +0 -32
  201. data/test/cases/column_alias_test.rb +0 -17
  202. data/test/cases/column_definition_test.rb +0 -70
  203. data/test/cases/connection_pool_test.rb +0 -25
  204. data/test/cases/connection_test_firebird.rb +0 -8
  205. data/test/cases/connection_test_mysql.rb +0 -65
  206. data/test/cases/copy_table_test_sqlite.rb +0 -80
  207. data/test/cases/counter_cache_test.rb +0 -84
  208. data/test/cases/database_statements_test.rb +0 -12
  209. data/test/cases/datatype_test_postgresql.rb +0 -204
  210. data/test/cases/date_time_test.rb +0 -37
  211. data/test/cases/default_test_firebird.rb +0 -16
  212. data/test/cases/defaults_test.rb +0 -111
  213. data/test/cases/deprecated_finder_test.rb +0 -30
  214. data/test/cases/dirty_test.rb +0 -316
  215. data/test/cases/finder_respond_to_test.rb +0 -76
  216. data/test/cases/finder_test.rb +0 -1098
  217. data/test/cases/fixtures_test.rb +0 -661
  218. data/test/cases/helper.rb +0 -68
  219. data/test/cases/i18n_test.rb +0 -46
  220. data/test/cases/inheritance_test.rb +0 -262
  221. data/test/cases/invalid_date_test.rb +0 -24
  222. data/test/cases/json_serialization_test.rb +0 -219
  223. data/test/cases/lifecycle_test.rb +0 -193
  224. data/test/cases/locking_test.rb +0 -350
  225. data/test/cases/method_scoping_test.rb +0 -704
  226. data/test/cases/migration_test.rb +0 -1649
  227. data/test/cases/migration_test_firebird.rb +0 -124
  228. data/test/cases/mixin_test.rb +0 -96
  229. data/test/cases/modules_test.rb +0 -109
  230. data/test/cases/multiple_db_test.rb +0 -85
  231. data/test/cases/named_scope_test.rb +0 -372
  232. data/test/cases/nested_attributes_test.rb +0 -840
  233. data/test/cases/pk_test.rb +0 -119
  234. data/test/cases/pooled_connections_test.rb +0 -103
  235. data/test/cases/query_cache_test.rb +0 -129
  236. data/test/cases/readonly_test.rb +0 -107
  237. data/test/cases/reflection_test.rb +0 -234
  238. data/test/cases/reload_models_test.rb +0 -22
  239. data/test/cases/repair_helper.rb +0 -50
  240. data/test/cases/reserved_word_test_mysql.rb +0 -176
  241. data/test/cases/sanitize_test.rb +0 -25
  242. data/test/cases/schema_authorization_test_postgresql.rb +0 -75
  243. data/test/cases/schema_dumper_test.rb +0 -211
  244. data/test/cases/schema_test_postgresql.rb +0 -178
  245. data/test/cases/serialization_test.rb +0 -47
  246. data/test/cases/sp_test_mysql.rb +0 -16
  247. data/test/cases/synonym_test_oracle.rb +0 -17
  248. data/test/cases/timestamp_test.rb +0 -75
  249. data/test/cases/transactions_test.rb +0 -543
  250. data/test/cases/unconnected_test.rb +0 -32
  251. data/test/cases/validations_i18n_test.rb +0 -925
  252. data/test/cases/validations_test.rb +0 -1684
  253. data/test/cases/xml_serialization_test.rb +0 -240
  254. data/test/cases/yaml_serialization_test.rb +0 -11
  255. data/test/config.rb +0 -5
  256. data/test/connections/jdbc_jdbcderby/connection.rb +0 -18
  257. data/test/connections/jdbc_jdbch2/connection.rb +0 -18
  258. data/test/connections/jdbc_jdbchsqldb/connection.rb +0 -18
  259. data/test/connections/jdbc_jdbcmysql/connection.rb +0 -26
  260. data/test/connections/jdbc_jdbcpostgresql/connection.rb +0 -26
  261. data/test/connections/jdbc_jdbcsqlite3/connection.rb +0 -25
  262. data/test/connections/native_db2/connection.rb +0 -25
  263. data/test/connections/native_firebird/connection.rb +0 -26
  264. data/test/connections/native_frontbase/connection.rb +0 -27
  265. data/test/connections/native_mysql/connection.rb +0 -25
  266. data/test/connections/native_openbase/connection.rb +0 -21
  267. data/test/connections/native_oracle/connection.rb +0 -27
  268. data/test/connections/native_postgresql/connection.rb +0 -21
  269. data/test/connections/native_sqlite/connection.rb +0 -25
  270. data/test/connections/native_sqlite3/connection.rb +0 -25
  271. data/test/connections/native_sqlite3/in_memory_connection.rb +0 -18
  272. data/test/connections/native_sybase/connection.rb +0 -23
  273. data/test/fixtures/accounts.yml +0 -29
  274. data/test/fixtures/all/developers.yml +0 -0
  275. data/test/fixtures/all/people.csv +0 -0
  276. data/test/fixtures/all/tasks.yml +0 -0
  277. data/test/fixtures/author_addresses.yml +0 -5
  278. data/test/fixtures/author_favorites.yml +0 -4
  279. data/test/fixtures/authors.yml +0 -9
  280. data/test/fixtures/binaries.yml +0 -132
  281. data/test/fixtures/books.yml +0 -7
  282. data/test/fixtures/categories/special_categories.yml +0 -9
  283. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +0 -4
  284. data/test/fixtures/categories.yml +0 -14
  285. data/test/fixtures/categories_ordered.yml +0 -7
  286. data/test/fixtures/categories_posts.yml +0 -23
  287. data/test/fixtures/categorizations.yml +0 -17
  288. data/test/fixtures/clubs.yml +0 -6
  289. data/test/fixtures/comments.yml +0 -59
  290. data/test/fixtures/companies.yml +0 -56
  291. data/test/fixtures/computers.yml +0 -4
  292. data/test/fixtures/courses.yml +0 -7
  293. data/test/fixtures/customers.yml +0 -26
  294. data/test/fixtures/developers.yml +0 -21
  295. data/test/fixtures/developers_projects.yml +0 -17
  296. data/test/fixtures/edges.yml +0 -6
  297. data/test/fixtures/entrants.yml +0 -14
  298. data/test/fixtures/faces.yml +0 -11
  299. data/test/fixtures/fk_test_has_fk.yml +0 -3
  300. data/test/fixtures/fk_test_has_pk.yml +0 -2
  301. data/test/fixtures/funny_jokes.yml +0 -10
  302. data/test/fixtures/interests.yml +0 -33
  303. data/test/fixtures/items.yml +0 -4
  304. data/test/fixtures/jobs.yml +0 -7
  305. data/test/fixtures/legacy_things.yml +0 -3
  306. data/test/fixtures/mateys.yml +0 -4
  307. data/test/fixtures/member_types.yml +0 -6
  308. data/test/fixtures/members.yml +0 -6
  309. data/test/fixtures/memberships.yml +0 -20
  310. data/test/fixtures/men.yml +0 -5
  311. data/test/fixtures/minimalistics.yml +0 -2
  312. data/test/fixtures/mixed_case_monkeys.yml +0 -6
  313. data/test/fixtures/mixins.yml +0 -29
  314. data/test/fixtures/movies.yml +0 -7
  315. data/test/fixtures/naked/csv/accounts.csv +0 -1
  316. data/test/fixtures/naked/yml/accounts.yml +0 -1
  317. data/test/fixtures/naked/yml/companies.yml +0 -1
  318. data/test/fixtures/naked/yml/courses.yml +0 -1
  319. data/test/fixtures/organizations.yml +0 -5
  320. data/test/fixtures/owners.yml +0 -7
  321. data/test/fixtures/parrots.yml +0 -27
  322. data/test/fixtures/parrots_pirates.yml +0 -7
  323. data/test/fixtures/people.yml +0 -15
  324. data/test/fixtures/pets.yml +0 -14
  325. data/test/fixtures/pirates.yml +0 -9
  326. data/test/fixtures/polymorphic_designs.yml +0 -19
  327. data/test/fixtures/polymorphic_prices.yml +0 -19
  328. data/test/fixtures/posts.yml +0 -52
  329. data/test/fixtures/price_estimates.yml +0 -7
  330. data/test/fixtures/projects.yml +0 -7
  331. data/test/fixtures/readers.yml +0 -9
  332. data/test/fixtures/references.yml +0 -17
  333. data/test/fixtures/reserved_words/distinct.yml +0 -5
  334. data/test/fixtures/reserved_words/distincts_selects.yml +0 -11
  335. data/test/fixtures/reserved_words/group.yml +0 -14
  336. data/test/fixtures/reserved_words/select.yml +0 -8
  337. data/test/fixtures/reserved_words/values.yml +0 -7
  338. data/test/fixtures/ships.yml +0 -5
  339. data/test/fixtures/sponsors.yml +0 -9
  340. data/test/fixtures/subscribers.yml +0 -7
  341. data/test/fixtures/subscriptions.yml +0 -12
  342. data/test/fixtures/taggings.yml +0 -28
  343. data/test/fixtures/tags.yml +0 -7
  344. data/test/fixtures/tasks.yml +0 -7
  345. data/test/fixtures/tees.yml +0 -4
  346. data/test/fixtures/ties.yml +0 -4
  347. data/test/fixtures/topics.yml +0 -42
  348. data/test/fixtures/toys.yml +0 -4
  349. data/test/fixtures/treasures.yml +0 -10
  350. data/test/fixtures/vertices.yml +0 -4
  351. data/test/fixtures/warehouse-things.yml +0 -3
  352. data/test/fixtures/zines.yml +0 -5
  353. data/test/migrations/broken/100_migration_that_raises_exception.rb +0 -10
  354. data/test/migrations/decimal/1_give_me_big_numbers.rb +0 -15
  355. data/test/migrations/duplicate/1_people_have_last_names.rb +0 -9
  356. data/test/migrations/duplicate/2_we_need_reminders.rb +0 -12
  357. data/test/migrations/duplicate/3_foo.rb +0 -7
  358. data/test/migrations/duplicate/3_innocent_jointable.rb +0 -12
  359. data/test/migrations/duplicate_names/20080507052938_chunky.rb +0 -7
  360. data/test/migrations/duplicate_names/20080507053028_chunky.rb +0 -7
  361. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +0 -12
  362. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +0 -9
  363. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +0 -12
  364. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +0 -9
  365. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +0 -8
  366. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +0 -12
  367. data/test/migrations/missing/1000_people_have_middle_names.rb +0 -9
  368. data/test/migrations/missing/1_people_have_last_names.rb +0 -9
  369. data/test/migrations/missing/3_we_need_reminders.rb +0 -12
  370. data/test/migrations/missing/4_innocent_jointable.rb +0 -12
  371. data/test/migrations/valid/1_people_have_last_names.rb +0 -9
  372. data/test/migrations/valid/2_we_need_reminders.rb +0 -12
  373. data/test/migrations/valid/3_innocent_jointable.rb +0 -12
  374. data/test/models/author.rb +0 -151
  375. data/test/models/auto_id.rb +0 -4
  376. data/test/models/binary.rb +0 -2
  377. data/test/models/bird.rb +0 -9
  378. data/test/models/book.rb +0 -4
  379. data/test/models/categorization.rb +0 -5
  380. data/test/models/category.rb +0 -34
  381. data/test/models/citation.rb +0 -6
  382. data/test/models/club.rb +0 -13
  383. data/test/models/column_name.rb +0 -3
  384. data/test/models/comment.rb +0 -29
  385. data/test/models/company.rb +0 -173
  386. data/test/models/company_in_module.rb +0 -78
  387. data/test/models/computer.rb +0 -3
  388. data/test/models/contact.rb +0 -16
  389. data/test/models/contract.rb +0 -5
  390. data/test/models/course.rb +0 -3
  391. data/test/models/customer.rb +0 -73
  392. data/test/models/default.rb +0 -2
  393. data/test/models/developer.rb +0 -101
  394. data/test/models/edge.rb +0 -5
  395. data/test/models/entrant.rb +0 -3
  396. data/test/models/essay.rb +0 -3
  397. data/test/models/event.rb +0 -3
  398. data/test/models/event_author.rb +0 -8
  399. data/test/models/face.rb +0 -7
  400. data/test/models/guid.rb +0 -2
  401. data/test/models/interest.rb +0 -5
  402. data/test/models/invoice.rb +0 -4
  403. data/test/models/item.rb +0 -7
  404. data/test/models/job.rb +0 -5
  405. data/test/models/joke.rb +0 -3
  406. data/test/models/keyboard.rb +0 -3
  407. data/test/models/legacy_thing.rb +0 -3
  408. data/test/models/line_item.rb +0 -3
  409. data/test/models/man.rb +0 -9
  410. data/test/models/matey.rb +0 -4
  411. data/test/models/member.rb +0 -12
  412. data/test/models/member_detail.rb +0 -5
  413. data/test/models/member_type.rb +0 -3
  414. data/test/models/membership.rb +0 -9
  415. data/test/models/minimalistic.rb +0 -2
  416. data/test/models/mixed_case_monkey.rb +0 -3
  417. data/test/models/movie.rb +0 -5
  418. data/test/models/order.rb +0 -4
  419. data/test/models/organization.rb +0 -6
  420. data/test/models/owner.rb +0 -5
  421. data/test/models/parrot.rb +0 -22
  422. data/test/models/person.rb +0 -16
  423. data/test/models/pet.rb +0 -5
  424. data/test/models/pirate.rb +0 -80
  425. data/test/models/polymorphic_design.rb +0 -3
  426. data/test/models/polymorphic_price.rb +0 -3
  427. data/test/models/post.rb +0 -102
  428. data/test/models/price_estimate.rb +0 -3
  429. data/test/models/project.rb +0 -30
  430. data/test/models/reader.rb +0 -4
  431. data/test/models/reference.rb +0 -4
  432. data/test/models/reply.rb +0 -46
  433. data/test/models/ship.rb +0 -19
  434. data/test/models/ship_part.rb +0 -7
  435. data/test/models/sponsor.rb +0 -4
  436. data/test/models/subject.rb +0 -4
  437. data/test/models/subscriber.rb +0 -8
  438. data/test/models/subscription.rb +0 -4
  439. data/test/models/tag.rb +0 -7
  440. data/test/models/tagging.rb +0 -10
  441. data/test/models/task.rb +0 -3
  442. data/test/models/tee.rb +0 -4
  443. data/test/models/tie.rb +0 -4
  444. data/test/models/topic.rb +0 -80
  445. data/test/models/toy.rb +0 -6
  446. data/test/models/treasure.rb +0 -8
  447. data/test/models/vertex.rb +0 -9
  448. data/test/models/warehouse_thing.rb +0 -5
  449. data/test/models/zine.rb +0 -3
  450. data/test/schema/mysql_specific_schema.rb +0 -31
  451. data/test/schema/postgresql_specific_schema.rb +0 -114
  452. data/test/schema/schema.rb +0 -550
  453. data/test/schema/schema2.rb +0 -6
  454. data/test/schema/sqlite_specific_schema.rb +0 -25
@@ -0,0 +1,221 @@
1
+ require 'active_support/concern'
2
+
3
+ module ActiveRecord
4
+ module AttributeAssignment
5
+ extend ActiveSupport::Concern
6
+ include ActiveModel::MassAssignmentSecurity
7
+
8
+ module ClassMethods
9
+ private
10
+
11
+ # The primary key and inheritance column can never be set by mass-assignment for security reasons.
12
+ def attributes_protected_by_default
13
+ default = [ primary_key, inheritance_column ]
14
+ default << 'id' unless primary_key.eql? 'id'
15
+ default
16
+ end
17
+ end
18
+
19
+ # Allows you to set all the attributes at once by passing in a hash with keys
20
+ # matching the attribute names (which again matches the column names).
21
+ #
22
+ # If any attributes are protected by either +attr_protected+ or
23
+ # +attr_accessible+ then only settable attributes will be assigned.
24
+ #
25
+ # class User < ActiveRecord::Base
26
+ # attr_protected :is_admin
27
+ # end
28
+ #
29
+ # user = User.new
30
+ # user.attributes = { :username => 'Phusion', :is_admin => true }
31
+ # user.username # => "Phusion"
32
+ # user.is_admin? # => false
33
+ def attributes=(new_attributes)
34
+ return unless new_attributes.is_a?(Hash)
35
+
36
+ assign_attributes(new_attributes)
37
+ end
38
+
39
+ # Allows you to set all the attributes for a particular mass-assignment
40
+ # security role by passing in a hash of attributes with keys matching
41
+ # the attribute names (which again matches the column names) and the role
42
+ # name using the :as option.
43
+ #
44
+ # To bypass mass-assignment security you can use the :without_protection => true
45
+ # option.
46
+ #
47
+ # class User < ActiveRecord::Base
48
+ # attr_accessible :name
49
+ # attr_accessible :name, :is_admin, :as => :admin
50
+ # end
51
+ #
52
+ # user = User.new
53
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true })
54
+ # user.name # => "Josh"
55
+ # user.is_admin? # => false
56
+ #
57
+ # user = User.new
58
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
59
+ # user.name # => "Josh"
60
+ # user.is_admin? # => true
61
+ #
62
+ # user = User.new
63
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
64
+ # user.name # => "Josh"
65
+ # user.is_admin? # => true
66
+ def assign_attributes(new_attributes, options = {})
67
+ return if new_attributes.blank?
68
+
69
+ attributes = new_attributes.stringify_keys
70
+ multi_parameter_attributes = []
71
+ nested_parameter_attributes = []
72
+ @mass_assignment_options = options
73
+
74
+ unless options[:without_protection]
75
+ attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
76
+ end
77
+
78
+ attributes.each do |k, v|
79
+ if k.include?("(")
80
+ multi_parameter_attributes << [ k, v ]
81
+ elsif respond_to?("#{k}=")
82
+ if v.is_a?(Hash)
83
+ nested_parameter_attributes << [ k, v ]
84
+ else
85
+ send("#{k}=", v)
86
+ end
87
+ else
88
+ raise(UnknownAttributeError, "unknown attribute: #{k}")
89
+ end
90
+ end
91
+
92
+ # assign any deferred nested attributes after the base attributes have been set
93
+ nested_parameter_attributes.each do |k,v|
94
+ send("#{k}=", v)
95
+ end
96
+
97
+ @mass_assignment_options = nil
98
+ assign_multiparameter_attributes(multi_parameter_attributes)
99
+ end
100
+
101
+ protected
102
+
103
+ def mass_assignment_options
104
+ @mass_assignment_options ||= {}
105
+ end
106
+
107
+ def mass_assignment_role
108
+ mass_assignment_options[:as] || :default
109
+ end
110
+
111
+ private
112
+
113
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
114
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
115
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
116
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
117
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
118
+ # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
119
+ # attribute will be set to nil.
120
+ def assign_multiparameter_attributes(pairs)
121
+ execute_callstack_for_multiparameter_attributes(
122
+ extract_callstack_for_multiparameter_attributes(pairs)
123
+ )
124
+ end
125
+
126
+ def instantiate_time_object(name, values)
127
+ if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
128
+ Time.zone.local(*values)
129
+ else
130
+ Time.time_with_datetime_fallback(self.class.default_timezone, *values)
131
+ end
132
+ end
133
+
134
+ def execute_callstack_for_multiparameter_attributes(callstack)
135
+ errors = []
136
+ callstack.each do |name, values_with_empty_parameters|
137
+ begin
138
+ send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
139
+ rescue => ex
140
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
141
+ end
142
+ end
143
+ unless errors.empty?
144
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
145
+ end
146
+ end
147
+
148
+ def read_value_from_parameter(name, values_hash_from_param)
149
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
150
+ if values_hash_from_param.values.all?{|v|v.nil?}
151
+ nil
152
+ elsif klass == Time
153
+ read_time_parameter_value(name, values_hash_from_param)
154
+ elsif klass == Date
155
+ read_date_parameter_value(name, values_hash_from_param)
156
+ else
157
+ read_other_parameter_value(klass, name, values_hash_from_param)
158
+ end
159
+ end
160
+
161
+ def read_time_parameter_value(name, values_hash_from_param)
162
+ # If Date bits were not provided, error
163
+ raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
164
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
165
+ # If Date bits were provided but blank, then return nil
166
+ return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
167
+
168
+ set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
169
+ # If Time bits are not there, then default to 0
170
+ (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
171
+ instantiate_time_object(name, set_values)
172
+ end
173
+
174
+ def read_date_parameter_value(name, values_hash_from_param)
175
+ return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
176
+ set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[3]]
177
+ begin
178
+ Date.new(*set_values)
179
+ rescue ArgumentError # if Date.new raises an exception on an invalid date
180
+ instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
181
+ end
182
+ end
183
+
184
+ def read_other_parameter_value(klass, name, values_hash_from_param)
185
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
186
+ values = (1..max_position).collect do |position|
187
+ raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
188
+ values_hash_from_param[position]
189
+ end
190
+ klass.new(*values)
191
+ end
192
+
193
+ def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
194
+ [values_hash_from_param.keys.max,upper_cap].min
195
+ end
196
+
197
+ def extract_callstack_for_multiparameter_attributes(pairs)
198
+ attributes = { }
199
+
200
+ pairs.each do |pair|
201
+ multiparameter_name, value = pair
202
+ attribute_name = multiparameter_name.split("(").first
203
+ attributes[attribute_name] = {} unless attributes.include?(attribute_name)
204
+
205
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
206
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
207
+ end
208
+
209
+ attributes
210
+ end
211
+
212
+ def type_cast_attribute_value(multiparameter_name, value)
213
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
214
+ end
215
+
216
+ def find_parameter_position(multiparameter_name)
217
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
218
+ end
219
+
220
+ end
221
+ end
@@ -0,0 +1,31 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module BeforeTypeCast
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attribute_method_suffix "_before_type_cast"
8
+ end
9
+
10
+ def read_attribute_before_type_cast(attr_name)
11
+ @attributes[attr_name]
12
+ end
13
+
14
+ # Returns a hash of attributes before typecasting and deserialization.
15
+ def attributes_before_type_cast
16
+ @attributes
17
+ end
18
+
19
+ private
20
+
21
+ # Handle *_before_type_cast for method_missing.
22
+ def attribute_before_type_cast(attribute_name)
23
+ if attribute_name == 'id'
24
+ read_attribute_before_type_cast(self.class.primary_key)
25
+ else
26
+ read_attribute_before_type_cast(attribute_name)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/deprecation'
3
+
4
+ module ActiveRecord
5
+ module AttributeMethods
6
+ module DeprecatedUnderscoreRead
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ attribute_method_prefix "_"
11
+ end
12
+
13
+ module ClassMethods
14
+ protected
15
+
16
+ def define_method__attribute(attr_name)
17
+ # Do nothing, let it hit method missing instead.
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def _attribute(attr_name)
24
+ ActiveSupport::Deprecation.warn(
25
+ "You have called '_#{attr_name}'. This is deprecated. Please use " \
26
+ "either '#{attr_name}' or read_attribute('#{attr_name}')."
27
+ )
28
+ read_attribute(attr_name)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,111 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'active_support/core_ext/object/blank'
3
+
4
+ module ActiveRecord
5
+ module AttributeMethods
6
+ module Dirty
7
+ extend ActiveSupport::Concern
8
+ include ActiveModel::Dirty
9
+ include AttributeMethods::Write
10
+
11
+ included do
12
+ if self < ::ActiveRecord::Timestamp
13
+ raise "You cannot include Dirty after Timestamp"
14
+ end
15
+
16
+ class_attribute :partial_updates
17
+ self.partial_updates = true
18
+ end
19
+
20
+ # Attempts to +save+ the record and clears changed attributes if successful.
21
+ def save(*) #:nodoc:
22
+ if status = super
23
+ @previously_changed = changes
24
+ @changed_attributes.clear
25
+ elsif IdentityMap.enabled?
26
+ IdentityMap.remove(self)
27
+ end
28
+ status
29
+ end
30
+
31
+ # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
32
+ def save!(*) #:nodoc:
33
+ super.tap do
34
+ @previously_changed = changes
35
+ @changed_attributes.clear
36
+ end
37
+ rescue
38
+ IdentityMap.remove(self) if IdentityMap.enabled?
39
+ raise
40
+ end
41
+
42
+ # <tt>reload</tt> the record and clears changed attributes.
43
+ def reload(*) #:nodoc:
44
+ super.tap do
45
+ @previously_changed.clear
46
+ @changed_attributes.clear
47
+ end
48
+ end
49
+
50
+ private
51
+ # Wrap write_attribute to remember original attribute value.
52
+ def write_attribute(attr, value)
53
+ attr = attr.to_s
54
+
55
+ # The attribute already has an unsaved change.
56
+ if attribute_changed?(attr)
57
+ old = @changed_attributes[attr]
58
+ @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
59
+ else
60
+ old = clone_attribute_value(:read_attribute, attr)
61
+ # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
62
+ old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
63
+ @changed_attributes[attr] = old if _field_changed?(attr, old, value)
64
+ end
65
+
66
+ # Carry on.
67
+ super(attr, value)
68
+ end
69
+
70
+ def update(*)
71
+ if partial_updates?
72
+ # Serialized attributes should always be written in case they've been
73
+ # changed in place.
74
+ super(changed | (attributes.keys & self.class.serialized_attributes.keys))
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ def _field_changed?(attr, old, value)
81
+ if column = column_for_attribute(attr)
82
+ if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
83
+ changes_from_zero_to_string?(old, value))
84
+ value = nil
85
+ else
86
+ value = column.type_cast(value)
87
+ end
88
+ end
89
+
90
+ old != value
91
+ end
92
+
93
+ def clone_with_time_zone_conversion_attribute?(attr, old)
94
+ old.class.name == "Time" && time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
95
+ end
96
+
97
+ def changes_from_nil_to_empty_string?(column, old, value)
98
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
99
+ # Hence we don't record it as a change if the value changes from nil to ''.
100
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
101
+ # be typecast back to 0 (''.to_i => 0)
102
+ column.null && (old.nil? || old == 0) && value.blank?
103
+ end
104
+
105
+ def changes_from_zero_to_string?(old, value)
106
+ # For columns with old 0 and value non-empty string
107
+ old == 0 && value.is_a?(String) && value.present? && value != '0'
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,114 @@
1
+ module ActiveRecord
2
+ module AttributeMethods
3
+ module PrimaryKey
4
+ extend ActiveSupport::Concern
5
+
6
+ # Returns this record's primary key value wrapped in an Array if one is available
7
+ def to_key
8
+ key = self.id
9
+ [key] if key
10
+ end
11
+
12
+ # Returns the primary key value
13
+ def id
14
+ read_attribute(self.class.primary_key)
15
+ end
16
+
17
+ # Sets the primary key value
18
+ def id=(value)
19
+ write_attribute(self.class.primary_key, value)
20
+ end
21
+
22
+ # Queries the primary key value
23
+ def id?
24
+ query_attribute(self.class.primary_key)
25
+ end
26
+
27
+ module ClassMethods
28
+ def define_method_attribute(attr_name)
29
+ super
30
+
31
+ if attr_name == primary_key && attr_name != 'id'
32
+ generated_attribute_methods.send(:alias_method, :id, primary_key)
33
+ generated_external_attribute_methods.module_eval <<-CODE, __FILE__, __LINE__
34
+ def id(v, attributes, attributes_cache, attr_name)
35
+ attr_name = '#{primary_key}'
36
+ send(attr_name, attributes[attr_name], attributes, attributes_cache, attr_name)
37
+ end
38
+ CODE
39
+ end
40
+ end
41
+
42
+ def dangerous_attribute_method?(method_name)
43
+ super && !['id', 'id=', 'id?'].include?(method_name)
44
+ end
45
+
46
+ # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
47
+ # primary_key_prefix_type setting, though.
48
+ def primary_key
49
+ @primary_key = reset_primary_key unless defined? @primary_key
50
+ @primary_key
51
+ end
52
+
53
+ # Returns a quoted version of the primary key name, used to construct SQL statements.
54
+ def quoted_primary_key
55
+ @quoted_primary_key ||= connection.quote_column_name(primary_key)
56
+ end
57
+
58
+ def reset_primary_key #:nodoc:
59
+ if self == base_class
60
+ self.primary_key = get_primary_key(base_class.name)
61
+ else
62
+ self.primary_key = base_class.primary_key
63
+ end
64
+ end
65
+
66
+ def get_primary_key(base_name) #:nodoc:
67
+ return 'id' unless base_name && !base_name.blank?
68
+
69
+ case primary_key_prefix_type
70
+ when :table_name
71
+ base_name.foreign_key(false)
72
+ when :table_name_with_underscore
73
+ base_name.foreign_key
74
+ else
75
+ if ActiveRecord::Base != self && table_exists?
76
+ connection.schema_cache.primary_keys[table_name]
77
+ else
78
+ 'id'
79
+ end
80
+ end
81
+ end
82
+
83
+ def original_primary_key #:nodoc:
84
+ deprecated_original_property_getter :primary_key
85
+ end
86
+
87
+ # Sets the name of the primary key column.
88
+ #
89
+ # class Project < ActiveRecord::Base
90
+ # self.primary_key = "sysid"
91
+ # end
92
+ #
93
+ # You can also define the primary_key method yourself:
94
+ #
95
+ # class Project < ActiveRecord::Base
96
+ # def self.primary_key
97
+ # "foo_" + super
98
+ # end
99
+ # end
100
+ # Project.primary_key # => "foo_id"
101
+ def primary_key=(value)
102
+ @original_primary_key = @primary_key if defined?(@primary_key)
103
+ @primary_key = value && value.to_s
104
+ @quoted_primary_key = nil
105
+ end
106
+
107
+ def set_primary_key(value = nil, &block) #:nodoc:
108
+ deprecated_property_setter :primary_key, value, block
109
+ @quoted_primary_key = nil
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,39 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module ActiveRecord
4
+ module AttributeMethods
5
+ module Query
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attribute_method_suffix "?"
10
+ end
11
+
12
+ def query_attribute(attr_name)
13
+ unless value = read_attribute(attr_name)
14
+ false
15
+ else
16
+ column = self.class.columns_hash[attr_name]
17
+ if column.nil?
18
+ if Numeric === value || value !~ /[^0-9]/
19
+ !value.to_i.zero?
20
+ else
21
+ return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
22
+ !value.blank?
23
+ end
24
+ elsif column.number?
25
+ !value.zero?
26
+ else
27
+ !value.blank?
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+ # Handle *? for method_missing.
34
+ def attribute?(attribute_name)
35
+ query_attribute(attribute_name)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,136 @@
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
+ cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
10
+ self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
11
+ end
12
+
13
+ module ClassMethods
14
+ # +cache_attributes+ allows you to declare which converted attribute values should
15
+ # be cached. Usually caching only pays off for attributes with expensive conversion
16
+ # methods, like time related columns (e.g. +created_at+, +updated_at+).
17
+ def cache_attributes(*attribute_names)
18
+ cached_attributes.merge attribute_names.map { |attr| attr.to_s }
19
+ end
20
+
21
+ # Returns the attributes which are cached. By default time related columns
22
+ # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
23
+ def cached_attributes
24
+ @cached_attributes ||= columns.select { |c| cacheable_column?(c) }.map { |col| col.name }.to_set
25
+ end
26
+
27
+ # Returns +true+ if the provided attribute is being cached.
28
+ def cache_attribute?(attr_name)
29
+ cached_attributes.include?(attr_name)
30
+ end
31
+
32
+ def undefine_attribute_methods
33
+ generated_external_attribute_methods.module_eval do
34
+ instance_methods.each { |m| undef_method(m) }
35
+ end
36
+
37
+ super
38
+ end
39
+
40
+ def type_cast_attribute(attr_name, attributes, cache = {}) #:nodoc:
41
+ return unless attr_name
42
+ attr_name = attr_name.to_s
43
+
44
+ if generated_external_attribute_methods.method_defined?(attr_name)
45
+ if attributes.has_key?(attr_name) || attr_name == 'id'
46
+ generated_external_attribute_methods.send(attr_name, attributes[attr_name], attributes, cache, attr_name)
47
+ end
48
+ elsif !attribute_methods_generated?
49
+ # If we haven't generated the caster methods yet, do that and
50
+ # then try again
51
+ define_attribute_methods
52
+ type_cast_attribute(attr_name, attributes, cache)
53
+ else
54
+ # If we get here, the attribute has no associated DB column, so
55
+ # just return it verbatim.
56
+ attributes[attr_name]
57
+ end
58
+ end
59
+
60
+ protected
61
+ # We want to generate the methods via module_eval rather than define_method,
62
+ # because define_method is slower on dispatch and uses more memory (because it
63
+ # creates a closure).
64
+ #
65
+ # But sometimes the database might return columns with characters that are not
66
+ # allowed in normal method names (like 'my_column(omg)'. So to work around this
67
+ # we first define with the __temp__ identifier, and then use alias method to
68
+ # rename it to what we want.
69
+ def define_method_attribute(attr_name)
70
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
71
+ def __temp__
72
+ #{internal_attribute_access_code(attr_name, attribute_cast_code(attr_name))}
73
+ end
74
+ alias_method '#{attr_name}', :__temp__
75
+ undef_method :__temp__
76
+ STR
77
+ end
78
+
79
+ private
80
+
81
+ def define_external_attribute_method(attr_name)
82
+ generated_external_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
83
+ def __temp__(v, attributes, attributes_cache, attr_name)
84
+ #{external_attribute_access_code(attr_name, attribute_cast_code(attr_name))}
85
+ end
86
+ alias_method '#{attr_name}', :__temp__
87
+ undef_method :__temp__
88
+ STR
89
+ end
90
+
91
+ def cacheable_column?(column)
92
+ attribute_types_cached_by_default.include?(column.type)
93
+ end
94
+
95
+ def internal_attribute_access_code(attr_name, cast_code)
96
+ access_code = "(v=@attributes[attr_name]) && #{cast_code}"
97
+
98
+ unless attr_name == primary_key
99
+ access_code.insert(0, "missing_attribute(attr_name, caller) unless @attributes.has_key?(attr_name); ")
100
+ end
101
+
102
+ if cache_attribute?(attr_name)
103
+ access_code = "@attributes_cache[attr_name] ||= (#{access_code})"
104
+ end
105
+
106
+ "attr_name = '#{attr_name}'; #{access_code}"
107
+ end
108
+
109
+ def external_attribute_access_code(attr_name, cast_code)
110
+ access_code = "v && #{cast_code}"
111
+
112
+ if cache_attribute?(attr_name)
113
+ access_code = "attributes_cache[attr_name] ||= (#{access_code})"
114
+ end
115
+
116
+ access_code
117
+ end
118
+
119
+ def attribute_cast_code(attr_name)
120
+ columns_hash[attr_name].type_cast_code('v')
121
+ end
122
+ end
123
+
124
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
125
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
126
+ def read_attribute(attr_name)
127
+ self.class.type_cast_attribute(attr_name, @attributes, @attributes_cache)
128
+ end
129
+
130
+ private
131
+ def attribute(attribute_name)
132
+ read_attribute(attribute_name)
133
+ end
134
+ end
135
+ end
136
+ end