activerecord 4.2.11.1 → 5.2.4
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +579 -1635
- data/MIT-LICENSE +2 -2
- data/README.rdoc +10 -11
- data/examples/performance.rb +32 -31
- data/examples/simple.rb +5 -4
- data/lib/active_record/aggregations.rb +263 -249
- data/lib/active_record/association_relation.rb +11 -6
- data/lib/active_record/associations/alias_tracker.rb +29 -35
- data/lib/active_record/associations/association.rb +77 -43
- data/lib/active_record/associations/association_scope.rb +106 -133
- data/lib/active_record/associations/belongs_to_association.rb +52 -41
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
- data/lib/active_record/associations/builder/association.rb +29 -38
- data/lib/active_record/associations/builder/belongs_to.rb +77 -30
- data/lib/active_record/associations/builder/collection_association.rb +9 -22
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +42 -35
- data/lib/active_record/associations/builder/has_many.rb +6 -4
- data/lib/active_record/associations/builder/has_one.rb +13 -6
- data/lib/active_record/associations/builder/singular_association.rb +15 -11
- data/lib/active_record/associations/collection_association.rb +139 -280
- data/lib/active_record/associations/collection_proxy.rb +231 -133
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_many_association.rb +34 -89
- data/lib/active_record/associations/has_many_through_association.rb +49 -76
- data/lib/active_record/associations/has_one_association.rb +38 -24
- data/lib/active_record/associations/has_one_through_association.rb +18 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +40 -87
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
- data/lib/active_record/associations/join_dependency.rb +133 -159
- data/lib/active_record/associations/preloader/association.rb +85 -120
- data/lib/active_record/associations/preloader/through_association.rb +85 -74
- data/lib/active_record/associations/preloader.rb +81 -91
- data/lib/active_record/associations/singular_association.rb +27 -34
- data/lib/active_record/associations/through_association.rb +38 -18
- data/lib/active_record/associations.rb +1732 -1597
- data/lib/active_record/attribute_assignment.rb +58 -182
- data/lib/active_record/attribute_decorators.rb +39 -15
- data/lib/active_record/attribute_methods/before_type_cast.rb +10 -8
- data/lib/active_record/attribute_methods/dirty.rb +94 -135
- data/lib/active_record/attribute_methods/primary_key.rb +86 -71
- data/lib/active_record/attribute_methods/query.rb +4 -2
- data/lib/active_record/attribute_methods/read.rb +45 -63
- data/lib/active_record/attribute_methods/serialization.rb +40 -20
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
- data/lib/active_record/attribute_methods/write.rb +30 -45
- data/lib/active_record/attribute_methods.rb +166 -109
- data/lib/active_record/attributes.rb +201 -82
- data/lib/active_record/autosave_association.rb +94 -36
- data/lib/active_record/base.rb +57 -44
- data/lib/active_record/callbacks.rb +97 -57
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +24 -12
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -290
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +237 -90
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +71 -21
- data/lib/active_record/connection_adapters/abstract/quoting.rb +118 -52
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +318 -217
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +570 -228
- data/lib/active_record/connection_adapters/abstract/transaction.rb +138 -70
- data/lib/active_record/connection_adapters/abstract_adapter.rb +325 -202
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +542 -601
- data/lib/active_record/connection_adapters/column.rb +50 -41
- data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +41 -180
- data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +45 -114
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -7
- data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +55 -53
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -284
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +432 -323
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -308
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +40 -27
- data/lib/active_record/core.rb +178 -198
- data/lib/active_record/counter_cache.rb +79 -36
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -105
- data/lib/active_record/enum.rb +135 -88
- data/lib/active_record/errors.rb +179 -52
- data/lib/active_record/explain.rb +23 -11
- data/lib/active_record/explain_registry.rb +4 -2
- data/lib/active_record/explain_subscriber.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +35 -9
- data/lib/active_record/fixtures.rb +188 -132
- data/lib/active_record/gem_version.rb +5 -3
- data/lib/active_record/inheritance.rb +148 -112
- data/lib/active_record/integration.rb +70 -28
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +21 -3
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +88 -96
- data/lib/active_record/locking/pessimistic.rb +15 -3
- data/lib/active_record/log_subscriber.rb +95 -33
- data/lib/active_record/migration/command_recorder.rb +133 -90
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +8 -6
- data/lib/active_record/migration.rb +581 -282
- data/lib/active_record/model_schema.rb +290 -111
- data/lib/active_record/nested_attributes.rb +264 -222
- data/lib/active_record/no_touching.rb +7 -1
- data/lib/active_record/null_relation.rb +24 -37
- data/lib/active_record/persistence.rb +347 -119
- data/lib/active_record/query_cache.rb +13 -24
- data/lib/active_record/querying.rb +19 -17
- data/lib/active_record/railtie.rb +94 -32
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +9 -3
- data/lib/active_record/railties/databases.rake +149 -156
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +414 -267
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +204 -55
- data/lib/active_record/relation/calculations.rb +256 -248
- data/lib/active_record/relation/delegation.rb +67 -60
- data/lib/active_record/relation/finder_methods.rb +288 -239
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +86 -86
- data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -24
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/predicate_builder.rb +116 -119
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +448 -393
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +11 -13
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/relation.rb +287 -340
- data/lib/active_record/result.rb +54 -36
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +155 -124
- data/lib/active_record/schema.rb +30 -24
- data/lib/active_record/schema_dumper.rb +91 -87
- data/lib/active_record/schema_migration.rb +19 -16
- data/lib/active_record/scoping/default.rb +102 -85
- data/lib/active_record/scoping/named.rb +81 -32
- data/lib/active_record/scoping.rb +45 -26
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +5 -5
- data/lib/active_record/statement_cache.rb +45 -35
- data/lib/active_record/store.rb +42 -36
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +134 -96
- data/lib/active_record/tasks/mysql_database_tasks.rb +56 -100
- data/lib/active_record/tasks/postgresql_database_tasks.rb +83 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
- data/lib/active_record/timestamp.rb +70 -38
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +199 -124
- data/lib/active_record/translation.rb +2 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -0
- data/lib/active_record/type/date.rb +4 -45
- data/lib/active_record/type/date_time.rb +4 -49
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +24 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +11 -16
- data/lib/active_record/type/type_map.rb +15 -17
- data/lib/active_record/type/unsigned_integer.rb +9 -7
- data/lib/active_record/type.rb +79 -23
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +13 -4
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +40 -41
- data/lib/active_record/validations.rb +38 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +34 -22
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
- data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -3
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -1
- data/lib/rails/generators/active_record/migration.rb +18 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
- data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
- data/lib/rails/generators/active_record.rb +7 -5
- metadata +72 -49
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute.rb +0 -163
- data/lib/active_record/attribute_set/builder.rb +0 -106
- data/lib/active_record/attribute_set.rb +0 -81
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -110
@@ -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
|
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
|
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
|
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,13 +61,7 @@ module ActiveRecord
|
|
60
61
|
end
|
61
62
|
|
62
63
|
private
|
63
|
-
def
|
64
|
-
lock_col = self.class.locking_column
|
65
|
-
previous_lock_value = send(lock_col).to_i
|
66
|
-
send(lock_col + '=', previous_lock_value + 1)
|
67
|
-
end
|
68
|
-
|
69
|
-
def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
|
64
|
+
def _create_record(attribute_names = self.attribute_names, *)
|
70
65
|
if locking_enabled?
|
71
66
|
# We always want to persist the locking version, even if we don't detect
|
72
67
|
# a change from the default, since the database might have no default
|
@@ -75,131 +70,128 @@ module ActiveRecord
|
|
75
70
|
super
|
76
71
|
end
|
77
72
|
|
78
|
-
def
|
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")
|
79
80
|
return super unless locking_enabled?
|
80
|
-
return 0 if attribute_names.empty?
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
85
86
|
|
86
|
-
|
87
|
-
attribute_names.uniq!
|
87
|
+
self[locking_column] += 1
|
88
88
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
self.class.primary_key => id,
|
94
|
-
lock_col => previous_lock_value,
|
95
|
-
).update_all(
|
96
|
-
Hash[attributes_for_update(attribute_names).map do |name|
|
97
|
-
[name, _read_attribute(name)]
|
98
|
-
end]
|
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
|
99
93
|
)
|
100
94
|
|
101
|
-
|
102
|
-
raise ActiveRecord::StaleObjectError.new(self,
|
95
|
+
if affected_rows != 1
|
96
|
+
raise ActiveRecord::StaleObjectError.new(self, attempted_action)
|
103
97
|
end
|
104
98
|
|
105
99
|
affected_rows
|
106
100
|
|
107
|
-
# If something went wrong, revert the
|
101
|
+
# If something went wrong, revert the locking_column value.
|
108
102
|
rescue Exception
|
109
|
-
|
103
|
+
self[locking_column] = previous_lock_value.to_i
|
110
104
|
raise
|
111
105
|
end
|
112
106
|
end
|
113
107
|
|
114
108
|
def destroy_row
|
115
|
-
|
109
|
+
return super unless locking_enabled?
|
116
110
|
|
117
|
-
|
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
|
+
)
|
117
|
+
|
118
|
+
if affected_rows != 1
|
118
119
|
raise ActiveRecord::StaleObjectError.new(self, "destroy")
|
119
120
|
end
|
120
121
|
|
121
122
|
affected_rows
|
122
123
|
end
|
123
124
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
if locking_enabled?
|
128
|
-
column_name = self.class.locking_column
|
129
|
-
column = self.class.columns_hash[column_name]
|
130
|
-
substitute = self.class.connection.substitute_at(column)
|
125
|
+
module ClassMethods
|
126
|
+
DEFAULT_LOCKING_COLUMN = "lock_version"
|
131
127
|
|
132
|
-
|
133
|
-
|
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]
|
134
133
|
end
|
135
134
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
# Returns true if the +lock_optimistically+ flag is set to true
|
143
|
-
# (which it is, by default) and the table includes the
|
144
|
-
# +locking_column+ column (defaults to +lock_version+).
|
145
|
-
def locking_enabled?
|
146
|
-
lock_optimistically && columns_hash[locking_column]
|
147
|
-
end
|
148
|
-
|
149
|
-
# Set the column to use for optimistic locking. Defaults to +lock_version+.
|
150
|
-
def locking_column=(value)
|
151
|
-
clear_caches_calculated_from_columns
|
152
|
-
@locking_column = value.to_s
|
153
|
-
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
|
154
140
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
160
146
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
165
151
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
172
158
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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
|
184
173
|
end
|
185
|
-
end
|
186
|
-
super
|
187
174
|
end
|
188
|
-
end
|
189
175
|
end
|
190
176
|
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
184
|
+
|
185
|
+
def serialize(value)
|
194
186
|
super.to_i
|
195
187
|
end
|
196
188
|
|
197
189
|
def init_with(coder)
|
198
|
-
__setobj__(coder[
|
190
|
+
__setobj__(coder["subtype"])
|
199
191
|
end
|
200
192
|
|
201
193
|
def encode_with(coder)
|
202
|
-
coder[
|
194
|
+
coder["subtype"] = __getobj__
|
203
195
|
end
|
204
196
|
end
|
205
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:
|
55
|
-
# PostgreSQL:
|
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
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
70
|
-
|
71
|
-
|
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
|
|