activerecord 3.2.19 → 5.0.0
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 +7 -0
- data/CHANGELOG.md +1715 -604
- data/MIT-LICENSE +2 -2
- data/README.rdoc +40 -45
- data/examples/performance.rb +33 -22
- data/examples/simple.rb +3 -4
- data/lib/active_record/aggregations.rb +76 -51
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +54 -40
- data/lib/active_record/associations/association.rb +76 -56
- data/lib/active_record/associations/association_scope.rb +125 -93
- data/lib/active_record/associations/belongs_to_association.rb +57 -28
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +120 -32
- data/lib/active_record/associations/builder/belongs_to.rb +115 -62
- data/lib/active_record/associations/builder/collection_association.rb +61 -53
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
- data/lib/active_record/associations/builder/has_many.rb +9 -65
- data/lib/active_record/associations/builder/has_one.rb +18 -52
- data/lib/active_record/associations/builder/singular_association.rb +18 -19
- data/lib/active_record/associations/collection_association.rb +268 -186
- data/lib/active_record/associations/collection_proxy.rb +1003 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +81 -41
- data/lib/active_record/associations/has_many_through_association.rb +76 -55
- data/lib/active_record/associations/has_one_association.rb +51 -21
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +239 -155
- data/lib/active_record/associations/preloader/association.rb +97 -62
- data/lib/active_record/associations/preloader/collection_association.rb +2 -8
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +75 -33
- data/lib/active_record/associations/preloader.rb +111 -79
- data/lib/active_record/associations/singular_association.rb +35 -13
- data/lib/active_record/associations/through_association.rb +41 -19
- data/lib/active_record/associations.rb +727 -501
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +213 -0
- data/lib/active_record/attribute_assignment.rb +32 -162
- data/lib/active_record/attribute_decorators.rb +67 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -61
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +7 -6
- data/lib/active_record/attribute_methods/read.rb +56 -117
- data/lib/active_record/attribute_methods/serialization.rb +43 -96
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
- data/lib/active_record/attribute_methods/write.rb +34 -45
- data/lib/active_record/attribute_methods.rb +333 -144
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +108 -0
- data/lib/active_record/attribute_set.rb +108 -0
- data/lib/active_record/attributes.rb +265 -0
- data/lib/active_record/autosave_association.rb +285 -223
- data/lib/active_record/base.rb +95 -490
- data/lib/active_record/callbacks.rb +95 -61
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +28 -19
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
- data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
- data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
- data/lib/active_record/connection_adapters/column.rb +30 -259
- data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
- data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
- data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +155 -0
- data/lib/active_record/core.rb +561 -0
- data/lib/active_record/counter_cache.rb +146 -105
- data/lib/active_record/dynamic_matchers.rb +101 -64
- data/lib/active_record/enum.rb +234 -0
- data/lib/active_record/errors.rb +153 -56
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +10 -6
- data/lib/active_record/fixture_set/file.rb +77 -0
- data/lib/active_record/fixtures.rb +355 -232
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +144 -79
- data/lib/active_record/integration.rb +66 -13
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +46 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +77 -56
- data/lib/active_record/locking/pessimistic.rb +6 -6
- data/lib/active_record/log_subscriber.rb +53 -28
- data/lib/active_record/migration/command_recorder.rb +166 -33
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +792 -264
- data/lib/active_record/model_schema.rb +192 -130
- data/lib/active_record/nested_attributes.rb +238 -145
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +89 -0
- data/lib/active_record/persistence.rb +357 -157
- data/lib/active_record/query_cache.rb +22 -43
- data/lib/active_record/querying.rb +34 -23
- data/lib/active_record/railtie.rb +88 -48
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +5 -4
- data/lib/active_record/railties/databases.rake +170 -422
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -5
- data/lib/active_record/reflection.rb +715 -189
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +203 -50
- data/lib/active_record/relation/calculations.rb +203 -194
- data/lib/active_record/relation/delegation.rb +103 -25
- data/lib/active_record/relation/finder_methods.rb +457 -261
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +167 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +153 -48
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +1019 -194
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +46 -150
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +450 -245
- data/lib/active_record/result.rb +104 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +120 -94
- data/lib/active_record/schema.rb +28 -18
- data/lib/active_record/schema_dumper.rb +141 -74
- data/lib/active_record/schema_migration.rb +50 -0
- data/lib/active_record/scoping/default.rb +64 -57
- data/lib/active_record/scoping/named.rb +93 -108
- data/lib/active_record/scoping.rb +73 -121
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +7 -5
- data/lib/active_record/statement_cache.rb +113 -0
- data/lib/active_record/store.rb +173 -15
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +313 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
- data/lib/active_record/timestamp.rb +42 -24
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +233 -105
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +7 -0
- data/lib/active_record/type/date_time.rb +7 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +63 -0
- data/lib/active_record/type/time.rb +20 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type.rb +72 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +33 -18
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +66 -0
- data/lib/active_record/validations/uniqueness.rb +128 -68
- data/lib/active_record/validations.rb +48 -40
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +71 -47
- data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
- data/lib/rails/generators/active_record/migration.rb +18 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +188 -134
- 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/has_and_belongs_to_many.rb +0 -60
- 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/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/model/templates/migration.rb +0 -15
- 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
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
11
11
|
#
|
12
12
|
# == Usage
|
13
13
|
#
|
14
|
-
# Active
|
14
|
+
# Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
|
15
15
|
# record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
|
16
16
|
# will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
|
17
17
|
#
|
@@ -22,7 +22,7 @@ module ActiveRecord
|
|
22
22
|
# p1.save
|
23
23
|
#
|
24
24
|
# p2.first_name = "should fail"
|
25
|
-
# p2.save # Raises
|
25
|
+
# p2.save # Raises an ActiveRecord::StaleObjectError
|
26
26
|
#
|
27
27
|
# Optimistic locking will also check for stale data when objects are destroyed. Example:
|
28
28
|
#
|
@@ -32,7 +32,7 @@ module ActiveRecord
|
|
32
32
|
# p1.first_name = "Michael"
|
33
33
|
# p1.save
|
34
34
|
#
|
35
|
-
# p2.destroy # Raises
|
35
|
+
# p2.destroy # Raises an ActiveRecord::StaleObjectError
|
36
36
|
#
|
37
37
|
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
|
38
38
|
# or otherwise apply the business logic needed to resolve the conflict.
|
@@ -40,16 +40,18 @@ module ActiveRecord
|
|
40
40
|
# This locking mechanism will function inside a single Ruby process. To make it work across all
|
41
41
|
# web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
|
42
42
|
#
|
43
|
-
# You must ensure that your database schema defaults the +lock_version+ column to 0.
|
44
|
-
#
|
45
43
|
# 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
|
-
#
|
44
|
+
# To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
|
45
|
+
#
|
46
|
+
# class Person < ActiveRecord::Base
|
47
|
+
# self.locking_column = :lock_person
|
48
|
+
# end
|
49
|
+
#
|
48
50
|
module Optimistic
|
49
51
|
extend ActiveSupport::Concern
|
50
52
|
|
51
53
|
included do
|
52
|
-
|
54
|
+
class_attribute :lock_optimistically, instance_writer: false
|
53
55
|
self.lock_optimistically = true
|
54
56
|
end
|
55
57
|
|
@@ -64,7 +66,16 @@ module ActiveRecord
|
|
64
66
|
send(lock_col + '=', previous_lock_value + 1)
|
65
67
|
end
|
66
68
|
|
67
|
-
def
|
69
|
+
def _create_record(attribute_names = self.attribute_names, *) # :nodoc:
|
70
|
+
if locking_enabled?
|
71
|
+
# We always want to persist the locking version, even if we don't detect
|
72
|
+
# a change from the default, since the database might have no default
|
73
|
+
attribute_names |= [self.class.locking_column]
|
74
|
+
end
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
def _update_record(attribute_names = self.attribute_names) #:nodoc:
|
68
79
|
return super unless locking_enabled?
|
69
80
|
return 0 if attribute_names.empty?
|
70
81
|
|
@@ -78,13 +89,14 @@ module ActiveRecord
|
|
78
89
|
begin
|
79
90
|
relation = self.class.unscoped
|
80
91
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
92
|
+
affected_rows = relation.where(
|
93
|
+
self.class.primary_key => id,
|
94
|
+
lock_col => previous_lock_value,
|
95
|
+
).update_all(
|
96
|
+
attributes_for_update(attribute_names).map do |name|
|
97
|
+
[name, _read_attribute(name)]
|
98
|
+
end.to_h
|
99
|
+
)
|
88
100
|
|
89
101
|
unless affected_rows == 1
|
90
102
|
raise ActiveRecord::StaleObjectError.new(self, "update")
|
@@ -99,26 +111,25 @@ module ActiveRecord
|
|
99
111
|
end
|
100
112
|
end
|
101
113
|
|
102
|
-
def
|
103
|
-
|
114
|
+
def destroy_row
|
115
|
+
affected_rows = super
|
104
116
|
|
105
|
-
|
117
|
+
if locking_enabled? && affected_rows != 1
|
118
|
+
raise ActiveRecord::StaleObjectError.new(self, "destroy")
|
119
|
+
end
|
106
120
|
|
107
|
-
|
108
|
-
|
109
|
-
lock_col = self.class.locking_column
|
110
|
-
predicate = table[self.class.primary_key].eq(id).
|
111
|
-
and(table[lock_col].eq(send(lock_col).to_i))
|
121
|
+
affected_rows
|
122
|
+
end
|
112
123
|
|
113
|
-
|
124
|
+
def relation_for_destroy
|
125
|
+
relation = super
|
114
126
|
|
115
|
-
|
116
|
-
|
117
|
-
|
127
|
+
if locking_enabled?
|
128
|
+
locking_column = self.class.locking_column
|
129
|
+
relation = relation.where(locking_column => _read_attribute(locking_column))
|
118
130
|
end
|
119
131
|
|
120
|
-
|
121
|
-
freeze
|
132
|
+
relation
|
122
133
|
end
|
123
134
|
|
124
135
|
module ClassMethods
|
@@ -131,31 +142,18 @@ module ActiveRecord
|
|
131
142
|
lock_optimistically && columns_hash[locking_column]
|
132
143
|
end
|
133
144
|
|
134
|
-
def locking_column=(value)
|
135
|
-
@original_locking_column = @locking_column if defined?(@locking_column)
|
136
|
-
@locking_column = value.to_s
|
137
|
-
end
|
138
|
-
|
139
145
|
# Set the column to use for optimistic locking. Defaults to +lock_version+.
|
140
|
-
def
|
141
|
-
|
146
|
+
def locking_column=(value)
|
147
|
+
reload_schema_from_cache
|
148
|
+
@locking_column = value.to_s
|
142
149
|
end
|
143
150
|
|
144
151
|
# The version column used for optimistic locking. Defaults to +lock_version+.
|
145
152
|
def locking_column
|
146
|
-
|
153
|
+
@locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column)
|
147
154
|
@locking_column
|
148
155
|
end
|
149
156
|
|
150
|
-
def original_locking_column #:nodoc:
|
151
|
-
deprecated_original_property_getter :locking_column
|
152
|
-
end
|
153
|
-
|
154
|
-
# Quote the column name used for optimistic locking.
|
155
|
-
def quoted_locking_column
|
156
|
-
connection.quote_column_name(locking_column)
|
157
|
-
end
|
158
|
-
|
159
157
|
# Reset the column used for optimistic locking back to the +lock_version+ default.
|
160
158
|
def reset_locking_column
|
161
159
|
self.locking_column = DEFAULT_LOCKING_COLUMN
|
@@ -168,18 +166,41 @@ module ActiveRecord
|
|
168
166
|
super
|
169
167
|
end
|
170
168
|
|
171
|
-
|
172
|
-
|
173
|
-
#
|
174
|
-
#
|
175
|
-
|
176
|
-
|
177
|
-
|
169
|
+
private
|
170
|
+
|
171
|
+
# We need to apply this decorator here, rather than on module inclusion. The closure
|
172
|
+
# created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the
|
173
|
+
# sub class being decorated. As such, changes to `lock_optimistically`, or
|
174
|
+
# `locking_column` would not be picked up.
|
175
|
+
def inherited(subclass)
|
176
|
+
subclass.class_eval do
|
177
|
+
is_lock_column = ->(name, _) { lock_optimistically && name == locking_column }
|
178
|
+
decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type|
|
179
|
+
LockingType.new(type)
|
180
|
+
end
|
178
181
|
end
|
179
|
-
|
180
|
-
attributes
|
182
|
+
super
|
181
183
|
end
|
182
184
|
end
|
183
185
|
end
|
186
|
+
|
187
|
+
class LockingType < DelegateClass(Type::Value) # :nodoc:
|
188
|
+
def deserialize(value)
|
189
|
+
# `nil` *should* be changed to 0
|
190
|
+
super.to_i
|
191
|
+
end
|
192
|
+
|
193
|
+
def serialize(value)
|
194
|
+
super.to_i
|
195
|
+
end
|
196
|
+
|
197
|
+
def init_with(coder)
|
198
|
+
__setobj__(coder['subtype'])
|
199
|
+
end
|
200
|
+
|
201
|
+
def encode_with(coder)
|
202
|
+
coder['subtype'] = __getobj__
|
203
|
+
end
|
204
|
+
end
|
184
205
|
end
|
185
206
|
end
|
@@ -3,12 +3,12 @@ module ActiveRecord
|
|
3
3
|
# Locking::Pessimistic provides support for row-level locking using
|
4
4
|
# SELECT ... FOR UPDATE and other lock types.
|
5
5
|
#
|
6
|
-
#
|
6
|
+
# Chain <tt>ActiveRecord::Base#find</tt> to <tt>ActiveRecord::QueryMethods#lock</tt> to obtain an exclusive
|
7
7
|
# lock on the selected rows:
|
8
8
|
# # select * from accounts where id=1 for update
|
9
|
-
# Account.find(1
|
9
|
+
# Account.lock.find(1)
|
10
10
|
#
|
11
|
-
#
|
11
|
+
# Call <tt>lock('some locking clause')</tt> to use a database-specific locking clause
|
12
12
|
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example:
|
13
13
|
#
|
14
14
|
# Account.transaction do
|
@@ -26,7 +26,7 @@ module ActiveRecord
|
|
26
26
|
#
|
27
27
|
# Account.transaction do
|
28
28
|
# # select * from accounts where ...
|
29
|
-
# accounts = Account.where(...)
|
29
|
+
# accounts = Account.where(...)
|
30
30
|
# account1 = accounts.detect { |account| ... }
|
31
31
|
# account2 = accounts.detect { |account| ... }
|
32
32
|
# # select * from accounts where id=? for update
|
@@ -51,7 +51,7 @@ module ActiveRecord
|
|
51
51
|
# end
|
52
52
|
#
|
53
53
|
# Database-specific information on row locking:
|
54
|
-
# MySQL: http://dev.mysql.com/doc/refman/5.
|
54
|
+
# MySQL: http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
|
55
55
|
# PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
|
56
56
|
module Pessimistic
|
57
57
|
# Obtain a row lock on this record. Reloads the record to obtain the requested
|
@@ -64,7 +64,7 @@ module ActiveRecord
|
|
64
64
|
end
|
65
65
|
|
66
66
|
# Wraps the passed block in a transaction, locking the object
|
67
|
-
# before yielding. You pass
|
67
|
+
# before yielding. You can pass the SQL locking clause
|
68
68
|
# as argument (see <tt>lock!</tt>).
|
69
69
|
def with_lock(lock = true)
|
70
70
|
transaction do
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
3
|
+
IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
|
4
|
+
|
3
5
|
def self.runtime=(value)
|
4
|
-
|
6
|
+
ActiveRecord::RuntimeRegistry.sql_runtime = value
|
5
7
|
end
|
6
8
|
|
7
9
|
def self.runtime
|
8
|
-
|
10
|
+
ActiveRecord::RuntimeRegistry.sql_runtime ||= 0
|
9
11
|
end
|
10
12
|
|
11
13
|
def self.reset_runtime
|
@@ -15,52 +17,75 @@ module ActiveRecord
|
|
15
17
|
|
16
18
|
def initialize
|
17
19
|
super
|
18
|
-
@
|
20
|
+
@odd = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def render_bind(attribute)
|
24
|
+
value = if attribute.type.binary? && attribute.value
|
25
|
+
if attribute.value.is_a?(Hash)
|
26
|
+
"<#{attribute.value_for_database.to_s.bytesize} bytes of binary data>"
|
27
|
+
else
|
28
|
+
"<#{attribute.value.bytesize} bytes of binary data>"
|
29
|
+
end
|
30
|
+
else
|
31
|
+
attribute.value_for_database
|
32
|
+
end
|
33
|
+
|
34
|
+
[attribute.name, value]
|
19
35
|
end
|
20
36
|
|
21
37
|
def sql(event)
|
22
|
-
self.class.runtime += event.duration
|
23
38
|
return unless logger.debug?
|
24
39
|
|
40
|
+
self.class.runtime += event.duration
|
41
|
+
|
25
42
|
payload = event.payload
|
26
43
|
|
27
|
-
return if
|
44
|
+
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
|
28
45
|
|
29
|
-
name =
|
30
|
-
sql = payload[:sql]
|
46
|
+
name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
|
47
|
+
sql = payload[:sql]
|
31
48
|
binds = nil
|
32
49
|
|
33
50
|
unless (payload[:binds] || []).empty?
|
34
|
-
binds = " " + payload[:binds].map { |
|
35
|
-
if col
|
36
|
-
[col.name, v]
|
37
|
-
else
|
38
|
-
[nil, v]
|
39
|
-
end
|
40
|
-
}.inspect
|
51
|
+
binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
|
41
52
|
end
|
42
53
|
|
43
|
-
|
44
|
-
|
45
|
-
sql = color(sql, nil, true)
|
46
|
-
else
|
47
|
-
name = color(name, MAGENTA, true)
|
48
|
-
end
|
54
|
+
name = colorize_payload_name(name, payload[:name])
|
55
|
+
sql = color(sql, sql_color(sql), true)
|
49
56
|
|
50
57
|
debug " #{name} #{sql}#{binds}"
|
51
58
|
end
|
52
59
|
|
53
|
-
|
54
|
-
return unless logger.debug?
|
55
|
-
|
56
|
-
name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true)
|
57
|
-
line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line]
|
60
|
+
private
|
58
61
|
|
59
|
-
|
62
|
+
def colorize_payload_name(name, payload_name)
|
63
|
+
if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists
|
64
|
+
color(name, MAGENTA, true)
|
65
|
+
else
|
66
|
+
color(name, CYAN, true)
|
67
|
+
end
|
60
68
|
end
|
61
69
|
|
62
|
-
def
|
63
|
-
|
70
|
+
def sql_color(sql)
|
71
|
+
case sql
|
72
|
+
when /\A\s*rollback/mi
|
73
|
+
RED
|
74
|
+
when /select .*for update/mi, /\A\s*lock/mi
|
75
|
+
WHITE
|
76
|
+
when /\A\s*select/i
|
77
|
+
BLUE
|
78
|
+
when /\A\s*insert/i
|
79
|
+
GREEN
|
80
|
+
when /\A\s*update/i
|
81
|
+
YELLOW
|
82
|
+
when /\A\s*delete/i
|
83
|
+
RED
|
84
|
+
when /transaction\s*\Z/i
|
85
|
+
CYAN
|
86
|
+
else
|
87
|
+
MAGENTA
|
88
|
+
end
|
64
89
|
end
|
65
90
|
|
66
91
|
def logger
|
@@ -5,69 +5,150 @@ module ActiveRecord
|
|
5
5
|
# knows how to invert the following commands:
|
6
6
|
#
|
7
7
|
# * add_column
|
8
|
+
# * add_foreign_key
|
8
9
|
# * add_index
|
10
|
+
# * add_reference
|
9
11
|
# * add_timestamps
|
12
|
+
# * change_column
|
13
|
+
# * change_column_default (must supply a :from and :to option)
|
14
|
+
# * change_column_null
|
15
|
+
# * create_join_table
|
10
16
|
# * create_table
|
17
|
+
# * disable_extension
|
18
|
+
# * drop_join_table
|
19
|
+
# * drop_table (must supply a block)
|
20
|
+
# * enable_extension
|
21
|
+
# * remove_column (must supply a type)
|
22
|
+
# * remove_columns (must specify at least one column name or more)
|
23
|
+
# * remove_foreign_key (must supply a second table)
|
24
|
+
# * remove_index
|
25
|
+
# * remove_reference
|
11
26
|
# * remove_timestamps
|
12
27
|
# * rename_column
|
13
28
|
# * rename_index
|
14
29
|
# * rename_table
|
15
30
|
class CommandRecorder
|
16
|
-
|
31
|
+
ReversibleAndIrreversibleMethods = [:create_table, :create_join_table, :rename_table, :add_column, :remove_column,
|
32
|
+
:rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps,
|
33
|
+
:change_column_default, :add_reference, :remove_reference, :transaction,
|
34
|
+
:drop_join_table, :drop_table, :execute_block, :enable_extension, :disable_extension,
|
35
|
+
:change_column, :execute, :remove_columns, :change_column_null,
|
36
|
+
:add_foreign_key, :remove_foreign_key
|
37
|
+
]
|
38
|
+
include JoinTable
|
39
|
+
|
40
|
+
attr_accessor :commands, :delegate, :reverting
|
17
41
|
|
18
42
|
def initialize(delegate = nil)
|
19
43
|
@commands = []
|
20
44
|
@delegate = delegate
|
45
|
+
@reverting = false
|
21
46
|
end
|
22
47
|
|
23
|
-
#
|
48
|
+
# While executing the given block, the recorded will be in reverting mode.
|
49
|
+
# All commands recorded will end up being recorded reverted
|
50
|
+
# and in reverse order.
|
51
|
+
# For example:
|
52
|
+
#
|
53
|
+
# recorder.revert{ recorder.record(:rename_table, [:old, :new]) }
|
54
|
+
# # same effect as recorder.record(:rename_table, [:new, :old])
|
55
|
+
def revert
|
56
|
+
@reverting = !@reverting
|
57
|
+
previous = @commands
|
58
|
+
@commands = []
|
59
|
+
yield
|
60
|
+
ensure
|
61
|
+
@commands = previous.concat(@commands.reverse)
|
62
|
+
@reverting = !@reverting
|
63
|
+
end
|
64
|
+
|
65
|
+
# Record +command+. +command+ should be a method name and arguments.
|
24
66
|
# For example:
|
25
67
|
#
|
26
68
|
# recorder.record(:method_name, [:arg1, :arg2])
|
27
|
-
def record(*command)
|
28
|
-
@
|
69
|
+
def record(*command, &block)
|
70
|
+
if @reverting
|
71
|
+
@commands << inverse_of(*command, &block)
|
72
|
+
else
|
73
|
+
@commands << (command << block)
|
74
|
+
end
|
29
75
|
end
|
30
76
|
|
31
|
-
# Returns
|
32
|
-
# commands stored in +commands+. For example:
|
77
|
+
# Returns the inverse of the given command. For example:
|
33
78
|
#
|
34
|
-
# recorder.
|
35
|
-
#
|
79
|
+
# recorder.inverse_of(:rename_table, [:old, :new])
|
80
|
+
# # => [:rename_table, [:new, :old]]
|
36
81
|
#
|
37
82
|
# This method will raise an +IrreversibleMigration+ exception if it cannot
|
38
|
-
# invert the +
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
83
|
+
# invert the +command+.
|
84
|
+
def inverse_of(command, args, &block)
|
85
|
+
method = :"invert_#{command}"
|
86
|
+
raise IrreversibleMigration, <<-MSG.strip_heredoc unless respond_to?(method, true)
|
87
|
+
This migration uses #{command}, which is not automatically reversible.
|
88
|
+
To make the migration reversible you can either:
|
89
|
+
1. Define #up and #down methods in place of the #change method.
|
90
|
+
2. Use the #reversible method to define reversible behavior.
|
91
|
+
MSG
|
92
|
+
send(method, args, &block)
|
45
93
|
end
|
46
94
|
|
47
95
|
def respond_to?(*args) # :nodoc:
|
48
96
|
super || delegate.respond_to?(*args)
|
49
97
|
end
|
50
98
|
|
51
|
-
|
99
|
+
ReversibleAndIrreversibleMethods.each do |method|
|
52
100
|
class_eval <<-EOV, __FILE__, __LINE__ + 1
|
53
|
-
def #{method}(*args) # def create_table(*args)
|
54
|
-
record(:"#{method}", args) # record(:create_table, args)
|
55
|
-
end
|
101
|
+
def #{method}(*args, &block) # def create_table(*args, &block)
|
102
|
+
record(:"#{method}", args, &block) # record(:create_table, args, &block)
|
103
|
+
end # end
|
56
104
|
EOV
|
57
105
|
end
|
106
|
+
alias :add_belongs_to :add_reference
|
107
|
+
alias :remove_belongs_to :remove_reference
|
108
|
+
|
109
|
+
def change_table(table_name, options = {}) # :nodoc:
|
110
|
+
yield delegate.update_table_definition(table_name, self)
|
111
|
+
end
|
58
112
|
|
59
113
|
private
|
60
114
|
|
61
|
-
|
62
|
-
|
115
|
+
module StraightReversions
|
116
|
+
private
|
117
|
+
{ transaction: :transaction,
|
118
|
+
execute_block: :execute_block,
|
119
|
+
create_table: :drop_table,
|
120
|
+
create_join_table: :drop_join_table,
|
121
|
+
add_column: :remove_column,
|
122
|
+
add_timestamps: :remove_timestamps,
|
123
|
+
add_reference: :remove_reference,
|
124
|
+
enable_extension: :disable_extension
|
125
|
+
}.each do |cmd, inv|
|
126
|
+
[[inv, cmd], [cmd, inv]].uniq.each do |method, inverse|
|
127
|
+
class_eval <<-EOV, __FILE__, __LINE__ + 1
|
128
|
+
def invert_#{method}(args, &block) # def invert_create_table(args, &block)
|
129
|
+
[:#{inverse}, args, block] # [:drop_table, args, block]
|
130
|
+
end # end
|
131
|
+
EOV
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
include StraightReversions
|
137
|
+
|
138
|
+
def invert_drop_table(args, &block)
|
139
|
+
if args.size == 1 && block == nil
|
140
|
+
raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
|
141
|
+
end
|
142
|
+
super
|
63
143
|
end
|
64
144
|
|
65
145
|
def invert_rename_table(args)
|
66
146
|
[:rename_table, args.reverse]
|
67
147
|
end
|
68
148
|
|
69
|
-
def
|
70
|
-
|
149
|
+
def invert_remove_column(args)
|
150
|
+
raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2
|
151
|
+
super
|
71
152
|
end
|
72
153
|
|
73
154
|
def invert_rename_index(args)
|
@@ -80,26 +161,78 @@ module ActiveRecord
|
|
80
161
|
|
81
162
|
def invert_add_index(args)
|
82
163
|
table, columns, options = *args
|
83
|
-
|
84
|
-
|
164
|
+
options ||= {}
|
165
|
+
|
166
|
+
index_name = options[:name]
|
167
|
+
options_hash = index_name ? { name: index_name } : { column: columns }
|
168
|
+
|
85
169
|
[:remove_index, [table, options_hash]]
|
86
170
|
end
|
87
171
|
|
88
|
-
def
|
89
|
-
|
172
|
+
def invert_remove_index(args)
|
173
|
+
table, options_or_column = *args
|
174
|
+
if (options = options_or_column).is_a?(Hash)
|
175
|
+
unless options[:column]
|
176
|
+
raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
|
177
|
+
end
|
178
|
+
options = options.dup
|
179
|
+
[:add_index, [table, options.delete(:column), options]]
|
180
|
+
elsif (column = options_or_column).present?
|
181
|
+
[:add_index, [table, column]]
|
182
|
+
end
|
90
183
|
end
|
91
184
|
|
92
|
-
|
93
|
-
|
185
|
+
alias :invert_add_belongs_to :invert_add_reference
|
186
|
+
alias :invert_remove_belongs_to :invert_remove_reference
|
187
|
+
|
188
|
+
def invert_change_column_default(args)
|
189
|
+
table, column, options = *args
|
190
|
+
|
191
|
+
unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to)
|
192
|
+
raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option."
|
193
|
+
end
|
194
|
+
|
195
|
+
[:change_column_default, [table, column, from: options[:to], to: options[:from]]]
|
196
|
+
end
|
197
|
+
|
198
|
+
def invert_change_column_null(args)
|
199
|
+
args[2] = !args[2]
|
200
|
+
[:change_column_null, args]
|
201
|
+
end
|
202
|
+
|
203
|
+
def invert_add_foreign_key(args)
|
204
|
+
from_table, to_table, add_options = args
|
205
|
+
add_options ||= {}
|
206
|
+
|
207
|
+
if add_options[:name]
|
208
|
+
options = { name: add_options[:name] }
|
209
|
+
elsif add_options[:column]
|
210
|
+
options = { column: add_options[:column] }
|
211
|
+
else
|
212
|
+
options = to_table
|
213
|
+
end
|
214
|
+
|
215
|
+
[:remove_foreign_key, [from_table, options]]
|
216
|
+
end
|
217
|
+
|
218
|
+
def invert_remove_foreign_key(args)
|
219
|
+
from_table, to_table, remove_options = args
|
220
|
+
raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash)
|
221
|
+
|
222
|
+
reversed_args = [from_table, to_table]
|
223
|
+
reversed_args << remove_options if remove_options
|
224
|
+
|
225
|
+
[:add_foreign_key, reversed_args]
|
94
226
|
end
|
95
227
|
|
96
228
|
# Forwards any missing method call to the \target.
|
97
229
|
def method_missing(method, *args, &block)
|
98
|
-
@delegate.
|
99
|
-
|
100
|
-
|
230
|
+
if @delegate.respond_to?(method)
|
231
|
+
@delegate.send(method, *args, &block)
|
232
|
+
else
|
233
|
+
super
|
234
|
+
end
|
101
235
|
end
|
102
|
-
|
103
236
|
end
|
104
237
|
end
|
105
238
|
end
|