activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -4,12 +4,20 @@ en:
4
4
  #created_at: "Created at"
5
5
  #updated_at: "Updated at"
6
6
 
7
+ # Default error messages
8
+ errors:
9
+ messages:
10
+ required: "must exist"
11
+ taken: "has already been taken"
12
+
7
13
  # Active Record models configuration
8
14
  activerecord:
9
15
  errors:
10
16
  messages:
11
- taken: "has already been taken"
12
17
  record_invalid: "Validation failed: %{errors}"
18
+ restrict_dependent_destroy:
19
+ has_one: "Cannot delete record because a dependent %{record} exists"
20
+ has_many: "Cannot delete record because dependent %{record} exist"
13
21
  # Append your own errors here or at the model/attributes scope.
14
22
 
15
23
  # You can define own errors for models or model attributes.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Locking
3
5
  # == What is Optimistic Locking
@@ -11,7 +13,7 @@ module ActiveRecord
11
13
  #
12
14
  # == Usage
13
15
  #
14
- # Active Records support optimistic locking if the field +lock_version+ is present. Each update to the
16
+ # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
15
17
  # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
16
18
  # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
17
19
  #
@@ -22,7 +24,7 @@ module ActiveRecord
22
24
  # p1.save
23
25
  #
24
26
  # p2.first_name = "should fail"
25
- # p2.save # Raises a ActiveRecord::StaleObjectError
27
+ # p2.save # Raises an ActiveRecord::StaleObjectError
26
28
  #
27
29
  # Optimistic locking will also check for stale data when objects are destroyed. Example:
28
30
  #
@@ -32,7 +34,7 @@ module ActiveRecord
32
34
  # p1.first_name = "Michael"
33
35
  # p1.save
34
36
  #
35
- # p2.destroy # Raises a ActiveRecord::StaleObjectError
37
+ # p2.destroy # Raises an ActiveRecord::StaleObjectError
36
38
  #
37
39
  # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
38
40
  # or otherwise apply the business logic needed to resolve the conflict.
@@ -40,17 +42,18 @@ module ActiveRecord
40
42
  # This locking mechanism will function inside a single Ruby process. To make it work across all
41
43
  # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
42
44
  #
43
- # You must ensure that your database schema defaults the +lock_version+ column to 0.
44
- #
45
45
  # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
46
- # To override the name of the +lock_version+ column, invoke the <tt>set_locking_column</tt> method.
47
- # This method uses the same syntax as <tt>set_table_name</tt>
46
+ # To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
47
+ #
48
+ # class Person < ActiveRecord::Base
49
+ # self.locking_column = :lock_person
50
+ # end
51
+ #
48
52
  module Optimistic
49
53
  extend ActiveSupport::Concern
50
54
 
51
55
  included do
52
- cattr_accessor :lock_optimistically, :instance_writer => false
53
- self.lock_optimistically = true
56
+ class_attribute :lock_optimistically, instance_writer: false, default: true
54
57
  end
55
58
 
56
59
  def locking_enabled? #:nodoc:
@@ -58,127 +61,137 @@ module ActiveRecord
58
61
  end
59
62
 
60
63
  private
61
- def increment_lock
62
- lock_col = self.class.locking_column
63
- previous_lock_value = send(lock_col).to_i
64
- send(lock_col + '=', previous_lock_value + 1)
64
+ def _create_record(attribute_names = self.attribute_names, *)
65
+ if locking_enabled?
66
+ # We always want to persist the locking version, even if we don't detect
67
+ # a change from the default, since the database might have no default
68
+ attribute_names |= [self.class.locking_column]
69
+ end
70
+ super
65
71
  end
66
72
 
67
- def update(attribute_names = @attributes.keys) #:nodoc:
68
- return super unless locking_enabled?
69
- return 0 if attribute_names.empty?
70
-
71
- lock_col = self.class.locking_column
72
- previous_lock_value = send(lock_col).to_i
73
- increment_lock
73
+ def _touch_row(attribute_names, time)
74
+ super
75
+ ensure
76
+ clear_attribute_change(self.class.locking_column) if locking_enabled?
77
+ end
74
78
 
75
- attribute_names += [lock_col]
76
- attribute_names.uniq!
79
+ def _update_row(attribute_names, attempted_action = "update")
80
+ return super unless locking_enabled?
77
81
 
78
82
  begin
79
- relation = self.class.unscoped
83
+ locking_column = self.class.locking_column
84
+ previous_lock_value = read_attribute_before_type_cast(locking_column)
85
+ attribute_names << locking_column
80
86
 
81
- stmt = relation.where(
82
- relation.table[self.class.primary_key].eq(id).and(
83
- relation.table[lock_col].eq(quote_value(previous_lock_value, self.class.columns_hash[lock_col]))
84
- )
85
- ).arel.compile_update(arel_attributes_values(false, false, attribute_names))
87
+ self[locking_column] += 1
86
88
 
87
- affected_rows = connection.update stmt
89
+ affected_rows = self.class._update_record(
90
+ attributes_with_values(attribute_names),
91
+ self.class.primary_key => id_in_database,
92
+ locking_column => previous_lock_value
93
+ )
88
94
 
89
- unless affected_rows == 1
90
- raise ActiveRecord::StaleObjectError.new(self, "update")
95
+ if affected_rows != 1
96
+ raise ActiveRecord::StaleObjectError.new(self, attempted_action)
91
97
  end
92
98
 
93
99
  affected_rows
94
100
 
95
- # If something went wrong, revert the version.
101
+ # If something went wrong, revert the locking_column value.
96
102
  rescue Exception
97
- send(lock_col + '=', previous_lock_value)
103
+ self[locking_column] = previous_lock_value.to_i
98
104
  raise
99
105
  end
100
106
  end
101
107
 
102
- def destroy #:nodoc:
108
+ def destroy_row
103
109
  return super unless locking_enabled?
104
110
 
105
- destroy_associations
111
+ locking_column = self.class.locking_column
106
112
 
107
- if persisted?
108
- table = self.class.arel_table
109
- lock_col = self.class.locking_column
110
- predicate = table[self.class.primary_key].eq(id).
111
- and(table[lock_col].eq(send(lock_col).to_i))
113
+ affected_rows = self.class._delete_record(
114
+ self.class.primary_key => id_in_database,
115
+ locking_column => read_attribute_before_type_cast(locking_column)
116
+ )
112
117
 
113
- affected_rows = self.class.unscoped.where(predicate).delete_all
114
-
115
- unless affected_rows == 1
116
- raise ActiveRecord::StaleObjectError.new(self, "destroy")
117
- end
118
+ if affected_rows != 1
119
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
118
120
  end
119
121
 
120
- @destroyed = true
121
- freeze
122
+ affected_rows
122
123
  end
123
124
 
124
- module ClassMethods
125
- DEFAULT_LOCKING_COLUMN = 'lock_version'
125
+ module ClassMethods
126
+ DEFAULT_LOCKING_COLUMN = "lock_version"
126
127
 
127
- # Returns true if the +lock_optimistically+ flag is set to true
128
- # (which it is, by default) and the table includes the
129
- # +locking_column+ column (defaults to +lock_version+).
130
- def locking_enabled?
131
- lock_optimistically && columns_hash[locking_column]
132
- end
128
+ # Returns true if the +lock_optimistically+ flag is set to true
129
+ # (which it is, by default) and the table includes the
130
+ # +locking_column+ column (defaults to +lock_version+).
131
+ def locking_enabled?
132
+ lock_optimistically && columns_hash[locking_column]
133
+ end
133
134
 
134
- def locking_column=(value)
135
- @original_locking_column = @locking_column if defined?(@locking_column)
136
- @locking_column = value.to_s
137
- end
135
+ # Set the column to use for optimistic locking. Defaults to +lock_version+.
136
+ def locking_column=(value)
137
+ reload_schema_from_cache
138
+ @locking_column = value.to_s
139
+ end
138
140
 
139
- # Set the column to use for optimistic locking. Defaults to +lock_version+.
140
- def set_locking_column(value = nil, &block)
141
- deprecated_property_setter :locking_column, value, block
142
- end
141
+ # The version column used for optimistic locking. Defaults to +lock_version+.
142
+ def locking_column
143
+ @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
144
+ @locking_column
145
+ end
143
146
 
144
- # The version column used for optimistic locking. Defaults to +lock_version+.
145
- def locking_column
146
- reset_locking_column unless defined?(@locking_column)
147
- @locking_column
148
- end
147
+ # Reset the column used for optimistic locking back to the +lock_version+ default.
148
+ def reset_locking_column
149
+ self.locking_column = DEFAULT_LOCKING_COLUMN
150
+ end
149
151
 
150
- def original_locking_column #:nodoc:
151
- deprecated_original_property_getter :locking_column
152
- end
152
+ # Make sure the lock version column gets updated when counters are
153
+ # updated.
154
+ def update_counters(id, counters)
155
+ counters = counters.merge(locking_column => 1) if locking_enabled?
156
+ super
157
+ end
153
158
 
154
- # Quote the column name used for optimistic locking.
155
- def quoted_locking_column
156
- connection.quote_column_name(locking_column)
159
+ private
160
+
161
+ # We need to apply this decorator here, rather than on module inclusion. The closure
162
+ # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
163
+ # sub class being decorated. As such, changes to `lock_optimistically`, or
164
+ # `locking_column` would not be picked up.
165
+ def inherited(subclass)
166
+ subclass.class_eval do
167
+ is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
168
+ decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
169
+ LockingType.new(type)
170
+ end
171
+ end
172
+ super
173
+ end
157
174
  end
175
+ end
158
176
 
159
- # Reset the column used for optimistic locking back to the +lock_version+ default.
160
- def reset_locking_column
161
- self.locking_column = DEFAULT_LOCKING_COLUMN
162
- end
177
+ # In de/serialize we change `nil` to 0, so that we can allow passing
178
+ # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
179
+ # during update record.
180
+ class LockingType < DelegateClass(Type::Value) # :nodoc:
181
+ def deserialize(value)
182
+ super.to_i
183
+ end
163
184
 
164
- # Make sure the lock version column gets updated when counters are
165
- # updated.
166
- def update_counters(id, counters)
167
- counters = counters.merge(locking_column => 1) if locking_enabled?
168
- super
169
- end
185
+ def serialize(value)
186
+ super.to_i
187
+ end
170
188
 
171
- # If the locking column has no default value set,
172
- # start the lock version at zero. Note we can't use
173
- # <tt>locking_enabled?</tt> at this point as
174
- # <tt>@attributes</tt> may not have been initialized yet.
175
- def initialize_attributes(attributes, options = {}) #:nodoc:
176
- if attributes.key?(locking_column) && lock_optimistically
177
- attributes[locking_column] ||= 0
178
- end
189
+ def init_with(coder)
190
+ __setobj__(coder["subtype"])
191
+ end
179
192
 
180
- attributes
181
- end
193
+ def encode_with(coder)
194
+ coder["subtype"] = __getobj__
182
195
  end
183
196
  end
184
197
  end
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Locking
3
5
  # Locking::Pessimistic provides support for row-level locking using
4
6
  # SELECT ... FOR UPDATE and other lock types.
5
7
  #
6
- # Pass <tt>:lock => true</tt> to <tt>ActiveRecord::Base.find</tt> to obtain an exclusive
8
+ # Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
7
9
  # lock on the selected rows:
8
10
  # # select * from accounts where id=1 for update
9
- # Account.find(1, :lock => true)
11
+ # Account.lock.find(1)
10
12
  #
11
- # Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause
13
+ # Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
12
14
  # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
13
15
  #
14
16
  # Account.transaction do
@@ -26,7 +28,7 @@ module ActiveRecord
26
28
  #
27
29
  # Account.transaction do
28
30
  # # select * from accounts where ...
29
- # accounts = Account.where(...).all
31
+ # accounts = Account.where(...)
30
32
  # account1 = accounts.detect { |account| ... }
31
33
  # account2 = accounts.detect { |account| ... }
32
34
  # # select * from accounts where id=? for update
@@ -51,20 +53,30 @@ module ActiveRecord
51
53
  # end
52
54
  #
53
55
  # Database-specific information on row locking:
54
- # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
55
- # PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
56
+ # MySQL: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
57
+ # PostgreSQL: https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
56
58
  module Pessimistic
57
59
  # Obtain a row lock on this record. Reloads the record to obtain the requested
58
60
  # lock. Pass an SQL locking clause to append the end of the SELECT statement
59
61
  # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
60
62
  # the locked record.
61
63
  def lock!(lock = true)
62
- reload(:lock => lock) if persisted?
64
+ if persisted?
65
+ if has_changes_to_save?
66
+ raise(<<-MSG.squish)
67
+ Locking a record with unpersisted changes is not supported. Use
68
+ `save` to persist the changes, or `reload` to discard them
69
+ explicitly.
70
+ MSG
71
+ end
72
+
73
+ reload(lock: lock)
74
+ end
63
75
  self
64
76
  end
65
77
 
66
78
  # Wraps the passed block in a transaction, locking the object
67
- # before yielding. You pass can the SQL locking clause
79
+ # before yielding. You can pass the SQL locking clause
68
80
  # as argument (see <tt>lock!</tt>).
69
81
  def with_lock(lock = true)
70
82
  transaction do
@@ -1,11 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class LogSubscriber < ActiveSupport::LogSubscriber
5
+ IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
6
+
3
7
  def self.runtime=(value)
4
- Thread.current["active_record_sql_runtime"] = value
8
+ ActiveRecord::RuntimeRegistry.sql_runtime = value
5
9
  end
6
10
 
7
11
  def self.runtime
8
- Thread.current["active_record_sql_runtime"] ||= 0
12
+ ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
9
13
  end
10
14
 
11
15
  def self.reset_runtime
@@ -13,59 +17,120 @@ module ActiveRecord
13
17
  rt
14
18
  end
15
19
 
16
- def initialize
17
- super
18
- @odd_or_even = false
19
- end
20
-
21
20
  def sql(event)
22
21
  self.class.runtime += event.duration
23
22
  return unless logger.debug?
24
23
 
25
24
  payload = event.payload
26
25
 
27
- return if 'SCHEMA' == payload[:name]
26
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
28
27
 
29
- name = '%s (%.1fms)' % [payload[:name], event.duration]
30
- sql = payload[:sql].squeeze(' ')
28
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
29
+ name = "CACHE #{name}" if payload[:cached]
30
+ sql = payload[:sql]
31
31
  binds = nil
32
32
 
33
33
  unless (payload[:binds] || []).empty?
34
- binds = " " + payload[:binds].map { |col,v|
35
- if col
36
- [col.name, v]
37
- else
38
- [nil, v]
39
- end
34
+ casted_params = type_casted_binds(payload[:type_casted_binds])
35
+ binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
36
+ render_bind(attr, value)
40
37
  }.inspect
41
38
  end
42
39
 
43
- if odd?
44
- name = color(name, CYAN, true)
45
- sql = color(sql, nil, true)
46
- else
47
- name = color(name, MAGENTA, true)
48
- end
40
+ name = colorize_payload_name(name, payload[:name])
41
+ sql = color(sql, sql_color(sql), true)
49
42
 
50
43
  debug " #{name} #{sql}#{binds}"
51
44
  end
52
45
 
53
- def identity(event)
54
- return unless logger.debug?
46
+ private
47
+ def type_casted_binds(casted_binds)
48
+ casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
49
+ end
55
50
 
56
- name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true)
57
- line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line]
51
+ def render_bind(attr, value)
52
+ if attr.is_a?(Array)
53
+ attr = attr.first
54
+ elsif attr.type.binary? && attr.value
55
+ value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
56
+ end
58
57
 
59
- debug " #{name} #{line}"
60
- end
58
+ [attr && attr.name, value]
59
+ end
61
60
 
62
- def odd?
63
- @odd_or_even = !@odd_or_even
64
- end
61
+ def colorize_payload_name(name, payload_name)
62
+ if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
63
+ color(name, MAGENTA, true)
64
+ else
65
+ color(name, CYAN, true)
66
+ end
67
+ end
65
68
 
66
- def logger
67
- ActiveRecord::Base.logger
68
- end
69
+ def sql_color(sql)
70
+ case sql
71
+ when /\A\s*rollback/mi
72
+ RED
73
+ when /select .*for update/mi, /\A\s*lock/mi
74
+ WHITE
75
+ when /\A\s*select/i
76
+ BLUE
77
+ when /\A\s*insert/i
78
+ GREEN
79
+ when /\A\s*update/i
80
+ YELLOW
81
+ when /\A\s*delete/i
82
+ RED
83
+ when /transaction\s*\Z/i
84
+ CYAN
85
+ else
86
+ MAGENTA
87
+ end
88
+ end
89
+
90
+ def logger
91
+ ActiveRecord::Base.logger
92
+ end
93
+
94
+ def debug(progname = nil, &block)
95
+ return unless super
96
+
97
+ if ActiveRecord::Base.verbose_query_logs
98
+ log_query_source
99
+ end
100
+ end
101
+
102
+ def log_query_source
103
+ source_line, line_number = extract_callstack(caller_locations)
104
+
105
+ if source_line
106
+ if defined?(::Rails.root)
107
+ app_root = "#{::Rails.root.to_s}/".freeze
108
+ source_line = source_line.sub(app_root, "")
109
+ end
110
+
111
+ logger.debug(" ↳ #{ source_line }:#{ line_number }")
112
+ end
113
+ end
114
+
115
+ def extract_callstack(callstack)
116
+ line = callstack.find do |frame|
117
+ frame.absolute_path && !ignored_callstack(frame.absolute_path)
118
+ end
119
+
120
+ offending_line = line || callstack.first
121
+
122
+ [
123
+ offending_line.path,
124
+ offending_line.lineno
125
+ ]
126
+ end
127
+
128
+ RAILS_GEM_ROOT = File.expand_path("../../..", __dir__) + "/"
129
+
130
+ def ignored_callstack(path)
131
+ path.start_with?(RAILS_GEM_ROOT) ||
132
+ path.start_with?(RbConfig::CONFIG["rubylibdir"])
133
+ end
69
134
  end
70
135
  end
71
136