activerecord 5.2.3 → 6.0.0.rc2
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 +605 -554
- 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/associations.rb +19 -14
- data/lib/active_record/associations/association.rb +52 -19
- 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 +24 -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 +15 -5
- 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 +111 -19
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +95 -123
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +20 -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 +132 -53
- data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
- data/lib/active_record/connection_adapters/abstract_adapter.rb +172 -41
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +128 -194
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +73 -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 +24 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -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/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 +160 -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 +118 -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 +127 -143
- data/lib/active_record/connection_handling.rb +149 -27
- data/lib/active_record/core.rb +100 -60
- data/lib/active_record/counter_cache.rb +4 -29
- data/lib/active_record/database_configurations.rb +204 -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 +28 -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 +140 -472
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +13 -3
- data/lib/active_record/insert_all.rb +180 -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 +90 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +87 -76
- 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 +30 -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 +193 -46
- data/lib/active_record/reflection.rb +42 -44
- data/lib/active_record/relation.rb +310 -80
- data/lib/active_record/relation/batches.rb +13 -10
- data/lib/active_record/relation/calculations.rb +58 -49
- data/lib/active_record/relation/delegation.rb +26 -43
- data/lib/active_record/relation/finder_methods.rb +14 -27
- 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 +182 -68
- 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 +159 -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 +38 -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 +56 -65
- 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/uniqueness.rb +15 -27
- data/lib/arel.rb +51 -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 +109 -27
- 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
|
@@ -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.
|
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,51 @@ module ActiveRecord
|
|
294
315
|
@frequency = frequency
|
295
316
|
end
|
296
317
|
|
318
|
+
@mutex = Mutex.new
|
319
|
+
@pools = {}
|
320
|
+
|
321
|
+
class << self
|
322
|
+
def register_pool(pool, frequency) # :nodoc:
|
323
|
+
@mutex.synchronize do
|
324
|
+
unless @pools.key?(frequency)
|
325
|
+
@pools[frequency] = []
|
326
|
+
spawn_thread(frequency)
|
327
|
+
end
|
328
|
+
@pools[frequency] << WeakRef.new(pool)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
private
|
333
|
+
|
334
|
+
def spawn_thread(frequency)
|
335
|
+
Thread.new(frequency) do |t|
|
336
|
+
loop do
|
337
|
+
sleep t
|
338
|
+
@mutex.synchronize do
|
339
|
+
@pools[frequency].select!(&:weakref_alive?)
|
340
|
+
@pools[frequency].each do |p|
|
341
|
+
p.reap
|
342
|
+
p.flush
|
343
|
+
rescue WeakRef::RefError
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
297
351
|
def run
|
298
352
|
return unless frequency && frequency > 0
|
299
|
-
|
300
|
-
loop do
|
301
|
-
sleep t
|
302
|
-
p.reap
|
303
|
-
p.flush
|
304
|
-
end
|
305
|
-
}
|
353
|
+
self.class.register_pool(pool, frequency)
|
306
354
|
end
|
307
355
|
end
|
308
356
|
|
309
357
|
include MonitorMixin
|
310
358
|
include QueryCache::ConnectionPoolConfiguration
|
359
|
+
include ConnectionAdapters::AbstractPool
|
311
360
|
|
312
361
|
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
|
313
|
-
attr_reader :spec, :
|
362
|
+
attr_reader :spec, :size, :reaper
|
314
363
|
|
315
364
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
316
365
|
# object which describes database connection information (e.g. adapter,
|
@@ -379,7 +428,7 @@ module ActiveRecord
|
|
379
428
|
# #connection can be called any number of times; the connection is
|
380
429
|
# held in a cache keyed by a thread.
|
381
430
|
def connection
|
382
|
-
@thread_cached_conns[connection_cache_key(
|
431
|
+
@thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
|
383
432
|
end
|
384
433
|
|
385
434
|
# Returns true if there is an open connection being used for the current thread.
|
@@ -388,7 +437,7 @@ module ActiveRecord
|
|
388
437
|
# #connection or #with_connection methods. Connections obtained through
|
389
438
|
# #checkout will not be detected by #active_connection?
|
390
439
|
def active_connection?
|
391
|
-
@thread_cached_conns[connection_cache_key(
|
440
|
+
@thread_cached_conns[connection_cache_key(current_thread)]
|
392
441
|
end
|
393
442
|
|
394
443
|
# Signal that the thread is finished with the current connection.
|
@@ -423,6 +472,21 @@ module ActiveRecord
|
|
423
472
|
synchronize { @connections.any? }
|
424
473
|
end
|
425
474
|
|
475
|
+
# Returns an array containing the connections currently in the pool.
|
476
|
+
# Access to the array does not require synchronization on the pool because
|
477
|
+
# the array is newly created and not retained by the pool.
|
478
|
+
#
|
479
|
+
# However; this method bypasses the ConnectionPool's thread-safe connection
|
480
|
+
# access pattern. A returned connection may be owned by another thread,
|
481
|
+
# unowned, or by happen-stance owned by the calling thread.
|
482
|
+
#
|
483
|
+
# Calling methods on a connection without ownership is subject to the
|
484
|
+
# thread-safety guarantees of the underlying method. Many of the methods
|
485
|
+
# on connection adapter classes are inherently multi-thread unsafe.
|
486
|
+
def connections
|
487
|
+
synchronize { @connections.dup }
|
488
|
+
end
|
489
|
+
|
426
490
|
# Disconnects all connections in the pool, and clears the pool.
|
427
491
|
#
|
428
492
|
# Raises:
|
@@ -668,6 +732,10 @@ module ActiveRecord
|
|
668
732
|
thread
|
669
733
|
end
|
670
734
|
|
735
|
+
def current_thread
|
736
|
+
@lock_thread || Thread.current
|
737
|
+
end
|
738
|
+
|
671
739
|
# Take control of all existing connections so a "group" action such as
|
672
740
|
# reload/disconnect can be performed safely. It is no longer enough to
|
673
741
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
@@ -686,13 +754,13 @@ module ActiveRecord
|
|
686
754
|
end
|
687
755
|
|
688
756
|
newly_checked_out = []
|
689
|
-
timeout_time =
|
757
|
+
timeout_time = Concurrent.monotonic_time + (@checkout_timeout * 2)
|
690
758
|
|
691
759
|
@available.with_a_bias_for(Thread.current) do
|
692
760
|
loop do
|
693
761
|
synchronize do
|
694
762
|
return if collected_conns.size == @connections.size && @now_connecting == 0
|
695
|
-
remaining_timeout = timeout_time -
|
763
|
+
remaining_timeout = timeout_time - Concurrent.monotonic_time
|
696
764
|
remaining_timeout = 0 if remaining_timeout < 0
|
697
765
|
conn = checkout_for_exclusive_access(remaining_timeout)
|
698
766
|
collected_conns << conn
|
@@ -731,7 +799,7 @@ module ActiveRecord
|
|
731
799
|
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
|
732
800
|
# rescue block, because doing so would put it outside of synchronize section, without
|
733
801
|
# being in a critical section thread_report might become inaccurate
|
734
|
-
msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
|
802
|
+
msg = +"could not obtain ownership of all database connections in #{checkout_timeout} seconds"
|
735
803
|
|
736
804
|
thread_report = []
|
737
805
|
@connections.each do |conn|
|
@@ -809,7 +877,7 @@ module ActiveRecord
|
|
809
877
|
|
810
878
|
def new_connection
|
811
879
|
Base.send(spec.adapter_method, spec.config).tap do |conn|
|
812
|
-
conn.
|
880
|
+
conn.check_version
|
813
881
|
end
|
814
882
|
end
|
815
883
|
|
@@ -937,15 +1005,30 @@ module ActiveRecord
|
|
937
1005
|
end
|
938
1006
|
end
|
939
1007
|
|
1008
|
+
attr_reader :prevent_writes
|
1009
|
+
|
940
1010
|
def initialize
|
941
1011
|
# These caches are keyed by spec.name (ConnectionSpecification#name).
|
942
1012
|
@owner_to_pool = ConnectionHandler.create_owner_to_pool
|
1013
|
+
@prevent_writes = false
|
943
1014
|
|
944
1015
|
# Backup finalizer: if the forked child never needed a pool, the above
|
945
1016
|
# early discard has not occurred
|
946
1017
|
ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
|
947
1018
|
end
|
948
1019
|
|
1020
|
+
# Prevent writing to the database regardless of role.
|
1021
|
+
#
|
1022
|
+
# In some cases you may want to prevent writes to the database
|
1023
|
+
# even if you are on a database that can write. `while_preventing_writes`
|
1024
|
+
# will prevent writes to the database for the duration of the block.
|
1025
|
+
def while_preventing_writes
|
1026
|
+
original, @prevent_writes = @prevent_writes, true
|
1027
|
+
yield
|
1028
|
+
ensure
|
1029
|
+
@prevent_writes = original
|
1030
|
+
end
|
1031
|
+
|
949
1032
|
def connection_pool_list
|
950
1033
|
owner_to_pool.values.compact
|
951
1034
|
end
|
@@ -1010,15 +1093,24 @@ module ActiveRecord
|
|
1010
1093
|
# for (not necessarily the current class).
|
1011
1094
|
def retrieve_connection(spec_name) #:nodoc:
|
1012
1095
|
pool = retrieve_connection_pool(spec_name)
|
1013
|
-
|
1096
|
+
|
1097
|
+
unless pool
|
1098
|
+
# multiple database application
|
1099
|
+
if ActiveRecord::Base.connection_handler != ActiveRecord::Base.default_connection_handler
|
1100
|
+
raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found for the '#{ActiveRecord::Base.current_role}' role."
|
1101
|
+
else
|
1102
|
+
raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found."
|
1103
|
+
end
|
1104
|
+
end
|
1105
|
+
|
1014
1106
|
pool.connection
|
1015
1107
|
end
|
1016
1108
|
|
1017
1109
|
# Returns true if a connection that's accessible to this class has
|
1018
1110
|
# already been opened.
|
1019
1111
|
def connected?(spec_name)
|
1020
|
-
|
1021
|
-
|
1112
|
+
pool = retrieve_connection_pool(spec_name)
|
1113
|
+
pool && pool.connected?
|
1022
1114
|
end
|
1023
1115
|
|
1024
1116
|
# Remove the connection for this class. This will close the active
|