activerecord 3.2.22.5 → 5.2.8
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +657 -621
- data/MIT-LICENSE +2 -2
- data/README.rdoc +41 -46
- data/examples/performance.rb +55 -42
- data/examples/simple.rb +6 -5
- data/lib/active_record/aggregations.rb +264 -236
- data/lib/active_record/association_relation.rb +40 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -42
- data/lib/active_record/associations/association.rb +127 -75
- data/lib/active_record/associations/association_scope.rb +126 -92
- data/lib/active_record/associations/belongs_to_association.rb +78 -27
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
- data/lib/active_record/associations/builder/association.rb +117 -32
- data/lib/active_record/associations/builder/belongs_to.rb +135 -60
- data/lib/active_record/associations/builder/collection_association.rb +61 -54
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
- data/lib/active_record/associations/builder/has_many.rb +10 -64
- data/lib/active_record/associations/builder/has_one.rb +19 -51
- data/lib/active_record/associations/builder/singular_association.rb +28 -18
- data/lib/active_record/associations/collection_association.rb +226 -293
- data/lib/active_record/associations/collection_proxy.rb +1067 -69
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +83 -47
- data/lib/active_record/associations/has_many_through_association.rb +98 -65
- data/lib/active_record/associations/has_one_association.rb +57 -20
- data/lib/active_record/associations/has_one_through_association.rb +18 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
- data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
- data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
- data/lib/active_record/associations/join_dependency.rb +212 -164
- data/lib/active_record/associations/preloader/association.rb +95 -89
- data/lib/active_record/associations/preloader/through_association.rb +84 -44
- data/lib/active_record/associations/preloader.rb +123 -111
- data/lib/active_record/associations/singular_association.rb +33 -24
- data/lib/active_record/associations/through_association.rb +60 -26
- data/lib/active_record/associations.rb +1759 -1506
- data/lib/active_record/attribute_assignment.rb +60 -193
- data/lib/active_record/attribute_decorators.rb +90 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
- data/lib/active_record/attribute_methods/dirty.rb +113 -74
- data/lib/active_record/attribute_methods/primary_key.rb +106 -77
- data/lib/active_record/attribute_methods/query.rb +8 -5
- data/lib/active_record/attribute_methods/read.rb +63 -114
- data/lib/active_record/attribute_methods/serialization.rb +60 -90
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
- data/lib/active_record/attribute_methods/write.rb +43 -45
- data/lib/active_record/attribute_methods.rb +366 -149
- data/lib/active_record/attributes.rb +266 -0
- data/lib/active_record/autosave_association.rb +312 -225
- data/lib/active_record/base.rb +114 -505
- data/lib/active_record/callbacks.rb +145 -67
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +32 -23
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
- data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
- data/lib/active_record/connection_adapters/column.rb +50 -255
- data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
- 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 +59 -210
- data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
- data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
- 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 +545 -27
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +145 -0
- data/lib/active_record/core.rb +559 -0
- data/lib/active_record/counter_cache.rb +200 -105
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +107 -69
- data/lib/active_record/enum.rb +244 -0
- data/lib/active_record/errors.rb +245 -60
- data/lib/active_record/explain.rb +35 -71
- data/lib/active_record/explain_registry.rb +32 -0
- data/lib/active_record/explain_subscriber.rb +18 -9
- data/lib/active_record/fixture_set/file.rb +82 -0
- data/lib/active_record/fixtures.rb +418 -275
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +209 -100
- data/lib/active_record/integration.rb +116 -21
- 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 +9 -1
- data/lib/active_record/locking/optimistic.rb +107 -94
- data/lib/active_record/locking/pessimistic.rb +20 -8
- data/lib/active_record/log_subscriber.rb +99 -34
- data/lib/active_record/migration/command_recorder.rb +199 -64
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +17 -0
- data/lib/active_record/migration.rb +893 -296
- data/lib/active_record/model_schema.rb +328 -175
- data/lib/active_record/nested_attributes.rb +338 -242
- data/lib/active_record/no_touching.rb +58 -0
- data/lib/active_record/null_relation.rb +68 -0
- data/lib/active_record/persistence.rb +557 -170
- data/lib/active_record/query_cache.rb +14 -43
- data/lib/active_record/querying.rb +36 -24
- data/lib/active_record/railtie.rb +147 -52
- data/lib/active_record/railties/console_sandbox.rb +5 -4
- data/lib/active_record/railties/controller_runtime.rb +13 -6
- data/lib/active_record/railties/databases.rake +206 -488
- data/lib/active_record/readonly_attributes.rb +4 -6
- data/lib/active_record/reflection.rb +734 -228
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/batches.rb +249 -52
- data/lib/active_record/relation/calculations.rb +330 -284
- data/lib/active_record/relation/delegation.rb +135 -37
- data/lib/active_record/relation/finder_methods.rb +450 -287
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- 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 +19 -0
- data/lib/active_record/relation/predicate_builder.rb +132 -43
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +1037 -221
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +48 -151
- 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 +451 -359
- data/lib/active_record/result.rb +129 -20
- data/lib/active_record/runtime_registry.rb +24 -0
- data/lib/active_record/sanitization.rb +164 -136
- data/lib/active_record/schema.rb +31 -19
- data/lib/active_record/schema_dumper.rb +154 -107
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping/default.rb +108 -98
- data/lib/active_record/scoping/named.rb +125 -112
- data/lib/active_record/scoping.rb +77 -123
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +10 -6
- data/lib/active_record/statement_cache.rb +121 -0
- data/lib/active_record/store.rb +175 -16
- 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 +337 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
- data/lib/active_record/timestamp.rb +80 -41
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +240 -119
- 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 +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
- 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 +71 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +21 -0
- data/lib/active_record/type/type_map.rb +62 -0
- data/lib/active_record/type/unsigned_integer.rb +17 -0
- data/lib/active_record/type.rb +79 -0
- 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 +35 -18
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +68 -0
- data/lib/active_record/validations/uniqueness.rb +133 -75
- data/lib/active_record/validations.rb +53 -43
- data/lib/active_record/version.rb +7 -7
- data/lib/active_record.rb +89 -57
- 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 +61 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
- data/lib/rails/generators/active_record/migration.rb +28 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
- data/lib/rails/generators/active_record.rb +10 -16
- metadata +141 -62
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- 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_and_belongs_to_many.rb +0 -60
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
- 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_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -4,12 +4,20 @@ en:
|
|
4
4
|
#created_at: "Created at"
|
5
5
|
#updated_at: "Updated at"
|
6
6
|
|
7
|
+
# Default error messages
|
8
|
+
errors:
|
9
|
+
messages:
|
10
|
+
required: "must exist"
|
11
|
+
taken: "has already been taken"
|
12
|
+
|
7
13
|
# Active Record models configuration
|
8
14
|
activerecord:
|
9
15
|
errors:
|
10
16
|
messages:
|
11
|
-
taken: "has already been taken"
|
12
17
|
record_invalid: "Validation failed: %{errors}"
|
18
|
+
restrict_dependent_destroy:
|
19
|
+
has_one: "Cannot delete record because a dependent %{record} exists"
|
20
|
+
has_many: "Cannot delete record because dependent %{record} exist"
|
13
21
|
# Append your own errors here or at the model/attributes scope.
|
14
22
|
|
15
23
|
# You can define own errors for models or model attributes.
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Locking
|
3
5
|
# == What is Optimistic Locking
|
@@ -11,7 +13,7 @@ module ActiveRecord
|
|
11
13
|
#
|
12
14
|
# == Usage
|
13
15
|
#
|
14
|
-
# Active
|
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.
|
@@ -40,17 +42,18 @@ module ActiveRecord
|
|
40
42
|
# This locking mechanism will function inside a single Ruby process. To make it work across all
|
41
43
|
# web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
|
42
44
|
#
|
43
|
-
# You must ensure that your database schema defaults the +lock_version+ column to 0.
|
44
|
-
#
|
45
45
|
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
|
46
|
-
# To override the name of the +lock_version+ column,
|
47
|
-
#
|
46
|
+
# To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
|
47
|
+
#
|
48
|
+
# class Person < ActiveRecord::Base
|
49
|
+
# self.locking_column = :lock_person
|
50
|
+
# end
|
51
|
+
#
|
48
52
|
module Optimistic
|
49
53
|
extend ActiveSupport::Concern
|
50
54
|
|
51
55
|
included do
|
52
|
-
|
53
|
-
self.lock_optimistically = true
|
56
|
+
class_attribute :lock_optimistically, instance_writer: false, default: true
|
54
57
|
end
|
55
58
|
|
56
59
|
def locking_enabled? #:nodoc:
|
@@ -58,127 +61,137 @@ module ActiveRecord
|
|
58
61
|
end
|
59
62
|
|
60
63
|
private
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
64
|
+
def _create_record(attribute_names = self.attribute_names, *)
|
65
|
+
if locking_enabled?
|
66
|
+
# We always want to persist the locking version, even if we don't detect
|
67
|
+
# a change from the default, since the database might have no default
|
68
|
+
attribute_names |= [self.class.locking_column]
|
69
|
+
end
|
70
|
+
super
|
65
71
|
end
|
66
72
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
previous_lock_value = send(lock_col).to_i
|
73
|
-
increment_lock
|
73
|
+
def _touch_row(attribute_names, time)
|
74
|
+
super
|
75
|
+
ensure
|
76
|
+
clear_attribute_change(self.class.locking_column) if locking_enabled?
|
77
|
+
end
|
74
78
|
|
75
|
-
|
76
|
-
|
79
|
+
def _update_row(attribute_names, attempted_action = "update")
|
80
|
+
return super unless locking_enabled?
|
77
81
|
|
78
82
|
begin
|
79
|
-
|
83
|
+
locking_column = self.class.locking_column
|
84
|
+
previous_lock_value = read_attribute_before_type_cast(locking_column)
|
85
|
+
attribute_names << locking_column
|
80
86
|
|
81
|
-
|
82
|
-
relation.table[self.class.primary_key].eq(id).and(
|
83
|
-
relation.table[lock_col].eq(quote_value(previous_lock_value, self.class.columns_hash[lock_col]))
|
84
|
-
)
|
85
|
-
).arel.compile_update(arel_attributes_values(false, false, attribute_names))
|
87
|
+
self[locking_column] += 1
|
86
88
|
|
87
|
-
affected_rows =
|
89
|
+
affected_rows = self.class._update_record(
|
90
|
+
attributes_with_values(attribute_names),
|
91
|
+
self.class.primary_key => id_in_database,
|
92
|
+
locking_column => previous_lock_value
|
93
|
+
)
|
88
94
|
|
89
|
-
|
90
|
-
raise ActiveRecord::StaleObjectError.new(self,
|
95
|
+
if affected_rows != 1
|
96
|
+
raise ActiveRecord::StaleObjectError.new(self, attempted_action)
|
91
97
|
end
|
92
98
|
|
93
99
|
affected_rows
|
94
100
|
|
95
|
-
# If something went wrong, revert the
|
101
|
+
# If something went wrong, revert the locking_column value.
|
96
102
|
rescue Exception
|
97
|
-
|
103
|
+
self[locking_column] = previous_lock_value.to_i
|
98
104
|
raise
|
99
105
|
end
|
100
106
|
end
|
101
107
|
|
102
|
-
def
|
108
|
+
def destroy_row
|
103
109
|
return super unless locking_enabled?
|
104
110
|
|
105
|
-
|
111
|
+
locking_column = self.class.locking_column
|
106
112
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
and(table[lock_col].eq(send(lock_col).to_i))
|
113
|
+
affected_rows = self.class._delete_record(
|
114
|
+
self.class.primary_key => id_in_database,
|
115
|
+
locking_column => read_attribute_before_type_cast(locking_column)
|
116
|
+
)
|
112
117
|
|
113
|
-
|
114
|
-
|
115
|
-
unless affected_rows == 1
|
116
|
-
raise ActiveRecord::StaleObjectError.new(self, "destroy")
|
117
|
-
end
|
118
|
+
if affected_rows != 1
|
119
|
+
raise ActiveRecord::StaleObjectError.new(self, "destroy")
|
118
120
|
end
|
119
121
|
|
120
|
-
|
121
|
-
freeze
|
122
|
+
affected_rows
|
122
123
|
end
|
123
124
|
|
124
|
-
|
125
|
-
|
125
|
+
module ClassMethods
|
126
|
+
DEFAULT_LOCKING_COLUMN = "lock_version"
|
126
127
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
128
|
+
# Returns true if the +lock_optimistically+ flag is set to true
|
129
|
+
# (which it is, by default) and the table includes the
|
130
|
+
# +locking_column+ column (defaults to +lock_version+).
|
131
|
+
def locking_enabled?
|
132
|
+
lock_optimistically && columns_hash[locking_column]
|
133
|
+
end
|
133
134
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
135
|
+
# Set the column to use for optimistic locking. Defaults to +lock_version+.
|
136
|
+
def locking_column=(value)
|
137
|
+
reload_schema_from_cache
|
138
|
+
@locking_column = value.to_s
|
139
|
+
end
|
138
140
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
# The version column used for optimistic locking. Defaults to +lock_version+.
|
142
|
+
def locking_column
|
143
|
+
@locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
|
144
|
+
@locking_column
|
145
|
+
end
|
143
146
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
end
|
147
|
+
# Reset the column used for optimistic locking back to the +lock_version+ default.
|
148
|
+
def reset_locking_column
|
149
|
+
self.locking_column = DEFAULT_LOCKING_COLUMN
|
150
|
+
end
|
149
151
|
|
150
|
-
|
151
|
-
|
152
|
-
|
152
|
+
# Make sure the lock version column gets updated when counters are
|
153
|
+
# updated.
|
154
|
+
def update_counters(id, counters)
|
155
|
+
counters = counters.merge(locking_column => 1) if locking_enabled?
|
156
|
+
super
|
157
|
+
end
|
153
158
|
|
154
|
-
|
155
|
-
|
156
|
-
|
159
|
+
private
|
160
|
+
|
161
|
+
# We need to apply this decorator here, rather than on module inclusion. The closure
|
162
|
+
# created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
|
163
|
+
# sub class being decorated. As such, changes to `lock_optimistically`, or
|
164
|
+
# `locking_column` would not be picked up.
|
165
|
+
def inherited(subclass)
|
166
|
+
subclass.class_eval do
|
167
|
+
is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
|
168
|
+
decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
|
169
|
+
LockingType.new(type)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
super
|
173
|
+
end
|
157
174
|
end
|
175
|
+
end
|
158
176
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
177
|
+
# In de/serialize we change `nil` to 0, so that we can allow passing
|
178
|
+
# `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError`
|
179
|
+
# during update record.
|
180
|
+
class LockingType < DelegateClass(Type::Value) # :nodoc:
|
181
|
+
def deserialize(value)
|
182
|
+
super.to_i
|
183
|
+
end
|
163
184
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
counters = counters.merge(locking_column => 1) if locking_enabled?
|
168
|
-
super
|
169
|
-
end
|
185
|
+
def serialize(value)
|
186
|
+
super.to_i
|
187
|
+
end
|
170
188
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
# <tt>@attributes</tt> may not have been initialized yet.
|
175
|
-
def initialize_attributes(attributes, options = {}) #:nodoc:
|
176
|
-
if attributes.key?(locking_column) && lock_optimistically
|
177
|
-
attributes[locking_column] ||= 0
|
178
|
-
end
|
189
|
+
def init_with(coder)
|
190
|
+
__setobj__(coder["subtype"])
|
191
|
+
end
|
179
192
|
|
180
|
-
|
181
|
-
|
193
|
+
def encode_with(coder)
|
194
|
+
coder["subtype"] = __getobj__
|
182
195
|
end
|
183
196
|
end
|
184
197
|
end
|
@@ -1,14 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
module Locking
|
3
5
|
# Locking::Pessimistic provides support for row-level locking using
|
4
6
|
# SELECT ... FOR UPDATE and other lock types.
|
5
7
|
#
|
6
|
-
#
|
8
|
+
# Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
|
7
9
|
# lock on the selected rows:
|
8
10
|
# # select * from accounts where id=1 for update
|
9
|
-
# Account.find(1
|
11
|
+
# Account.lock.find(1)
|
10
12
|
#
|
11
|
-
#
|
13
|
+
# Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
|
12
14
|
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
|
13
15
|
#
|
14
16
|
# Account.transaction do
|
@@ -26,7 +28,7 @@ module ActiveRecord
|
|
26
28
|
#
|
27
29
|
# Account.transaction do
|
28
30
|
# # select * from accounts where ...
|
29
|
-
# accounts = Account.where(...)
|
31
|
+
# accounts = Account.where(...)
|
30
32
|
# account1 = accounts.detect { |account| ... }
|
31
33
|
# account2 = accounts.detect { |account| ... }
|
32
34
|
# # select * from accounts where id=? for update
|
@@ -51,20 +53,30 @@ module ActiveRecord
|
|
51
53
|
# end
|
52
54
|
#
|
53
55
|
# Database-specific information on row locking:
|
54
|
-
# MySQL:
|
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
|
|
66
78
|
# Wraps the passed block in a transaction, locking the object
|
67
|
-
# before yielding. You pass
|
79
|
+
# before yielding. You can pass the SQL locking clause
|
68
80
|
# as argument (see <tt>lock!</tt>).
|
69
81
|
def with_lock(lock = true)
|
70
82
|
transaction do
|
@@ -1,11 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveRecord
|
2
4
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
5
|
+
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
6
|
+
|
3
7
|
def self.runtime=(value)
|
4
|
-
|
8
|
+
ActiveRecord::RuntimeRegistry.sql_runtime = value
|
5
9
|
end
|
6
10
|
|
7
11
|
def self.runtime
|
8
|
-
|
12
|
+
ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
|
9
13
|
end
|
10
14
|
|
11
15
|
def self.reset_runtime
|
@@ -13,59 +17,120 @@ module ActiveRecord
|
|
13
17
|
rt
|
14
18
|
end
|
15
19
|
|
16
|
-
def initialize
|
17
|
-
super
|
18
|
-
@odd_or_even = false
|
19
|
-
end
|
20
|
-
|
21
20
|
def sql(event)
|
22
21
|
self.class.runtime += event.duration
|
23
22
|
return unless logger.debug?
|
24
23
|
|
25
24
|
payload = event.payload
|
26
25
|
|
27
|
-
return if
|
26
|
+
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
28
27
|
|
29
|
-
name =
|
30
|
-
|
28
|
+
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
29
|
+
name = "CACHE #{name}" if payload[:cached]
|
30
|
+
sql = payload[:sql]
|
31
31
|
binds = nil
|
32
32
|
|
33
33
|
unless (payload[:binds] || []).empty?
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
else
|
38
|
-
[nil, v]
|
39
|
-
end
|
34
|
+
casted_params = type_casted_binds(payload[:type_casted_binds])
|
35
|
+
binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
|
36
|
+
render_bind(attr, value)
|
40
37
|
}.inspect
|
41
38
|
end
|
42
39
|
|
43
|
-
|
44
|
-
|
45
|
-
sql = color(sql, nil, true)
|
46
|
-
else
|
47
|
-
name = color(name, MAGENTA, true)
|
48
|
-
end
|
40
|
+
name = colorize_payload_name(name, payload[:name])
|
41
|
+
sql = color(sql, sql_color(sql), true)
|
49
42
|
|
50
43
|
debug " #{name} #{sql}#{binds}"
|
51
44
|
end
|
52
45
|
|
53
|
-
|
54
|
-
|
46
|
+
private
|
47
|
+
def type_casted_binds(casted_binds)
|
48
|
+
casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
|
49
|
+
end
|
55
50
|
|
56
|
-
|
57
|
-
|
51
|
+
def render_bind(attr, value)
|
52
|
+
if attr.is_a?(Array)
|
53
|
+
attr = attr.first
|
54
|
+
elsif attr.type.binary? && attr.value
|
55
|
+
value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
|
56
|
+
end
|
58
57
|
|
59
|
-
|
60
|
-
|
58
|
+
[attr && attr.name, value]
|
59
|
+
end
|
61
60
|
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
def colorize_payload_name(name, payload_name)
|
62
|
+
if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
|
63
|
+
color(name, MAGENTA, true)
|
64
|
+
else
|
65
|
+
color(name, CYAN, true)
|
66
|
+
end
|
67
|
+
end
|
65
68
|
|
66
|
-
|
67
|
-
|
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
|
69
134
|
end
|
70
135
|
end
|
71
136
|
|