activerecord 5.2.3 → 6.0.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 +4 -4
- data/CHANGELOG.md +691 -542
- data/MIT-LICENSE +3 -1
- data/README.rdoc +4 -2
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +9 -2
- data/lib/active_record/aggregations.rb +4 -2
- data/lib/active_record/association_relation.rb +15 -6
- data/lib/active_record/associations.rb +20 -15
- data/lib/active_record/associations/association.rb +61 -20
- data/lib/active_record/associations/association_scope.rb +4 -6
- data/lib/active_record/associations/belongs_to_association.rb +36 -42
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +19 -52
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +6 -21
- data/lib/active_record/associations/collection_proxy.rb +12 -15
- data/lib/active_record/associations/foreign_association.rb +7 -0
- data/lib/active_record/associations/has_many_association.rb +2 -10
- data/lib/active_record/associations/has_many_through_association.rb +18 -25
- data/lib/active_record/associations/has_one_association.rb +28 -30
- data/lib/active_record/associations/has_one_through_association.rb +5 -5
- data/lib/active_record/associations/join_dependency.rb +28 -28
- data/lib/active_record/associations/join_dependency/join_association.rb +27 -7
- data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
- data/lib/active_record/associations/preloader.rb +39 -31
- data/lib/active_record/associations/preloader/association.rb +38 -36
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/associations/singular_association.rb +2 -16
- data/lib/active_record/attribute_assignment.rb +7 -10
- data/lib/active_record/attribute_methods.rb +28 -100
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +111 -40
- data/lib/active_record/attribute_methods/primary_key.rb +15 -22
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +15 -53
- data/lib/active_record/attribute_methods/serialization.rb +1 -1
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
- data/lib/active_record/attribute_methods/write.rb +17 -24
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +22 -8
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/callbacks.rb +5 -19
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +126 -19
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +99 -123
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +21 -11
- data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +133 -54
- data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
- data/lib/active_record/connection_adapters/abstract_adapter.rb +187 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +138 -195
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +53 -43
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +75 -13
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +129 -13
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +22 -1
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +55 -53
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +164 -74
- data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +131 -143
- data/lib/active_record/connection_handling.rb +155 -26
- data/lib/active_record/core.rb +103 -59
- data/lib/active_record/counter_cache.rb +4 -29
- data/lib/active_record/database_configurations.rb +233 -0
- data/lib/active_record/database_configurations/database_config.rb +37 -0
- data/lib/active_record/database_configurations/hash_config.rb +50 -0
- data/lib/active_record/database_configurations/url_config.rb +79 -0
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +37 -7
- data/lib/active_record/errors.rb +15 -7
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/fixture_set/model_metadata.rb +33 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +153 -0
- data/lib/active_record/fixture_set/table_rows.rb +47 -0
- data/lib/active_record/fixtures.rb +145 -472
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +13 -3
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +68 -16
- data/lib/active_record/internal_metadata.rb +10 -2
- data/lib/active_record/locking/optimistic.rb +5 -6
- data/lib/active_record/locking/pessimistic.rb +3 -3
- data/lib/active_record/log_subscriber.rb +7 -26
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +100 -81
- data/lib/active_record/migration/command_recorder.rb +50 -6
- data/lib/active_record/migration/compatibility.rb +76 -49
- data/lib/active_record/model_schema.rb +33 -9
- data/lib/active_record/nested_attributes.rb +2 -2
- data/lib/active_record/no_touching.rb +7 -0
- data/lib/active_record/persistence.rb +228 -24
- data/lib/active_record/query_cache.rb +11 -4
- data/lib/active_record/querying.rb +32 -20
- data/lib/active_record/railtie.rb +80 -43
- data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
- data/lib/active_record/railties/controller_runtime.rb +30 -35
- data/lib/active_record/railties/databases.rake +196 -46
- data/lib/active_record/reflection.rb +42 -44
- data/lib/active_record/relation.rb +311 -80
- data/lib/active_record/relation/batches.rb +13 -10
- data/lib/active_record/relation/calculations.rb +58 -51
- data/lib/active_record/relation/delegation.rb +26 -43
- data/lib/active_record/relation/finder_methods.rb +24 -28
- data/lib/active_record/relation/merger.rb +11 -20
- data/lib/active_record/relation/predicate_builder.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
- data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
- data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
- data/lib/active_record/relation/query_attribute.rb +13 -8
- data/lib/active_record/relation/query_methods.rb +219 -79
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +14 -10
- data/lib/active_record/relation/where_clause_factory.rb +1 -2
- data/lib/active_record/result.rb +30 -11
- data/lib/active_record/sanitization.rb +32 -40
- data/lib/active_record/schema.rb +2 -11
- data/lib/active_record/schema_dumper.rb +22 -7
- data/lib/active_record/schema_migration.rb +5 -1
- data/lib/active_record/scoping.rb +8 -8
- data/lib/active_record/scoping/default.rb +6 -7
- data/lib/active_record/scoping/named.rb +19 -15
- data/lib/active_record/statement_cache.rb +32 -5
- data/lib/active_record/store.rb +87 -8
- data/lib/active_record/table_metadata.rb +10 -17
- data/lib/active_record/tasks/database_tasks.rb +194 -25
- data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +224 -0
- data/lib/active_record/timestamp.rb +39 -25
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +57 -66
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type.rb +3 -4
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type_caster/connection.rb +15 -14
- data/lib/active_record/type_caster/map.rb +1 -4
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record/validations/uniqueness.rb +15 -27
- data/lib/arel.rb +58 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes.rb +22 -0
- data/lib/arel/attributes/attribute.rb +37 -0
- data/lib/arel/collectors/bind.rb +24 -0
- data/lib/arel/collectors/composite.rb +31 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +20 -0
- data/lib/arel/collectors/substitute_binds.rb +28 -0
- data/lib/arel/crud.rb +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes.rb +68 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +52 -0
- data/lib/arel/nodes/bind_param.rb +36 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +50 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/delete_statement.rb +45 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +18 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +8 -0
- data/lib/arel/nodes/in.rb +8 -0
- data/lib/arel/nodes/infix_operation.rb +80 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +50 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +27 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +45 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +41 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +257 -0
- data/lib/arel/select_manager.rb +271 -0
- data/lib/arel/table.rb +110 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors.rb +20 -0
- data/lib/arel/visitors/depth_first.rb +204 -0
- data/lib/arel/visitors/dot.rb +297 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +157 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +159 -0
- data/lib/arel/visitors/oracle12.rb +66 -0
- data/lib/arel/visitors/postgresql.rb +110 -0
- data/lib/arel/visitors/sqlite.rb +39 -0
- data/lib/arel/visitors/to_sql.rb +889 -0
- data/lib/arel/visitors/visitor.rb +46 -0
- data/lib/arel/visitors/where_sql.rb +23 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/rails/generators/active_record/migration.rb +14 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +111 -26
- data/lib/active_record/collection_cache_key.rb +0 -53
@@ -73,7 +73,7 @@ module ActiveRecord
|
|
73
73
|
# `skip_time_zone_conversion_for_attributes` would not be picked up.
|
74
74
|
subclass.class_eval do
|
75
75
|
matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) }
|
76
|
-
decorate_matching_attribute_types(matcher,
|
76
|
+
decorate_matching_attribute_types(matcher, "_time_zone_conversion") do |type|
|
77
77
|
TimeZoneConverter.new(type)
|
78
78
|
end
|
79
79
|
end
|
@@ -13,19 +13,16 @@ module ActiveRecord
|
|
13
13
|
private
|
14
14
|
|
15
15
|
def define_method_attribute=(name)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
27
|
-
undef_method :__temp__#{safe_name}=
|
28
|
-
STR
|
16
|
+
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
17
|
+
generated_attribute_methods, name, writer: true,
|
18
|
+
) do |temp_method_name, attr_name_expr|
|
19
|
+
generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
20
|
+
def #{temp_method_name}(value)
|
21
|
+
name = #{attr_name_expr}
|
22
|
+
_write_attribute(name, value)
|
23
|
+
end
|
24
|
+
RUBY
|
25
|
+
end
|
29
26
|
end
|
30
27
|
end
|
31
28
|
|
@@ -33,33 +30,29 @@ module ActiveRecord
|
|
33
30
|
# specified +value+. Empty strings for Integer and Float columns are
|
34
31
|
# turned into +nil+.
|
35
32
|
def write_attribute(attr_name, value)
|
36
|
-
name =
|
37
|
-
|
38
|
-
else
|
39
|
-
attr_name.to_s
|
40
|
-
end
|
33
|
+
name = attr_name.to_s
|
34
|
+
name = self.class.attribute_aliases[name] || name
|
41
35
|
|
42
|
-
|
43
|
-
name = primary_key if name == "id".freeze && primary_key
|
44
|
-
sync_with_transaction_state if name == primary_key
|
36
|
+
name = @primary_key if name == "id" && @primary_key
|
45
37
|
_write_attribute(name, value)
|
46
38
|
end
|
47
39
|
|
48
40
|
# This method exists to avoid the expensive primary_key check internally, without
|
49
41
|
# breaking compatibility with the write_attribute API
|
50
42
|
def _write_attribute(attr_name, value) # :nodoc:
|
43
|
+
sync_with_transaction_state if @transaction_state&.finalized?
|
51
44
|
@attributes.write_from_user(attr_name.to_s, value)
|
52
45
|
value
|
53
46
|
end
|
54
47
|
|
55
48
|
private
|
56
49
|
def write_attribute_without_type_cast(attr_name, value)
|
57
|
-
|
58
|
-
@attributes.write_cast_value(
|
50
|
+
sync_with_transaction_state if @transaction_state&.finalized?
|
51
|
+
@attributes.write_cast_value(attr_name.to_s, value)
|
59
52
|
value
|
60
53
|
end
|
61
54
|
|
62
|
-
#
|
55
|
+
# Dispatch target for <tt>*=</tt> attribute methods.
|
63
56
|
def attribute=(attribute_name, value)
|
64
57
|
_write_attribute(attribute_name, value)
|
65
58
|
end
|
@@ -41,6 +41,9 @@ module ActiveRecord
|
|
41
41
|
# +range+ (PostgreSQL only) specifies that the type should be a range (see the
|
42
42
|
# examples below).
|
43
43
|
#
|
44
|
+
# When using a symbol for +cast_type+, extra options are forwarded to the
|
45
|
+
# constructor of the type object.
|
46
|
+
#
|
44
47
|
# ==== Examples
|
45
48
|
#
|
46
49
|
# The type detected by Active Record can be overridden.
|
@@ -112,6 +115,16 @@ module ActiveRecord
|
|
112
115
|
# my_float_range: 1.0..3.5
|
113
116
|
# }
|
114
117
|
#
|
118
|
+
# Passing options to the type constructor
|
119
|
+
#
|
120
|
+
# # app/models/my_model.rb
|
121
|
+
# class MyModel < ActiveRecord::Base
|
122
|
+
# attribute :small_int, :integer, limit: 2
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# MyModel.create(small_int: 65537)
|
126
|
+
# # => Error: 65537 is out of range for the limit of two bytes
|
127
|
+
#
|
115
128
|
# ==== Creating Custom Types
|
116
129
|
#
|
117
130
|
# Users may also define their own custom types, as long as they respond
|
@@ -149,7 +149,7 @@ module ActiveRecord
|
|
149
149
|
private
|
150
150
|
|
151
151
|
def define_non_cyclic_method(name, &block)
|
152
|
-
return if
|
152
|
+
return if instance_methods(false).include?(name)
|
153
153
|
define_method(name) do |*args|
|
154
154
|
result = true; @_already_called ||= {}
|
155
155
|
# Loop prevention for validation of associations
|
@@ -272,7 +272,7 @@ module ActiveRecord
|
|
272
272
|
# or saved. If +autosave+ is +false+ only new records will be returned,
|
273
273
|
# unless the parent is/was a new record itself.
|
274
274
|
def associated_records_to_validate_or_save(association, new_record, autosave)
|
275
|
-
if new_record
|
275
|
+
if new_record || custom_validation_context?
|
276
276
|
association && association.target
|
277
277
|
elsif autosave
|
278
278
|
association.target.find_all(&:changed_for_autosave?)
|
@@ -304,7 +304,7 @@ module ActiveRecord
|
|
304
304
|
def validate_single_association(reflection)
|
305
305
|
association = association_instance_get(reflection.name)
|
306
306
|
record = association && association.reader
|
307
|
-
association_valid?(reflection, record) if record
|
307
|
+
association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
|
308
308
|
end
|
309
309
|
|
310
310
|
# Validate the associated records if <tt>:validate</tt> or
|
@@ -324,7 +324,7 @@ module ActiveRecord
|
|
324
324
|
def association_valid?(reflection, record, index = nil)
|
325
325
|
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
|
326
326
|
|
327
|
-
context = validation_context
|
327
|
+
context = validation_context if custom_validation_context?
|
328
328
|
|
329
329
|
unless valid = record.valid?(context)
|
330
330
|
if reflection.options[:autosave]
|
@@ -382,10 +382,14 @@ module ActiveRecord
|
|
382
382
|
if association = association_instance_get(reflection.name)
|
383
383
|
autosave = reflection.options[:autosave]
|
384
384
|
|
385
|
+
# By saving the instance variable in a local variable,
|
386
|
+
# we make the whole callback re-entrant.
|
387
|
+
new_record_before_save = @new_record_before_save
|
388
|
+
|
385
389
|
# reconstruct the scope now that we know the owner's id
|
386
390
|
association.reset_scope
|
387
391
|
|
388
|
-
if records = associated_records_to_validate_or_save(association,
|
392
|
+
if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
|
389
393
|
if autosave
|
390
394
|
records_to_destroy = records.select(&:marked_for_destruction?)
|
391
395
|
records_to_destroy.each { |record| association.destroy(record) }
|
@@ -397,7 +401,7 @@ module ActiveRecord
|
|
397
401
|
|
398
402
|
saved = true
|
399
403
|
|
400
|
-
if autosave != false && (
|
404
|
+
if autosave != false && (new_record_before_save || record.new_record?)
|
401
405
|
if autosave
|
402
406
|
saved = association.insert_record(record, false)
|
403
407
|
elsif !reflection.nested?
|
@@ -412,7 +416,7 @@ module ActiveRecord
|
|
412
416
|
saved = record.save(validate: false)
|
413
417
|
end
|
414
418
|
|
415
|
-
raise
|
419
|
+
raise(RecordInvalid.new(association.owner)) unless saved
|
416
420
|
end
|
417
421
|
end
|
418
422
|
end
|
@@ -457,10 +461,16 @@ module ActiveRecord
|
|
457
461
|
# If the record is new or it has changed, returns true.
|
458
462
|
def record_changed?(reflection, record, key)
|
459
463
|
record.new_record? ||
|
460
|
-
|
464
|
+
association_foreign_key_changed?(reflection, record, key) ||
|
461
465
|
record.will_save_change_to_attribute?(reflection.foreign_key)
|
462
466
|
end
|
463
467
|
|
468
|
+
def association_foreign_key_changed?(reflection, record, key)
|
469
|
+
return false if reflection.through_reflection?
|
470
|
+
|
471
|
+
record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
|
472
|
+
end
|
473
|
+
|
464
474
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
465
475
|
#
|
466
476
|
# In addition, it will destroy the association if it was marked for destruction.
|
@@ -489,6 +499,10 @@ module ActiveRecord
|
|
489
499
|
end
|
490
500
|
end
|
491
501
|
|
502
|
+
def custom_validation_context?
|
503
|
+
validation_context && [:create, :update].exclude?(validation_context)
|
504
|
+
end
|
505
|
+
|
492
506
|
def _ensure_no_duplicate_errors
|
493
507
|
errors.messages.each_key do |attribute|
|
494
508
|
errors[attribute].uniq!
|
data/lib/active_record/base.rb
CHANGED
@@ -9,7 +9,6 @@ require "active_support/core_ext/module/attribute_accessors"
|
|
9
9
|
require "active_support/core_ext/array/extract_options"
|
10
10
|
require "active_support/core_ext/hash/deep_merge"
|
11
11
|
require "active_support/core_ext/hash/slice"
|
12
|
-
require "active_support/core_ext/hash/transform_values"
|
13
12
|
require "active_support/core_ext/string/behavior"
|
14
13
|
require "active_support/core_ext/kernel/singleton_class"
|
15
14
|
require "active_support/core_ext/module/introspection"
|
@@ -23,6 +22,7 @@ require "active_record/explain_subscriber"
|
|
23
22
|
require "active_record/relation/delegation"
|
24
23
|
require "active_record/attributes"
|
25
24
|
require "active_record/type_caster"
|
25
|
+
require "active_record/database_configurations"
|
26
26
|
|
27
27
|
module ActiveRecord #:nodoc:
|
28
28
|
# = Active Record
|
@@ -288,7 +288,7 @@ module ActiveRecord #:nodoc:
|
|
288
288
|
extend Explain
|
289
289
|
extend Enum
|
290
290
|
extend Delegation::DelegateCache
|
291
|
-
extend
|
291
|
+
extend Aggregations::ClassMethods
|
292
292
|
|
293
293
|
include Core
|
294
294
|
include Persistence
|
@@ -314,7 +314,6 @@ module ActiveRecord #:nodoc:
|
|
314
314
|
include ActiveModel::SecurePassword
|
315
315
|
include AutosaveAssociation
|
316
316
|
include NestedAttributes
|
317
|
-
include Aggregations
|
318
317
|
include Transactions
|
319
318
|
include TouchLater
|
320
319
|
include NoTouching
|
@@ -75,21 +75,7 @@ module ActiveRecord
|
|
75
75
|
# end
|
76
76
|
#
|
77
77
|
# Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
|
78
|
-
# run, both +destroy_author+ and +destroy_readers+ are called.
|
79
|
-
# where the +before_destroy+ method is overridden:
|
80
|
-
#
|
81
|
-
# class Topic < ActiveRecord::Base
|
82
|
-
# def before_destroy() destroy_author end
|
83
|
-
# end
|
84
|
-
#
|
85
|
-
# class Reply < Topic
|
86
|
-
# def before_destroy() destroy_readers end
|
87
|
-
# end
|
88
|
-
#
|
89
|
-
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
|
90
|
-
# So, use the callback macros when you want to ensure that a certain callback is called for the entire
|
91
|
-
# hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant
|
92
|
-
# to decide whether they want to call +super+ and trigger the inherited callbacks.
|
78
|
+
# run, both +destroy_author+ and +destroy_readers+ are called.
|
93
79
|
#
|
94
80
|
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
|
95
81
|
# callbacks before specifying the associations. Otherwise, you might trigger the loading of a
|
@@ -109,7 +95,7 @@ module ActiveRecord
|
|
109
95
|
#
|
110
96
|
# private
|
111
97
|
# def delete_parents
|
112
|
-
# self.class.
|
98
|
+
# self.class.delete_by(parent_id: id)
|
113
99
|
# end
|
114
100
|
# end
|
115
101
|
#
|
@@ -142,7 +128,7 @@ module ActiveRecord
|
|
142
128
|
# end
|
143
129
|
# end
|
144
130
|
#
|
145
|
-
# So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
|
131
|
+
# So you specify the object you want to be messaged on a given callback. When that callback is triggered, the object has
|
146
132
|
# a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
|
147
133
|
# initialization data such as the name of the attribute to work with:
|
148
134
|
#
|
@@ -338,7 +324,7 @@ module ActiveRecord
|
|
338
324
|
|
339
325
|
private
|
340
326
|
|
341
|
-
def create_or_update(
|
327
|
+
def create_or_update(**)
|
342
328
|
_run_save_callbacks { super }
|
343
329
|
end
|
344
330
|
|
@@ -346,7 +332,7 @@ module ActiveRecord
|
|
346
332
|
_run_create_callbacks { super }
|
347
333
|
end
|
348
334
|
|
349
|
-
def _update_record
|
335
|
+
def _update_record
|
350
336
|
_run_update_callbacks { super }
|
351
337
|
end
|
352
338
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "thread"
|
4
4
|
require "concurrent/map"
|
5
5
|
require "monitor"
|
6
|
+
require "weakref"
|
6
7
|
|
7
8
|
module ActiveRecord
|
8
9
|
# Raised when a connection could not be obtained within the connection
|
@@ -19,6 +20,26 @@ module ActiveRecord
|
|
19
20
|
end
|
20
21
|
|
21
22
|
module ConnectionAdapters
|
23
|
+
module AbstractPool # :nodoc:
|
24
|
+
def get_schema_cache(connection)
|
25
|
+
@schema_cache ||= SchemaCache.new(connection)
|
26
|
+
@schema_cache.connection = connection
|
27
|
+
@schema_cache
|
28
|
+
end
|
29
|
+
|
30
|
+
def set_schema_cache(cache)
|
31
|
+
@schema_cache = cache
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class NullPool # :nodoc:
|
36
|
+
include ConnectionAdapters::AbstractPool
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@schema_cache = nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
22
43
|
# Connection pool base class for managing Active Record database
|
23
44
|
# connections.
|
24
45
|
#
|
@@ -185,7 +206,7 @@ module ActiveRecord
|
|
185
206
|
def wait_poll(timeout)
|
186
207
|
@num_waiting += 1
|
187
208
|
|
188
|
-
t0 =
|
209
|
+
t0 = Concurrent.monotonic_time
|
189
210
|
elapsed = 0
|
190
211
|
loop do
|
191
212
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
@@ -194,7 +215,7 @@ module ActiveRecord
|
|
194
215
|
|
195
216
|
return remove if any?
|
196
217
|
|
197
|
-
elapsed =
|
218
|
+
elapsed = Concurrent.monotonic_time - t0
|
198
219
|
if elapsed >= timeout
|
199
220
|
msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
|
200
221
|
[timeout, elapsed]
|
@@ -294,23 +315,59 @@ module ActiveRecord
|
|
294
315
|
@frequency = frequency
|
295
316
|
end
|
296
317
|
|
318
|
+
@mutex = Mutex.new
|
319
|
+
@pools = {}
|
320
|
+
@threads = {}
|
321
|
+
|
322
|
+
class << self
|
323
|
+
def register_pool(pool, frequency) # :nodoc:
|
324
|
+
@mutex.synchronize do
|
325
|
+
unless @threads[frequency]&.alive?
|
326
|
+
@threads[frequency] = spawn_thread(frequency)
|
327
|
+
end
|
328
|
+
@pools[frequency] ||= []
|
329
|
+
@pools[frequency] << WeakRef.new(pool)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
private
|
334
|
+
|
335
|
+
def spawn_thread(frequency)
|
336
|
+
Thread.new(frequency) do |t|
|
337
|
+
running = true
|
338
|
+
while running
|
339
|
+
sleep t
|
340
|
+
@mutex.synchronize do
|
341
|
+
@pools[frequency].select!(&:weakref_alive?)
|
342
|
+
@pools[frequency].each do |p|
|
343
|
+
p.reap
|
344
|
+
p.flush
|
345
|
+
rescue WeakRef::RefError
|
346
|
+
end
|
347
|
+
|
348
|
+
if @pools[frequency].empty?
|
349
|
+
@pools.delete(frequency)
|
350
|
+
@threads.delete(frequency)
|
351
|
+
running = false
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
297
359
|
def run
|
298
360
|
return unless frequency && frequency > 0
|
299
|
-
|
300
|
-
loop do
|
301
|
-
sleep t
|
302
|
-
p.reap
|
303
|
-
p.flush
|
304
|
-
end
|
305
|
-
}
|
361
|
+
self.class.register_pool(pool, frequency)
|
306
362
|
end
|
307
363
|
end
|
308
364
|
|
309
365
|
include MonitorMixin
|
310
366
|
include QueryCache::ConnectionPoolConfiguration
|
367
|
+
include ConnectionAdapters::AbstractPool
|
311
368
|
|
312
369
|
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
|
313
|
-
attr_reader :spec, :
|
370
|
+
attr_reader :spec, :size, :reaper
|
314
371
|
|
315
372
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
316
373
|
# object which describes database connection information (e.g. adapter,
|
@@ -379,7 +436,7 @@ module ActiveRecord
|
|
379
436
|
# #connection can be called any number of times; the connection is
|
380
437
|
# held in a cache keyed by a thread.
|
381
438
|
def connection
|
382
|
-
@thread_cached_conns[connection_cache_key(
|
439
|
+
@thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
|
383
440
|
end
|
384
441
|
|
385
442
|
# Returns true if there is an open connection being used for the current thread.
|
@@ -388,7 +445,7 @@ module ActiveRecord
|
|
388
445
|
# #connection or #with_connection methods. Connections obtained through
|
389
446
|
# #checkout will not be detected by #active_connection?
|
390
447
|
def active_connection?
|
391
|
-
@thread_cached_conns[connection_cache_key(
|
448
|
+
@thread_cached_conns[connection_cache_key(current_thread)]
|
392
449
|
end
|
393
450
|
|
394
451
|
# Signal that the thread is finished with the current connection.
|
@@ -423,6 +480,21 @@ module ActiveRecord
|
|
423
480
|
synchronize { @connections.any? }
|
424
481
|
end
|
425
482
|
|
483
|
+
# Returns an array containing the connections currently in the pool.
|
484
|
+
# Access to the array does not require synchronization on the pool because
|
485
|
+
# the array is newly created and not retained by the pool.
|
486
|
+
#
|
487
|
+
# However; this method bypasses the ConnectionPool's thread-safe connection
|
488
|
+
# access pattern. A returned connection may be owned by another thread,
|
489
|
+
# unowned, or by happen-stance owned by the calling thread.
|
490
|
+
#
|
491
|
+
# Calling methods on a connection without ownership is subject to the
|
492
|
+
# thread-safety guarantees of the underlying method. Many of the methods
|
493
|
+
# on connection adapter classes are inherently multi-thread unsafe.
|
494
|
+
def connections
|
495
|
+
synchronize { @connections.dup }
|
496
|
+
end
|
497
|
+
|
426
498
|
# Disconnects all connections in the pool, and clears the pool.
|
427
499
|
#
|
428
500
|
# Raises:
|
@@ -578,6 +650,7 @@ module ActiveRecord
|
|
578
650
|
# or a thread dies unexpectedly.
|
579
651
|
def reap
|
580
652
|
stale_connections = synchronize do
|
653
|
+
return unless @connections
|
581
654
|
@connections.select do |conn|
|
582
655
|
conn.in_use? && !conn.owner.alive?
|
583
656
|
end.each do |conn|
|
@@ -602,6 +675,7 @@ module ActiveRecord
|
|
602
675
|
return if minimum_idle.nil?
|
603
676
|
|
604
677
|
idle_connections = synchronize do
|
678
|
+
return unless @connections
|
605
679
|
@connections.select do |conn|
|
606
680
|
!conn.in_use? && conn.seconds_idle >= minimum_idle
|
607
681
|
end.each do |conn|
|
@@ -668,6 +742,10 @@ module ActiveRecord
|
|
668
742
|
thread
|
669
743
|
end
|
670
744
|
|
745
|
+
def current_thread
|
746
|
+
@lock_thread || Thread.current
|
747
|
+
end
|
748
|
+
|
671
749
|
# Take control of all existing connections so a "group" action such as
|
672
750
|
# reload/disconnect can be performed safely. It is no longer enough to
|
673
751
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
@@ -686,13 +764,13 @@ module ActiveRecord
|
|
686
764
|
end
|
687
765
|
|
688
766
|
newly_checked_out = []
|
689
|
-
timeout_time =
|
767
|
+
timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
|
690
768
|
|
691
769
|
@available.with_a_bias_for(Thread.current) do
|
692
770
|
loop do
|
693
771
|
synchronize do
|
694
772
|
return if collected_conns.size == @connections.size && @now_connecting == 0
|
695
|
-
remaining_timeout = timeout_time -
|
773
|
+
remaining_timeout = timeout_time - Concurrent.monotonic_time
|
696
774
|
remaining_timeout = 0 if remaining_timeout < 0
|
697
775
|
conn = checkout_for_exclusive_access(remaining_timeout)
|
698
776
|
collected_conns << conn
|
@@ -731,7 +809,7 @@ module ActiveRecord
|
|
731
809
|
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
|
732
810
|
# rescue block, because doing so would put it outside of synchronize section, without
|
733
811
|
# being in a critical section thread_report might become inaccurate
|
734
|
-
msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
|
812
|
+
msg = +"could not obtain ownership of all database connections in #{checkout_timeout} seconds"
|
735
813
|
|
736
814
|
thread_report = []
|
737
815
|
@connections.each do |conn|
|
@@ -809,7 +887,7 @@ module ActiveRecord
|
|
809
887
|
|
810
888
|
def new_connection
|
811
889
|
Base.send(spec.adapter_method, spec.config).tap do |conn|
|
812
|
-
conn.
|
890
|
+
conn.check_version
|
813
891
|
end
|
814
892
|
end
|
815
893
|
|
@@ -946,6 +1024,26 @@ module ActiveRecord
|
|
946
1024
|
ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
|
947
1025
|
end
|
948
1026
|
|
1027
|
+
def prevent_writes # :nodoc:
|
1028
|
+
Thread.current[:prevent_writes]
|
1029
|
+
end
|
1030
|
+
|
1031
|
+
def prevent_writes=(prevent_writes) # :nodoc:
|
1032
|
+
Thread.current[:prevent_writes] = prevent_writes
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
# Prevent writing to the database regardless of role.
|
1036
|
+
#
|
1037
|
+
# In some cases you may want to prevent writes to the database
|
1038
|
+
# even if you are on a database that can write. `while_preventing_writes`
|
1039
|
+
# will prevent writes to the database for the duration of the block.
|
1040
|
+
def while_preventing_writes(enabled = true)
|
1041
|
+
original, self.prevent_writes = self.prevent_writes, enabled
|
1042
|
+
yield
|
1043
|
+
ensure
|
1044
|
+
self.prevent_writes = original
|
1045
|
+
end
|
1046
|
+
|
949
1047
|
def connection_pool_list
|
950
1048
|
owner_to_pool.values.compact
|
951
1049
|
end
|
@@ -1010,15 +1108,24 @@ module ActiveRecord
|
|
1010
1108
|
# for (not necessarily the current class).
|
1011
1109
|
def retrieve_connection(spec_name) #:nodoc:
|
1012
1110
|
pool = retrieve_connection_pool(spec_name)
|
1013
|
-
|
1111
|
+
|
1112
|
+
unless pool
|
1113
|
+
# multiple database application
|
1114
|
+
if ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
|
1115
|
+
raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
|
1116
|
+
else
|
1117
|
+
raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found."
|
1118
|
+
end
|
1119
|
+
end
|
1120
|
+
|
1014
1121
|
pool.connection
|
1015
1122
|
end
|
1016
1123
|
|
1017
1124
|
# Returns true if a connection that's accessible to this class has
|
1018
1125
|
# already been opened.
|
1019
1126
|
def connected?(spec_name)
|
1020
|
-
|
1021
|
-
|
1127
|
+
pool = retrieve_connection_pool(spec_name)
|
1128
|
+
pool && pool.connected?
|
1022
1129
|
end
|
1023
1130
|
|
1024
1131
|
# Remove the connection for this class. This will close the active
|