activerecord 4.2.0 → 5.2.8.1

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 (274) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +640 -928
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +264 -247
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +87 -41
  11. data/lib/active_record/associations/association_scope.rb +106 -132
  12. data/lib/active_record/associations/belongs_to_association.rb +55 -36
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +14 -23
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +145 -266
  22. data/lib/active_record/associations/collection_proxy.rb +242 -138
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -75
  25. data/lib/active_record/associations/has_many_through_association.rb +51 -69
  26. data/lib/active_record/associations/has_one_association.rb +39 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +134 -154
  32. data/lib/active_record/associations/preloader/association.rb +85 -116
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +83 -93
  35. data/lib/active_record/associations/singular_association.rb +27 -40
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1732 -1596
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +12 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -125
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +31 -46
  48. data/lib/active_record/attribute_methods.rb +170 -117
  49. data/lib/active_record/attributes.rb +201 -74
  50. data/lib/active_record/autosave_association.rb +118 -45
  51. data/lib/active_record/base.rb +60 -48
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +37 -13
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +617 -212
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  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 +42 -195
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
  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 +50 -57
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +5 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  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 +7 -9
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +466 -280
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +439 -330
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  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 +269 -324
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +205 -202
  129. data/lib/active_record/counter_cache.rb +80 -37
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +136 -90
  133. data/lib/active_record/errors.rb +180 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +11 -6
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +193 -135
  139. data/lib/active_record/gem_version.rb +5 -3
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  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 +3 -2
  145. data/lib/active_record/locking/optimistic.rb +92 -98
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +594 -267
  152. data/lib/active_record/model_schema.rb +292 -111
  153. data/lib/active_record/nested_attributes.rb +266 -214
  154. data/lib/active_record/no_touching.rb +8 -2
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +350 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +117 -35
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +160 -174
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +447 -288
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +259 -244
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +290 -253
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +91 -68
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
  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 +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +118 -92
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +446 -389
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +18 -16
  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 +287 -339
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -19
  193. data/lib/active_record/scoping/default.rb +102 -84
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  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 +136 -95
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +208 -123
  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 +4 -41
  212. data/lib/active_record/type/date_time.rb +4 -38
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  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 +30 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  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 +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +41 -32
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +36 -21
  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 +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -6
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record.rb +7 -5
  243. metadata +77 -53
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  251. data/lib/active_record/attribute.rb +0 -149
  252. data/lib/active_record/attribute_set/builder.rb +0 -86
  253. data/lib/active_record/attribute_set.rb +0 -77
  254. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  255. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  256. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  257. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  258. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  259. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  260. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  261. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  262. data/lib/active_record/type/big_integer.rb +0 -13
  263. data/lib/active_record/type/binary.rb +0 -50
  264. data/lib/active_record/type/boolean.rb +0 -30
  265. data/lib/active_record/type/decimal.rb +0 -40
  266. data/lib/active_record/type/decorator.rb +0 -14
  267. data/lib/active_record/type/float.rb +0 -19
  268. data/lib/active_record/type/integer.rb +0 -55
  269. data/lib/active_record/type/mutable.rb +0 -16
  270. data/lib/active_record/type/numeric.rb +0 -36
  271. data/lib/active_record/type/string.rb +0 -36
  272. data/lib/active_record/type/time_value.rb +0 -38
  273. data/lib/active_record/type/value.rb +0 -101
  274. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -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.
@@ -51,8 +53,7 @@ module ActiveRecord
51
53
  extend ActiveSupport::Concern
52
54
 
53
55
  included do
54
- class_attribute :lock_optimistically, instance_writer: false
55
- self.lock_optimistically = true
56
+ class_attribute :lock_optimistically, instance_writer: false, default: true
56
57
  end
57
58
 
58
59
  def locking_enabled? #:nodoc:
@@ -60,144 +61,137 @@ module ActiveRecord
60
61
  end
61
62
 
62
63
  private
63
- def increment_lock
64
- lock_col = self.class.locking_column
65
- previous_lock_value = send(lock_col).to_i
66
- 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
67
71
  end
68
72
 
69
- def _update_record(attribute_names = self.attribute_names) #:nodoc:
73
+ def _touch_row(attribute_names, time)
74
+ super
75
+ ensure
76
+ clear_attribute_change(self.class.locking_column) if locking_enabled?
77
+ end
78
+
79
+ def _update_row(attribute_names, attempted_action = "update")
70
80
  return super unless locking_enabled?
71
- return 0 if attribute_names.empty?
72
81
 
73
- lock_col = self.class.locking_column
74
- previous_lock_value = send(lock_col).to_i
75
- increment_lock
82
+ begin
83
+ locking_column = self.class.locking_column
84
+ previous_lock_value = read_attribute_before_type_cast(locking_column)
85
+ attribute_names << locking_column
76
86
 
77
- attribute_names += [lock_col]
78
- attribute_names.uniq!
87
+ self[locking_column] += 1
79
88
 
80
- begin
81
- relation = self.class.unscoped
82
-
83
- stmt = relation.where(
84
- relation.table[self.class.primary_key].eq(id).and(
85
- relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
86
- )
87
- ).arel.compile_update(
88
- arel_attributes_with_values_for_update(attribute_names),
89
- self.class.primary_key
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
90
93
  )
91
94
 
92
- affected_rows = self.class.connection.update stmt
93
-
94
- unless affected_rows == 1
95
- raise ActiveRecord::StaleObjectError.new(self, "update")
95
+ if affected_rows != 1
96
+ raise ActiveRecord::StaleObjectError.new(self, attempted_action)
96
97
  end
97
98
 
98
99
  affected_rows
99
100
 
100
- # If something went wrong, revert the version.
101
+ # If something went wrong, revert the locking_column value.
101
102
  rescue Exception
102
- send(lock_col + '=', previous_lock_value)
103
+ self[locking_column] = previous_lock_value.to_i
103
104
  raise
104
105
  end
105
106
  end
106
107
 
107
108
  def destroy_row
108
- affected_rows = super
109
+ return super unless locking_enabled?
110
+
111
+ locking_column = self.class.locking_column
112
+
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
+ )
109
117
 
110
- if locking_enabled? && affected_rows != 1
118
+ if affected_rows != 1
111
119
  raise ActiveRecord::StaleObjectError.new(self, "destroy")
112
120
  end
113
121
 
114
122
  affected_rows
115
123
  end
116
124
 
117
- def relation_for_destroy
118
- relation = super
125
+ module ClassMethods
126
+ DEFAULT_LOCKING_COLUMN = "lock_version"
119
127
 
120
- if locking_enabled?
121
- column_name = self.class.locking_column
122
- column = self.class.columns_hash[column_name]
123
- substitute = self.class.connection.substitute_at(column)
124
-
125
- relation = relation.where(self.class.arel_table[column_name].eq(substitute))
126
- relation.bind_values << [column, self[column_name].to_i]
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]
127
133
  end
128
134
 
129
- relation
130
- end
131
-
132
- module ClassMethods
133
- DEFAULT_LOCKING_COLUMN = 'lock_version'
134
-
135
- # Returns true if the +lock_optimistically+ flag is set to true
136
- # (which it is, by default) and the table includes the
137
- # +locking_column+ column (defaults to +lock_version+).
138
- def locking_enabled?
139
- lock_optimistically && columns_hash[locking_column]
140
- end
141
-
142
- # Set the column to use for optimistic locking. Defaults to +lock_version+.
143
- def locking_column=(value)
144
- clear_caches_calculated_from_columns
145
- @locking_column = value.to_s
146
- 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
147
140
 
148
- # The version column used for optimistic locking. Defaults to +lock_version+.
149
- def locking_column
150
- reset_locking_column unless defined?(@locking_column)
151
- @locking_column
152
- 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
153
146
 
154
- # Reset the column used for optimistic locking back to the +lock_version+ default.
155
- def reset_locking_column
156
- self.locking_column = DEFAULT_LOCKING_COLUMN
157
- 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
158
151
 
159
- # Make sure the lock version column gets updated when counters are
160
- # updated.
161
- def update_counters(id, counters)
162
- counters = counters.merge(locking_column => 1) if locking_enabled?
163
- super
164
- 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
165
158
 
166
- private
167
-
168
- # We need to apply this decorator here, rather than on module inclusion. The closure
169
- # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
170
- # sub class being decorated. As such, changes to `lock_optimistically`, or
171
- # `locking_column` would not be picked up.
172
- def inherited(subclass)
173
- subclass.class_eval do
174
- is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
175
- decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
176
- LockingType.new(type)
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
177
173
  end
178
- end
179
- super
180
174
  end
181
- end
182
175
  end
183
176
 
184
- class LockingType < SimpleDelegator # :nodoc:
185
- def type_cast_from_database(value)
186
- # `nil` *should* be changed to 0
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)
187
182
  super.to_i
188
183
  end
189
184
 
190
- def changed?(old_value, *)
191
- # Ensure we save if the default was `nil`
192
- super || old_value == 0
185
+ def serialize(value)
186
+ super.to_i
193
187
  end
194
188
 
195
189
  def init_with(coder)
196
- __setobj__(coder['subtype'])
190
+ __setobj__(coder["subtype"])
197
191
  end
198
192
 
199
193
  def encode_with(coder)
200
- coder['subtype'] = __getobj__
194
+ coder["subtype"] = __getobj__
201
195
  end
202
196
  end
203
197
  end
@@ -1,3 +1,5 @@
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
@@ -51,15 +53,25 @@ 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
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  class LogSubscriber < ActiveSupport::LogSubscriber
3
5
  IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
@@ -15,25 +17,6 @@ module ActiveRecord
15
17
  rt
16
18
  end
17
19
 
18
- def initialize
19
- super
20
- @odd = false
21
- end
22
-
23
- def render_bind(column, value)
24
- if column
25
- if column.binary?
26
- # This specifically deals with the PG adapter that casts bytea columns into a Hash.
27
- value = value[:value] if value.is_a?(Hash)
28
- value = value ? "<#{value.bytesize} bytes of binary data>" : "<NULL binary data>"
29
- end
30
-
31
- [column.name, value]
32
- else
33
- [nil, value]
34
- end
35
- end
36
-
37
20
  def sql(event)
38
21
  self.class.runtime += event.duration
39
22
  return unless logger.debug?
@@ -43,32 +26,111 @@ module ActiveRecord
43
26
  return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
44
27
 
45
28
  name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
29
+ name = "CACHE #{name}" if payload[:cached]
46
30
  sql = payload[:sql]
47
31
  binds = nil
48
32
 
49
33
  unless (payload[:binds] || []).empty?
50
- binds = " " + payload[:binds].map { |col,v|
51
- render_bind(col, v)
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)
52
37
  }.inspect
53
38
  end
54
39
 
55
- if odd?
56
- name = color(name, CYAN, true)
57
- sql = color(sql, nil, true)
58
- else
59
- name = color(name, MAGENTA, true)
60
- end
40
+ name = colorize_payload_name(name, payload[:name])
41
+ sql = color(sql, sql_color(sql), true)
61
42
 
62
43
  debug " #{name} #{sql}#{binds}"
63
44
  end
64
45
 
65
- def odd?
66
- @odd = !@odd
67
- end
46
+ private
47
+ def type_casted_binds(casted_binds)
48
+ casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
49
+ end
68
50
 
69
- def logger
70
- ActiveRecord::Base.logger
71
- end
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
57
+
58
+ [attr && attr.name, value]
59
+ end
60
+
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
68
+
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
72
134
  end
73
135
  end
74
136