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,123 @@
1
+ module ActiveRecord
2
+ # = Active Record Counter Cache
3
+ module CounterCache
4
+ # Resets one or more counter caches to their correct value using an SQL
5
+ # count query. This is useful when adding new counter caches, or if the
6
+ # counter has been corrupted or modified directly by SQL.
7
+ #
8
+ # ==== Parameters
9
+ #
10
+ # * +id+ - The id of the object you wish to reset a counter on.
11
+ # * +counters+ - One or more counter names to reset
12
+ #
13
+ # ==== Examples
14
+ #
15
+ # # For Post with id #1 records reset the comments_count
16
+ # Post.reset_counters(1, :comments)
17
+ def reset_counters(id, *counters)
18
+ object = find(id)
19
+ counters.each do |association|
20
+ has_many_association = reflect_on_association(association.to_sym)
21
+
22
+ if has_many_association.options[:as]
23
+ has_many_association.options[:as].to_s.classify
24
+ else
25
+ self.name
26
+ end
27
+
28
+ if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection
29
+ has_many_association = has_many_association.through_reflection
30
+ end
31
+
32
+ foreign_key = has_many_association.foreign_key.to_s
33
+ child_class = has_many_association.klass
34
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
35
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
36
+ counter_name = reflection.counter_cache_column
37
+
38
+ stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
39
+ arel_table[counter_name] => object.send(association).count
40
+ })
41
+ connection.update stmt
42
+ end
43
+ return true
44
+ end
45
+
46
+ # A generic "counter updater" implementation, intended primarily to be
47
+ # used by increment_counter and decrement_counter, but which may also
48
+ # be useful on its own. It simply does a direct SQL update for the record
49
+ # with the given ID, altering the given hash of counters by the amount
50
+ # given by the corresponding value:
51
+ #
52
+ # ==== Parameters
53
+ #
54
+ # * +id+ - The id of the object you wish to update a counter on or an Array of ids.
55
+ # * +counters+ - An Array of Hashes containing the names of the fields
56
+ # to update as keys and the amount to update the field by as values.
57
+ #
58
+ # ==== Examples
59
+ #
60
+ # # For the Post with id of 5, decrement the comment_count by 1, and
61
+ # # increment the action_count by 1
62
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
63
+ # # Executes the following SQL:
64
+ # # UPDATE posts
65
+ # # SET comment_count = COALESCE(comment_count, 0) - 1,
66
+ # # action_count = COALESCE(action_count, 0) + 1
67
+ # # WHERE id = 5
68
+ #
69
+ # # For the Posts with id of 10 and 15, increment the comment_count by 1
70
+ # Post.update_counters [10, 15], :comment_count => 1
71
+ # # Executes the following SQL:
72
+ # # UPDATE posts
73
+ # # SET comment_count = COALESCE(comment_count, 0) + 1
74
+ # # WHERE id IN (10, 15)
75
+ def update_counters(id, counters)
76
+ updates = counters.map do |counter_name, value|
77
+ operator = value < 0 ? '-' : '+'
78
+ quoted_column = connection.quote_column_name(counter_name)
79
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
80
+ end
81
+
82
+ IdentityMap.remove_by_id(symbolized_base_class, id) if IdentityMap.enabled?
83
+
84
+ update_all(updates.join(', '), primary_key => id )
85
+ end
86
+
87
+ # Increment a number field by one, usually representing a count.
88
+ #
89
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
90
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
91
+ # shown it would have to run an SQL query to find how many posts and comments there are.
92
+ #
93
+ # ==== Parameters
94
+ #
95
+ # * +counter_name+ - The name of the field that should be incremented.
96
+ # * +id+ - The id of the object that should be incremented.
97
+ #
98
+ # ==== Examples
99
+ #
100
+ # # Increment the post_count column for the record with an id of 5
101
+ # DiscussionBoard.increment_counter(:post_count, 5)
102
+ def increment_counter(counter_name, id)
103
+ update_counters(id, counter_name => 1)
104
+ end
105
+
106
+ # Decrement a number field by one, usually representing a count.
107
+ #
108
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
109
+ #
110
+ # ==== Parameters
111
+ #
112
+ # * +counter_name+ - The name of the field that should be decremented.
113
+ # * +id+ - The id of the object that should be decremented.
114
+ #
115
+ # ==== Examples
116
+ #
117
+ # # Decrement the post_count column for the record with an id of 5
118
+ # DiscussionBoard.decrement_counter(:post_count, 5)
119
+ def decrement_counter(counter_name, id)
120
+ update_counters(id, counter_name => -1)
121
+ end
122
+ end
123
+ end
@@ -1,41 +1,68 @@
1
1
  module ActiveRecord
2
+
3
+ # = Active Record Dynamic Finder Match
4
+ #
5
+ # Refer to ActiveRecord::Base documentation for Dynamic attribute-based finders for detailed info
6
+ #
2
7
  class DynamicFinderMatch
3
8
  def self.match(method)
4
- df_match = self.new(method)
5
- df_match.finder ? df_match : nil
6
- end
9
+ finder = :first
10
+ bang = false
11
+ instantiator = nil
7
12
 
8
- def initialize(method)
9
- @finder = :first
10
13
  case method.to_s
11
- when /^find_(all_by|last_by|by)_([_a-zA-Z]\w*)$/
12
- @finder = :last if $1 == 'last_by'
13
- @finder = :all if $1 == 'all_by'
14
+ when /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/
15
+ finder = :last if $1 == 'last_'
16
+ finder = :all if $1 == 'all_'
14
17
  names = $2
15
18
  when /^find_by_([_a-zA-Z]\w*)\!$/
16
- @bang = true
19
+ bang = true
20
+ names = $1
21
+ when /^find_or_create_by_([_a-zA-Z]\w*)\!$/
22
+ bang = true
23
+ instantiator = :create
17
24
  names = $1
18
25
  when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
19
- @instantiator = $1 == 'initialize' ? :new : :create
26
+ instantiator = $1 == 'initialize' ? :new : :create
20
27
  names = $2
21
28
  else
22
- @finder = nil
29
+ return nil
23
30
  end
24
- @attribute_names = names && names.split('_and_')
31
+
32
+ new(finder, instantiator, bang, names.split('_and_'))
33
+ end
34
+
35
+ def initialize(finder, instantiator, bang, attribute_names)
36
+ @finder = finder
37
+ @instantiator = instantiator
38
+ @bang = bang
39
+ @attribute_names = attribute_names
25
40
  end
26
41
 
27
42
  attr_reader :finder, :attribute_names, :instantiator
28
43
 
29
44
  def finder?
30
- !@finder.nil? && @instantiator.nil?
45
+ @finder && !@instantiator
31
46
  end
32
47
 
33
48
  def instantiator?
34
- @finder == :first && !@instantiator.nil?
49
+ @finder == :first && @instantiator
50
+ end
51
+
52
+ def creator?
53
+ @finder == :first && @instantiator == :create
35
54
  end
36
55
 
37
56
  def bang?
38
57
  @bang
39
58
  end
59
+
60
+ def save_record?
61
+ @instantiator == :create
62
+ end
63
+
64
+ def save_method
65
+ bang? ? :save! : :save
66
+ end
40
67
  end
41
68
  end
@@ -0,0 +1,84 @@
1
+ module ActiveRecord
2
+ module DynamicMatchers
3
+ def respond_to?(method_id, include_private = false)
4
+ if match = DynamicFinderMatch.match(method_id)
5
+ return true if all_attributes_exists?(match.attribute_names)
6
+ elsif match = DynamicScopeMatch.match(method_id)
7
+ return true if all_attributes_exists?(match.attribute_names)
8
+ end
9
+
10
+ super
11
+ end
12
+
13
+ private
14
+
15
+ # Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
16
+ # <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
17
+ # section at the top of this file for more detailed information.
18
+ #
19
+ # It's even possible to use all the additional parameters to +find+. For example, the
20
+ # full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
21
+ #
22
+ # Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
23
+ # is first invoked, so that future attempts to use it do not run through method_missing.
24
+ def method_missing(method_id, *arguments, &block)
25
+ if match = (DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id))
26
+ attribute_names = match.attribute_names
27
+ super unless all_attributes_exists?(attribute_names)
28
+ if !(match.is_a?(DynamicFinderMatch) && match.instantiator? && arguments.first.is_a?(Hash)) && arguments.size < attribute_names.size
29
+ method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'"
30
+ backtrace = [method_trace] + caller
31
+ raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace
32
+ end
33
+ if match.respond_to?(:scope?) && match.scope?
34
+ self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
35
+ def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
36
+ attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)]
37
+ #
38
+ scoped(:conditions => attributes) # scoped(:conditions => attributes)
39
+ end # end
40
+ METHOD
41
+ send(method_id, *arguments)
42
+ elsif match.finder?
43
+ options = if arguments.length > attribute_names.size
44
+ arguments.extract_options!
45
+ else
46
+ {}
47
+ end
48
+
49
+ relation = options.any? ? scoped(options) : scoped
50
+ relation.send :find_by_attributes, match, attribute_names, *arguments, &block
51
+ elsif match.instantiator?
52
+ scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
53
+ end
54
+ else
55
+ super
56
+ end
57
+ end
58
+
59
+ # Similar in purpose to +expand_hash_conditions_for_aggregates+.
60
+ def expand_attribute_names_for_aggregates(attribute_names)
61
+ attribute_names.map { |attribute_name|
62
+ unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
63
+ aggregate_mapping(aggregation).map do |field_attr, _|
64
+ field_attr.to_sym
65
+ end
66
+ else
67
+ attribute_name.to_sym
68
+ end
69
+ }.flatten
70
+ end
71
+
72
+ def all_attributes_exists?(attribute_names)
73
+ (expand_attribute_names_for_aggregates(attribute_names) -
74
+ column_methods_hash.keys).empty?
75
+ end
76
+
77
+ def aggregate_mapping(reflection)
78
+ mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
79
+ mapping.first.is_a?(Array) ? mapping : [mapping]
80
+ end
81
+
82
+
83
+ end
84
+ end
@@ -1,25 +1,23 @@
1
1
  module ActiveRecord
2
+
3
+ # = Active Record Dynamic Scope Match
4
+ #
5
+ # Provides dynamic attribute-based scopes such as <tt>scoped_by_price(4.99)</tt>
6
+ # if, for example, the <tt>Product</tt> has an attribute with that name. You can
7
+ # chain more <tt>scoped_by_* </tt> methods after the other. It acts like a named
8
+ # scope except that it's dynamic.
2
9
  class DynamicScopeMatch
3
10
  def self.match(method)
4
- ds_match = self.new(method)
5
- ds_match.scope ? ds_match : nil
11
+ return unless method.to_s =~ /^scoped_by_([_a-zA-Z]\w*)$/
12
+ new(true, $1 && $1.split('_and_'))
6
13
  end
7
14
 
8
- def initialize(method)
9
- @scope = true
10
- case method.to_s
11
- when /^scoped_by_([_a-zA-Z]\w*)$/
12
- names = $1
13
- else
14
- @scope = nil
15
- end
16
- @attribute_names = names && names.split('_and_')
15
+ def initialize(scope, attribute_names)
16
+ @scope = scope
17
+ @attribute_names = attribute_names
17
18
  end
18
19
 
19
20
  attr_reader :scope, :attribute_names
20
-
21
- def scope?
22
- !@scope.nil?
23
- end
21
+ alias :scope? :scope
24
22
  end
25
23
  end
@@ -0,0 +1,195 @@
1
+ module ActiveRecord
2
+
3
+ # = Active Record Errors
4
+ #
5
+ # Generic Active Record exception class.
6
+ class ActiveRecordError < StandardError
7
+ end
8
+
9
+ # Raised when the single-table inheritance mechanism fails to locate the subclass
10
+ # (for example due to improper usage of column that +inheritance_column+ points to).
11
+ class SubclassNotFound < ActiveRecordError #:nodoc:
12
+ end
13
+
14
+ # Raised when an object assigned to an association has an incorrect type.
15
+ #
16
+ # class Ticket < ActiveRecord::Base
17
+ # has_many :patches
18
+ # end
19
+ #
20
+ # class Patch < ActiveRecord::Base
21
+ # belongs_to :ticket
22
+ # end
23
+ #
24
+ # # Comments are not patches, this assignment raises AssociationTypeMismatch.
25
+ # @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
26
+ class AssociationTypeMismatch < ActiveRecordError
27
+ end
28
+
29
+ # Raised when unserialized object's type mismatches one specified for serializable field.
30
+ class SerializationTypeMismatch < ActiveRecordError
31
+ end
32
+
33
+ # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt>
34
+ # misses adapter field).
35
+ class AdapterNotSpecified < ActiveRecordError
36
+ end
37
+
38
+ # Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
39
+ class AdapterNotFound < ActiveRecordError
40
+ end
41
+
42
+ # Raised when connection to the database could not been established (for example when <tt>connection=</tt>
43
+ # is given a nil object).
44
+ class ConnectionNotEstablished < ActiveRecordError
45
+ end
46
+
47
+ # Raised when Active Record cannot find record by given id or set of ids.
48
+ class RecordNotFound < ActiveRecordError
49
+ end
50
+
51
+ # Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
52
+ # saved because record is invalid.
53
+ class RecordNotSaved < ActiveRecordError
54
+ end
55
+
56
+ # Raised when SQL statement cannot be executed by the database (for example, it's often the case for
57
+ # MySQL when Ruby driver used is too old).
58
+ class StatementInvalid < ActiveRecordError
59
+ end
60
+
61
+ # Raised when SQL statement is invalid and the application gets a blank result.
62
+ class ThrowResult < ActiveRecordError
63
+ end
64
+
65
+ # Parent class for all specific exceptions which wrap database driver exceptions
66
+ # provides access to the original exception also.
67
+ class WrappedDatabaseException < StatementInvalid
68
+ attr_reader :original_exception
69
+
70
+ def initialize(message, original_exception)
71
+ super(message)
72
+ @original_exception = original_exception
73
+ end
74
+ end
75
+
76
+ # Raised when a record cannot be inserted because it would violate a uniqueness constraint.
77
+ class RecordNotUnique < WrappedDatabaseException
78
+ end
79
+
80
+ # Raised when a record cannot be inserted or updated because it references a non-existent record.
81
+ class InvalidForeignKey < WrappedDatabaseException
82
+ end
83
+
84
+ # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example,
85
+ # when using +find+ method)
86
+ # does not match number of expected variables.
87
+ #
88
+ # For example, in
89
+ #
90
+ # Location.where("lat = ? AND lng = ?", 53.7362)
91
+ #
92
+ # two placeholders are given but only one variable to fill them.
93
+ class PreparedStatementInvalid < ActiveRecordError
94
+ end
95
+
96
+ # Raised on attempt to save stale record. Record is stale when it's being saved in another query after
97
+ # instantiation, for example, when two users edit the same wiki page and one starts editing and saves
98
+ # the page before the other.
99
+ #
100
+ # Read more about optimistic locking in ActiveRecord::Locking module RDoc.
101
+ class StaleObjectError < ActiveRecordError
102
+ attr_reader :record, :attempted_action
103
+
104
+ def initialize(record, attempted_action)
105
+ @record = record
106
+ @attempted_action = attempted_action
107
+ end
108
+
109
+ def message
110
+ "Attempted to #{attempted_action} a stale object: #{record.class.name}"
111
+ end
112
+ end
113
+
114
+ # Raised when association is being configured improperly or
115
+ # user tries to use offset and limit together with has_many or has_and_belongs_to_many associations.
116
+ class ConfigurationError < ActiveRecordError
117
+ end
118
+
119
+ # Raised on attempt to update record that is instantiated as read only.
120
+ class ReadOnlyRecord < ActiveRecordError
121
+ end
122
+
123
+ # ActiveRecord::Transactions::ClassMethods.transaction uses this exception
124
+ # to distinguish a deliberate rollback from other exceptional situations.
125
+ # Normally, raising an exception will cause the +transaction+ method to rollback
126
+ # the database transaction *and* pass on the exception. But if you raise an
127
+ # ActiveRecord::Rollback exception, then the database transaction will be rolled back,
128
+ # without passing on the exception.
129
+ #
130
+ # For example, you could do this in your controller to rollback a transaction:
131
+ #
132
+ # class BooksController < ActionController::Base
133
+ # def create
134
+ # Book.transaction do
135
+ # book = Book.new(params[:book])
136
+ # book.save!
137
+ # if today_is_friday?
138
+ # # The system must fail on Friday so that our support department
139
+ # # won't be out of job. We silently rollback this transaction
140
+ # # without telling the user.
141
+ # raise ActiveRecord::Rollback, "Call tech support!"
142
+ # end
143
+ # end
144
+ # # ActiveRecord::Rollback is the only exception that won't be passed on
145
+ # # by ActiveRecord::Base.transaction, so this line will still be reached
146
+ # # even on Friday.
147
+ # redirect_to root_url
148
+ # end
149
+ # end
150
+ class Rollback < ActiveRecordError
151
+ end
152
+
153
+ # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
154
+ class DangerousAttributeError < ActiveRecordError
155
+ end
156
+
157
+ # Raised when unknown attributes are supplied via mass assignment.
158
+ class UnknownAttributeError < NoMethodError
159
+ end
160
+
161
+ # Raised when an error occurred while doing a mass assignment to an attribute through the
162
+ # <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
163
+ # offending attribute.
164
+ class AttributeAssignmentError < ActiveRecordError
165
+ attr_reader :exception, :attribute
166
+ def initialize(message, exception, attribute)
167
+ @exception = exception
168
+ @attribute = attribute
169
+ @message = message
170
+ end
171
+ end
172
+
173
+ # Raised when there are multiple errors while doing a mass assignment through the +attributes+
174
+ # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
175
+ # objects, each corresponding to the error while assigning to an attribute.
176
+ class MultiparameterAssignmentErrors < ActiveRecordError
177
+ attr_reader :errors
178
+ def initialize(errors)
179
+ @errors = errors
180
+ end
181
+ end
182
+
183
+ # Raised when a primary key is needed, but there is not one specified in the schema or model.
184
+ class UnknownPrimaryKey < ActiveRecordError
185
+ attr_reader :model
186
+
187
+ def initialize(model)
188
+ @model = model
189
+ end
190
+
191
+ def message
192
+ "Unknown primary key for table #{model.table_name} in model #{model}."
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,86 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
3
+ module ActiveRecord
4
+ module Explain
5
+ def self.extended(base)
6
+ base.class_eval do
7
+ # If a query takes longer than these many seconds we log its query plan
8
+ # automatically. nil disables this feature.
9
+ class_attribute :auto_explain_threshold_in_seconds, :instance_writer => false
10
+ self.auto_explain_threshold_in_seconds = nil
11
+ end
12
+ end
13
+
14
+ # If the database adapter supports explain and auto explain is enabled,
15
+ # this method triggers EXPLAIN logging for the queries triggered by the
16
+ # block if it takes more than the threshold as a whole. That is, the
17
+ # threshold is not checked against each individual query, but against the
18
+ # duration of the entire block. This approach is convenient for relations.
19
+
20
+ #
21
+ # The available_queries_for_explain thread variable collects the queries
22
+ # to be explained. If the value is nil, it means queries are not being
23
+ # currently collected. A false value indicates collecting is turned
24
+ # off. Otherwise it is an array of queries.
25
+ def logging_query_plan # :nodoc:
26
+ return yield unless logger
27
+
28
+ threshold = auto_explain_threshold_in_seconds
29
+ current = Thread.current
30
+ if connection.supports_explain? && threshold && current[:available_queries_for_explain].nil?
31
+ begin
32
+ queries = current[:available_queries_for_explain] = []
33
+ start = Time.now
34
+ result = yield
35
+ logger.warn(exec_explain(queries)) if Time.now - start > threshold
36
+ result
37
+ ensure
38
+ current[:available_queries_for_explain] = nil
39
+ end
40
+ else
41
+ yield
42
+ end
43
+ end
44
+
45
+ # Relation#explain needs to be able to collect the queries regardless of
46
+ # whether auto explain is enabled. This method serves that purpose.
47
+ def collecting_queries_for_explain # :nodoc:
48
+ current = Thread.current
49
+ original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
50
+ return yield, current[:available_queries_for_explain]
51
+ ensure
52
+ # Note that the return value above does not depend on this assigment.
53
+ current[:available_queries_for_explain] = original
54
+ end
55
+
56
+ # Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
57
+ # Returns a formatted string ready to be logged.
58
+ def exec_explain(queries) # :nodoc:
59
+ queries && queries.map do |sql, bind|
60
+ [].tap do |msg|
61
+ msg << "EXPLAIN for: #{sql}"
62
+ unless bind.empty?
63
+ bind_msg = bind.map {|col, val| [col.name, val]}.inspect
64
+ msg.last << " #{bind_msg}"
65
+ end
66
+ msg << connection.explain(sql, bind)
67
+ end.join("\n")
68
+ end.join("\n")
69
+ end
70
+
71
+ # Silences automatic EXPLAIN logging for the duration of the block.
72
+ #
73
+ # This has high priority, no EXPLAINs will be run even if downwards
74
+ # the threshold is set to 0.
75
+ #
76
+ # As the name of the method suggests this only applies to automatic
77
+ # EXPLAINs, manual calls to +ActiveRecord::Relation#explain+ run.
78
+ def silence_auto_explain
79
+ current = Thread.current
80
+ original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false
81
+ yield
82
+ ensure
83
+ current[:available_queries_for_explain] = original
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,25 @@
1
+ require 'active_support/notifications'
2
+
3
+ module ActiveRecord
4
+ class ExplainSubscriber # :nodoc:
5
+ def call(*args)
6
+ if queries = Thread.current[:available_queries_for_explain]
7
+ payload = args.last
8
+ queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
9
+ end
10
+ end
11
+
12
+ # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
13
+ # our own EXPLAINs now matter how loopingly beautiful that would be.
14
+ #
15
+ # On the other hand, we want to monitor the performance of our real database
16
+ # queries, not the performance of the access to the query cache.
17
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
18
+ EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)\b/i
19
+ def ignore_payload?(payload)
20
+ payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS
21
+ end
22
+
23
+ ActiveSupport::Notifications.subscribe("sql.active_record", new)
24
+ end
25
+ end