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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +640 -928
- 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 +264 -247
- data/lib/active_record/association_relation.rb +24 -6
- data/lib/active_record/associations/alias_tracker.rb +29 -35
- data/lib/active_record/associations/association.rb +87 -41
- data/lib/active_record/associations/association_scope.rb +106 -132
- data/lib/active_record/associations/belongs_to_association.rb +55 -36
- 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 +14 -23
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
- 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 +145 -266
- data/lib/active_record/associations/collection_proxy.rb +242 -138
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +35 -75
- data/lib/active_record/associations/has_many_through_association.rb +51 -69
- data/lib/active_record/associations/has_one_association.rb +39 -24
- data/lib/active_record/associations/has_one_through_association.rb +18 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +40 -81
- 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 +134 -154
- data/lib/active_record/associations/preloader/association.rb +85 -116
- data/lib/active_record/associations/preloader/through_association.rb +85 -74
- data/lib/active_record/associations/preloader.rb +83 -93
- data/lib/active_record/associations/singular_association.rb +27 -40
- data/lib/active_record/associations/through_association.rb +48 -23
- data/lib/active_record/associations.rb +1732 -1596
- 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 +12 -5
- data/lib/active_record/attribute_methods/dirty.rb +94 -125
- 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 +62 -36
- data/lib/active_record/attribute_methods/write.rb +31 -46
- data/lib/active_record/attribute_methods.rb +170 -117
- data/lib/active_record/attributes.rb +201 -74
- data/lib/active_record/autosave_association.rb +118 -45
- data/lib/active_record/base.rb +60 -48
- 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 +37 -13
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -217
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +617 -212
- data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
- 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 +42 -195
- data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -57
- 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 +5 -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 -13
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -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 +7 -9
- 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 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
- 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 +466 -280
- 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 +439 -330
- 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 -324
- 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 +205 -202
- data/lib/active_record/counter_cache.rb +80 -37
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -105
- data/lib/active_record/enum.rb +136 -90
- data/lib/active_record/errors.rb +180 -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 +11 -6
- data/lib/active_record/fixture_set/file.rb +35 -9
- data/lib/active_record/fixtures.rb +193 -135
- 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 +48 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +92 -98
- 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 +594 -267
- data/lib/active_record/model_schema.rb +292 -111
- data/lib/active_record/nested_attributes.rb +266 -214
- data/lib/active_record/no_touching.rb +8 -2
- data/lib/active_record/null_relation.rb +24 -37
- data/lib/active_record/persistence.rb +350 -119
- data/lib/active_record/query_cache.rb +13 -24
- data/lib/active_record/querying.rb +19 -17
- data/lib/active_record/railtie.rb +117 -35
- 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 +160 -174
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +447 -288
- 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 +259 -244
- data/lib/active_record/relation/delegation.rb +67 -60
- data/lib/active_record/relation/finder_methods.rb +290 -253
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +91 -68
- data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
- 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 +118 -92
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +446 -389
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +18 -16
- 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 -339
- 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 -19
- data/lib/active_record/scoping/default.rb +102 -84
- 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 +136 -95
- data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
- data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
- 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 +208 -123
- 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 -41
- data/lib/active_record/type/date_time.rb +4 -38
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
- 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 +30 -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 +41 -32
- data/lib/active_record/validations.rb +38 -35
- data/lib/active_record/version.rb +3 -1
- data/lib/active_record.rb +36 -21
- 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 -6
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
- 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.rb +7 -5
- metadata +77 -53
- 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 -149
- data/lib/active_record/attribute_set/builder.rb +0 -86
- data/lib/active_record/attribute_set.rb +0 -77
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
- 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 -30
- data/lib/active_record/type/decimal.rb +0 -40
- 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 -55
- 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 -36
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -101
- /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
|
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,144 +61,137 @@ module ActiveRecord
|
|
60
61
|
end
|
61
62
|
|
62
63
|
private
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
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
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
78
|
-
attribute_names.uniq!
|
87
|
+
self[locking_column] += 1
|
79
88
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
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
|
101
|
+
# If something went wrong, revert the locking_column value.
|
101
102
|
rescue Exception
|
102
|
-
|
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
|
-
|
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
|
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
|
-
|
118
|
-
|
125
|
+
module ClassMethods
|
126
|
+
DEFAULT_LOCKING_COLUMN = "lock_version"
|
119
127
|
|
120
|
-
if
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
185
|
-
|
186
|
-
|
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
|
191
|
-
|
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[
|
190
|
+
__setobj__(coder["subtype"])
|
197
191
|
end
|
198
192
|
|
199
193
|
def encode_with(coder)
|
200
|
-
coder[
|
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:
|
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
|
|