activerecord 5.2.6 → 6.1.3.2
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 +1038 -571
- data/MIT-LICENSE +3 -1
- data/README.rdoc +7 -5
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +13 -12
- data/lib/active_record/aggregations.rb +9 -8
- data/lib/active_record/association_relation.rb +30 -10
- data/lib/active_record/associations.rb +137 -25
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +95 -42
- data/lib/active_record/associations/association_scope.rb +23 -21
- data/lib/active_record/associations/belongs_to_association.rb +54 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -6
- data/lib/active_record/associations/builder/association.rb +45 -22
- data/lib/active_record/associations/builder/belongs_to.rb +29 -59
- data/lib/active_record/associations/builder/collection_association.rb +8 -17
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -2
- data/lib/active_record/associations/builder/has_one.rb +33 -2
- data/lib/active_record/associations/builder/singular_association.rb +3 -1
- data/lib/active_record/associations/collection_association.rb +31 -29
- data/lib/active_record/associations/collection_proxy.rb +25 -21
- data/lib/active_record/associations/foreign_association.rb +20 -0
- data/lib/active_record/associations/has_many_association.rb +26 -13
- data/lib/active_record/associations/has_many_through_association.rb +24 -18
- data/lib/active_record/associations/has_one_association.rb +43 -31
- data/lib/active_record/associations/has_one_through_association.rb +5 -5
- data/lib/active_record/associations/join_dependency.rb +91 -60
- data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
- data/lib/active_record/associations/preloader.rb +47 -34
- data/lib/active_record/associations/preloader/association.rb +71 -43
- data/lib/active_record/associations/preloader/through_association.rb +49 -40
- data/lib/active_record/associations/singular_association.rb +3 -17
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/attribute_assignment.rb +17 -19
- data/lib/active_record/attribute_methods.rb +81 -143
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -40
- data/lib/active_record/attribute_methods/primary_key.rb +20 -25
- data/lib/active_record/attribute_methods/query.rb +4 -8
- data/lib/active_record/attribute_methods/read.rb +14 -56
- data/lib/active_record/attribute_methods/serialization.rb +12 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
- data/lib/active_record/attribute_methods/write.rb +18 -34
- data/lib/active_record/attributes.rb +46 -9
- data/lib/active_record/autosave_association.rb +57 -42
- data/lib/active_record/base.rb +4 -17
- data/lib/active_record/callbacks.rb +158 -43
- data/lib/active_record/coders/yaml_column.rb +1 -2
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +272 -130
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -14
- data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +207 -90
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +385 -144
- data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +228 -98
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
- data/lib/active_record/connection_adapters/column.rb +30 -12
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -32
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +59 -7
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +18 -7
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
- data/lib/active_record/connection_adapters/pool_config.rb +73 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -54
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.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 +15 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
- data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -120
- data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +174 -186
- data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
- data/lib/active_record/connection_handling.rb +293 -33
- data/lib/active_record/core.rb +323 -97
- data/lib/active_record/counter_cache.rb +8 -30
- data/lib/active_record/database_configurations.rb +272 -0
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
- data/lib/active_record/database_configurations/database_config.rb +80 -0
- data/lib/active_record/database_configurations/hash_config.rb +96 -0
- data/lib/active_record/database_configurations/url_config.rb +53 -0
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/dynamic_matchers.rb +3 -4
- data/lib/active_record/enum.rb +111 -37
- data/lib/active_record/errors.rb +62 -19
- data/lib/active_record/explain.rb +10 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +10 -17
- data/lib/active_record/fixture_set/model_metadata.rb +32 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +152 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +200 -481
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +53 -24
- data/lib/active_record/insert_all.rb +208 -0
- data/lib/active_record/integration.rb +67 -17
- data/lib/active_record/internal_metadata.rb +26 -9
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +37 -23
- data/lib/active_record/locking/pessimistic.rb +9 -5
- data/lib/active_record/log_subscriber.rb +35 -35
- data/lib/active_record/middleware/database_selector.rb +77 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/migration.rb +206 -157
- data/lib/active_record/migration/command_recorder.rb +96 -44
- data/lib/active_record/migration/compatibility.rb +142 -64
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/model_schema.rb +148 -22
- data/lib/active_record/nested_attributes.rb +4 -7
- data/lib/active_record/no_touching.rb +8 -1
- data/lib/active_record/null_relation.rb +0 -1
- data/lib/active_record/persistence.rb +267 -59
- data/lib/active_record/query_cache.rb +21 -4
- data/lib/active_record/querying.rb +40 -23
- data/lib/active_record/railtie.rb +115 -58
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/controller_runtime.rb +30 -35
- data/lib/active_record/railties/databases.rake +408 -78
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +109 -93
- data/lib/active_record/relation.rb +374 -104
- data/lib/active_record/relation/batches.rb +44 -35
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/calculations.rb +153 -90
- data/lib/active_record/relation/delegation.rb +35 -50
- data/lib/active_record/relation/finder_methods.rb +64 -39
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +32 -40
- data/lib/active_record/relation/predicate_builder.rb +62 -45
- data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
- data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/query_attribute.rb +13 -8
- data/lib/active_record/relation/query_methods.rb +475 -186
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +9 -9
- data/lib/active_record/relation/where_clause.rb +111 -61
- data/lib/active_record/result.rb +64 -38
- data/lib/active_record/runtime_registry.rb +2 -2
- data/lib/active_record/sanitization.rb +22 -41
- data/lib/active_record/schema.rb +2 -11
- data/lib/active_record/schema_dumper.rb +54 -9
- data/lib/active_record/schema_migration.rb +7 -9
- data/lib/active_record/scoping.rb +8 -9
- data/lib/active_record/scoping/default.rb +4 -6
- data/lib/active_record/scoping/named.rb +17 -24
- data/lib/active_record/secure_token.rb +16 -8
- data/lib/active_record/serialization.rb +5 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +49 -6
- data/lib/active_record/store.rb +88 -9
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +42 -43
- data/lib/active_record/tasks/database_tasks.rb +277 -81
- data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
- data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +246 -0
- data/lib/active_record/timestamp.rb +43 -32
- data/lib/active_record/touch_later.rb +23 -22
- data/lib/active_record/transactions.rb +62 -118
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type.rb +10 -5
- data/lib/active_record/type/adapter_specific_registry.rb +3 -13
- data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
- data/lib/active_record/type/serialized.rb +6 -3
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/type/type_map.rb +0 -1
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type_caster/connection.rb +15 -15
- data/lib/active_record/type_caster/map.rb +8 -8
- data/lib/active_record/validations.rb +4 -3
- data/lib/active_record/validations/associated.rb +1 -2
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +38 -30
- data/lib/arel.rb +54 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +41 -0
- data/lib/arel/collectors/bind.rb +29 -0
- data/lib/arel/collectors/composite.rb +39 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -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 +70 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +126 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -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 +15 -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 +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +72 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -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 +51 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -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 +19 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +31 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -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 +250 -0
- data/lib/arel/select_manager.rb +270 -0
- data/lib/arel/table.rb +118 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/visitors/dot.rb +308 -0
- data/lib/arel/visitors/mysql.rb +93 -0
- data/lib/arel/visitors/postgresql.rb +120 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +899 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/migration.rb +19 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
- data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +119 -34
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
- data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -102,6 +102,30 @@ module ActiveRecord
|
|
102
102
|
# If true, the default table name for a Product class will be "products". If false, it would just be "product".
|
103
103
|
# See table_name for the full rules on table/class naming. This is true, by default.
|
104
104
|
|
105
|
+
##
|
106
|
+
# :singleton-method: implicit_order_column
|
107
|
+
# :call-seq: implicit_order_column
|
108
|
+
#
|
109
|
+
# The name of the column records are ordered by if no explicit order clause
|
110
|
+
# is used during an ordered finder call. If not set the primary key is used.
|
111
|
+
|
112
|
+
##
|
113
|
+
# :singleton-method: implicit_order_column=
|
114
|
+
# :call-seq: implicit_order_column=(column_name)
|
115
|
+
#
|
116
|
+
# Sets the column to sort records by when no explicit order clause is used
|
117
|
+
# during an ordered finder call. Useful when the primary key is not an
|
118
|
+
# auto-incrementing integer, for example when it's a UUID. Records are subsorted
|
119
|
+
# by the primary key if it exists to ensure deterministic results.
|
120
|
+
|
121
|
+
##
|
122
|
+
# :singleton-method: immutable_strings_by_default=
|
123
|
+
# :call-seq: immutable_strings_by_default=(bool)
|
124
|
+
#
|
125
|
+
# Determines whether columns should infer their type as +:string+ or
|
126
|
+
# +:immutable_string+. This setting does not affect the behavior of
|
127
|
+
# <tt>attribute :foo, :string</tt>. Defaults to false.
|
128
|
+
|
105
129
|
included do
|
106
130
|
mattr_accessor :primary_key_prefix_type, instance_writer: false
|
107
131
|
|
@@ -110,12 +134,14 @@ module ActiveRecord
|
|
110
134
|
class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations"
|
111
135
|
class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata"
|
112
136
|
class_attribute :pluralize_table_names, instance_writer: false, default: true
|
137
|
+
class_attribute :implicit_order_column, instance_accessor: false
|
138
|
+
class_attribute :immutable_strings_by_default, instance_accessor: false
|
113
139
|
|
114
140
|
self.protected_environments = ["production"]
|
115
141
|
self.inheritance_column = "type"
|
116
142
|
self.ignored_columns = [].freeze
|
117
143
|
|
118
|
-
delegate :type_for_attribute, to: :class
|
144
|
+
delegate :type_for_attribute, :column_for_attribute, to: :class
|
119
145
|
|
120
146
|
initialize_load_schema_monitor
|
121
147
|
end
|
@@ -218,11 +244,11 @@ module ActiveRecord
|
|
218
244
|
end
|
219
245
|
|
220
246
|
def full_table_name_prefix #:nodoc:
|
221
|
-
(
|
247
|
+
(module_parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
|
222
248
|
end
|
223
249
|
|
224
250
|
def full_table_name_suffix #:nodoc:
|
225
|
-
(
|
251
|
+
(module_parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
|
226
252
|
end
|
227
253
|
|
228
254
|
# The array of names of environments where destructive actions should be prohibited. By default,
|
@@ -271,12 +297,42 @@ module ActiveRecord
|
|
271
297
|
|
272
298
|
# Sets the columns names the model should ignore. Ignored columns won't have attribute
|
273
299
|
# accessors defined, and won't be referenced in SQL queries.
|
300
|
+
#
|
301
|
+
# A common usage pattern for this method is to ensure all references to an attribute
|
302
|
+
# have been removed and deployed, before a migration to drop the column from the database
|
303
|
+
# has been deployed and run. Using this two step approach to dropping columns ensures there
|
304
|
+
# is no code that raises errors due to having a cached schema in memory at the time the
|
305
|
+
# schema migration is run.
|
306
|
+
#
|
307
|
+
# For example, given a model where you want to drop the "category" attribute, first mark it
|
308
|
+
# as ignored:
|
309
|
+
#
|
310
|
+
# class Project < ActiveRecord::Base
|
311
|
+
# # schema:
|
312
|
+
# # id :bigint
|
313
|
+
# # name :string, limit: 255
|
314
|
+
# # category :string, limit: 255
|
315
|
+
#
|
316
|
+
# self.ignored_columns = [:category]
|
317
|
+
# end
|
318
|
+
#
|
319
|
+
# The schema still contains "category", but now the model omits it, so any meta-driven code or
|
320
|
+
# schema caching will not attempt to use the column:
|
321
|
+
#
|
322
|
+
# Project.columns_hash["category"] => nil
|
323
|
+
#
|
324
|
+
# You will get an error if accessing that attribute directly, so ensure all usages of the
|
325
|
+
# column are removed (automated tests can help you find any usages).
|
326
|
+
#
|
327
|
+
# user = Project.create!(name: "First Project")
|
328
|
+
# user.category # => raises NoMethodError
|
274
329
|
def ignored_columns=(columns)
|
275
|
-
|
330
|
+
reload_schema_from_cache
|
331
|
+
@ignored_columns = columns.map(&:to_s).freeze
|
276
332
|
end
|
277
333
|
|
278
334
|
def sequence_name
|
279
|
-
if base_class
|
335
|
+
if base_class?
|
280
336
|
@sequence_name ||= reset_sequence_name
|
281
337
|
else
|
282
338
|
(@sequence_name ||= nil) || base_class.sequence_name
|
@@ -339,7 +395,7 @@ module ActiveRecord
|
|
339
395
|
|
340
396
|
def columns
|
341
397
|
load_schema
|
342
|
-
@columns ||= columns_hash.values
|
398
|
+
@columns ||= columns_hash.values.freeze
|
343
399
|
end
|
344
400
|
|
345
401
|
def attribute_types # :nodoc:
|
@@ -364,6 +420,8 @@ module ActiveRecord
|
|
364
420
|
# a string or a symbol.
|
365
421
|
def type_for_attribute(attr_name, &block)
|
366
422
|
attr_name = attr_name.to_s
|
423
|
+
attr_name = attribute_aliases[attr_name] || attr_name
|
424
|
+
|
367
425
|
if block
|
368
426
|
attribute_types.fetch(attr_name, &block)
|
369
427
|
else
|
@@ -371,11 +429,31 @@ module ActiveRecord
|
|
371
429
|
end
|
372
430
|
end
|
373
431
|
|
432
|
+
# Returns the column object for the named attribute.
|
433
|
+
# Returns an +ActiveRecord::ConnectionAdapters::NullColumn+ if the
|
434
|
+
# named attribute does not exist.
|
435
|
+
#
|
436
|
+
# class Person < ActiveRecord::Base
|
437
|
+
# end
|
438
|
+
#
|
439
|
+
# person = Person.new
|
440
|
+
# person.column_for_attribute(:name) # the result depends on the ConnectionAdapter
|
441
|
+
# # => #<ActiveRecord::ConnectionAdapters::Column:0x007ff4ab083980 @name="name", @sql_type="varchar(255)", @null=true, ...>
|
442
|
+
#
|
443
|
+
# person.column_for_attribute(:nothing)
|
444
|
+
# # => #<ActiveRecord::ConnectionAdapters::NullColumn:0xXXX @name=nil, @sql_type=nil, @cast_type=#<Type::Value>, ...>
|
445
|
+
def column_for_attribute(name)
|
446
|
+
name = name.to_s
|
447
|
+
columns_hash.fetch(name) do
|
448
|
+
ConnectionAdapters::NullColumn.new(name)
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
374
452
|
# Returns a hash where the keys are column names and the values are
|
375
453
|
# default values when instantiating the Active Record object for this table.
|
376
454
|
def column_defaults
|
377
455
|
load_schema
|
378
|
-
@column_defaults ||= _default_attributes.deep_dup.to_hash
|
456
|
+
@column_defaults ||= _default_attributes.deep_dup.to_hash.freeze
|
379
457
|
end
|
380
458
|
|
381
459
|
def _default_attributes # :nodoc:
|
@@ -385,7 +463,12 @@ module ActiveRecord
|
|
385
463
|
|
386
464
|
# Returns an array of column names as strings.
|
387
465
|
def column_names
|
388
|
-
@column_names ||= columns.map(&:name)
|
466
|
+
@column_names ||= columns.map(&:name).freeze
|
467
|
+
end
|
468
|
+
|
469
|
+
def symbol_column_to_string(name_symbol) # :nodoc:
|
470
|
+
@symbol_column_to_string_name_hash ||= column_names.index_by(&:to_sym)
|
471
|
+
@symbol_column_to_string_name_hash[name_symbol]
|
389
472
|
end
|
390
473
|
|
391
474
|
# Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
|
@@ -394,9 +477,8 @@ module ActiveRecord
|
|
394
477
|
@content_columns ||= columns.reject do |c|
|
395
478
|
c.name == primary_key ||
|
396
479
|
c.name == inheritance_column ||
|
397
|
-
c.name.end_with?("_id")
|
398
|
-
|
399
|
-
end
|
480
|
+
c.name.end_with?("_id", "_count")
|
481
|
+
end.freeze
|
400
482
|
end
|
401
483
|
|
402
484
|
# Resets all the cached information about columns, which will cause them
|
@@ -406,7 +488,7 @@ module ActiveRecord
|
|
406
488
|
# when just after creating a table you want to populate it with some default
|
407
489
|
# values, eg:
|
408
490
|
#
|
409
|
-
# class CreateJobLevels < ActiveRecord::Migration[
|
491
|
+
# class CreateJobLevels < ActiveRecord::Migration[6.0]
|
410
492
|
# def up
|
411
493
|
# create_table :job_levels do |t|
|
412
494
|
# t.integer :id
|
@@ -435,13 +517,11 @@ module ActiveRecord
|
|
435
517
|
end
|
436
518
|
|
437
519
|
protected
|
438
|
-
|
439
520
|
def initialize_load_schema_monitor
|
440
521
|
@load_schema_monitor = Monitor.new
|
441
522
|
end
|
442
523
|
|
443
524
|
private
|
444
|
-
|
445
525
|
def inherited(child_class)
|
446
526
|
super
|
447
527
|
child_class.initialize_load_schema_monitor
|
@@ -459,15 +539,27 @@ module ActiveRecord
|
|
459
539
|
load_schema!
|
460
540
|
|
461
541
|
@schema_loaded = true
|
542
|
+
rescue
|
543
|
+
reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
|
544
|
+
raise
|
462
545
|
end
|
463
546
|
end
|
464
547
|
|
465
548
|
def load_schema!
|
466
|
-
|
549
|
+
unless table_name
|
550
|
+
raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
|
551
|
+
end
|
552
|
+
|
553
|
+
columns_hash = connection.schema_cache.columns_hash(table_name)
|
554
|
+
columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
|
555
|
+
@columns_hash = columns_hash.freeze
|
467
556
|
@columns_hash.each do |name, column|
|
557
|
+
type = connection.lookup_cast_type_from_column(column)
|
558
|
+
type = _convert_type_from_options(type)
|
559
|
+
warn_if_deprecated_type(column)
|
468
560
|
define_attribute(
|
469
561
|
name,
|
470
|
-
|
562
|
+
type,
|
471
563
|
default: column.default,
|
472
564
|
user_provided_default: false
|
473
565
|
)
|
@@ -477,6 +569,7 @@ module ActiveRecord
|
|
477
569
|
def reload_schema_from_cache
|
478
570
|
@arel_table = nil
|
479
571
|
@column_names = nil
|
572
|
+
@symbol_column_to_string_name_hash = nil
|
480
573
|
@attribute_types = nil
|
481
574
|
@content_columns = nil
|
482
575
|
@default_attributes = nil
|
@@ -501,19 +594,52 @@ module ActiveRecord
|
|
501
594
|
|
502
595
|
# Computes and returns a table name according to default conventions.
|
503
596
|
def compute_table_name
|
504
|
-
|
505
|
-
if self == base
|
597
|
+
if base_class?
|
506
598
|
# Nested classes are prefixed with singular parent table name.
|
507
|
-
if
|
508
|
-
contained =
|
509
|
-
contained = contained.singularize if
|
599
|
+
if module_parent < Base && !module_parent.abstract_class?
|
600
|
+
contained = module_parent.table_name
|
601
|
+
contained = contained.singularize if module_parent.pluralize_table_names
|
510
602
|
contained += "_"
|
511
603
|
end
|
512
604
|
|
513
605
|
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}"
|
514
606
|
else
|
515
607
|
# STI subclasses always use their superclass' table.
|
516
|
-
|
608
|
+
base_class.table_name
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
612
|
+
def _convert_type_from_options(type)
|
613
|
+
if immutable_strings_by_default && type.respond_to?(:to_immutable_string)
|
614
|
+
type.to_immutable_string
|
615
|
+
else
|
616
|
+
type
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
def warn_if_deprecated_type(column)
|
621
|
+
return if attributes_to_define_after_schema_loads.key?(column.name)
|
622
|
+
return unless column.respond_to?(:oid)
|
623
|
+
|
624
|
+
if column.array?
|
625
|
+
array_arguments = ", array: true"
|
626
|
+
else
|
627
|
+
array_arguments = ""
|
628
|
+
end
|
629
|
+
|
630
|
+
if column.sql_type.start_with?("interval")
|
631
|
+
precision_arguments = column.precision.presence && ", precision: #{column.precision}"
|
632
|
+
ActiveSupport::Deprecation.warn(<<~WARNING)
|
633
|
+
The behavior of the `:interval` type will be changing in Rails 6.2
|
634
|
+
to return an `ActiveSupport::Duration` object. If you'd like to keep
|
635
|
+
the old behavior, you can add this line to #{self.name} model:
|
636
|
+
|
637
|
+
attribute :#{column.name}, :string#{precision_arguments}#{array_arguments}
|
638
|
+
|
639
|
+
If you'd like the new behavior today, you can add this line:
|
640
|
+
|
641
|
+
attribute :#{column.name}, :interval#{precision_arguments}#{array_arguments}
|
642
|
+
WARNING
|
517
643
|
end
|
518
644
|
end
|
519
645
|
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require "active_support/core_ext/hash/except"
|
4
4
|
require "active_support/core_ext/module/redefine_method"
|
5
|
-
require "active_support/core_ext/object/try"
|
6
5
|
require "active_support/core_ext/hash/indifferent_access"
|
7
6
|
|
8
7
|
module ActiveRecord
|
@@ -289,7 +288,7 @@ module ActiveRecord
|
|
289
288
|
# [:allow_destroy]
|
290
289
|
# If true, destroys any members from the attributes hash with a
|
291
290
|
# <tt>_destroy</tt> key and a value that evaluates to +true+
|
292
|
-
# (
|
291
|
+
# (e.g. 1, '1', true, or 'true'). This option is off by default.
|
293
292
|
# [:reject_if]
|
294
293
|
# Allows you to specify a Proc or a Symbol pointing to a method
|
295
294
|
# that checks whether a record should be built for a certain attribute
|
@@ -354,7 +353,6 @@ module ActiveRecord
|
|
354
353
|
end
|
355
354
|
|
356
355
|
private
|
357
|
-
|
358
356
|
# Generates a writer method for this association. Serves as a point for
|
359
357
|
# accessing the objects in the association. For example, this method
|
360
358
|
# could generate the following:
|
@@ -386,7 +384,6 @@ module ActiveRecord
|
|
386
384
|
end
|
387
385
|
|
388
386
|
private
|
389
|
-
|
390
387
|
# Attribute hash keys that should not be assigned as normal attributes.
|
391
388
|
# These hash keys are nested attributes implementation details.
|
392
389
|
UNASSIGNABLE_KEYS = %w( id _destroy )
|
@@ -426,7 +423,7 @@ module ActiveRecord
|
|
426
423
|
existing_record.assign_attributes(assignable_attributes)
|
427
424
|
association(association_name).initialize_attributes(existing_record)
|
428
425
|
else
|
429
|
-
method = "build_#{association_name}"
|
426
|
+
method = :"build_#{association_name}"
|
430
427
|
if respond_to?(method)
|
431
428
|
send(method, assignable_attributes)
|
432
429
|
else
|
@@ -501,7 +498,7 @@ module ActiveRecord
|
|
501
498
|
|
502
499
|
if attributes["id"].blank?
|
503
500
|
unless reject_new_record?(association_name, attributes)
|
504
|
-
association.build(attributes.except(*UNASSIGNABLE_KEYS))
|
501
|
+
association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
|
505
502
|
end
|
506
503
|
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s }
|
507
504
|
unless call_reject_if(association_name, attributes)
|
@@ -512,7 +509,7 @@ module ActiveRecord
|
|
512
509
|
if target_record
|
513
510
|
existing_record = target_record
|
514
511
|
else
|
515
|
-
association.add_to_target(existing_record, :
|
512
|
+
association.add_to_target(existing_record, skip_callbacks: true)
|
516
513
|
end
|
517
514
|
|
518
515
|
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
@@ -43,6 +43,13 @@ module ActiveRecord
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
+
# Returns +true+ if the class has +no_touching+ set, +false+ otherwise.
|
47
|
+
#
|
48
|
+
# Project.no_touching do
|
49
|
+
# Project.first.no_touching? # true
|
50
|
+
# Message.first.no_touching? # false
|
51
|
+
# end
|
52
|
+
#
|
46
53
|
def no_touching?
|
47
54
|
NoTouching.applied_to?(self.class)
|
48
55
|
end
|
@@ -51,7 +58,7 @@ module ActiveRecord
|
|
51
58
|
super unless no_touching?
|
52
59
|
end
|
53
60
|
|
54
|
-
def touch(
|
61
|
+
def touch(*, **) # :nodoc:
|
55
62
|
super unless no_touching?
|
56
63
|
end
|
57
64
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record/insert_all"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
# = Active Record \Persistence
|
5
7
|
module Persistence
|
@@ -55,6 +57,192 @@ module ActiveRecord
|
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
60
|
+
# Inserts a single record into the database in a single SQL INSERT
|
61
|
+
# statement. It does not instantiate any models nor does it trigger
|
62
|
+
# Active Record callbacks or validations. Though passed values
|
63
|
+
# go through Active Record's type casting and serialization.
|
64
|
+
#
|
65
|
+
# See <tt>ActiveRecord::Persistence#insert_all</tt> for documentation.
|
66
|
+
def insert(attributes, returning: nil, unique_by: nil)
|
67
|
+
insert_all([ attributes ], returning: returning, unique_by: unique_by)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Inserts multiple records into the database in a single SQL INSERT
|
71
|
+
# statement. It does not instantiate any models nor does it trigger
|
72
|
+
# Active Record callbacks or validations. Though passed values
|
73
|
+
# go through Active Record's type casting and serialization.
|
74
|
+
#
|
75
|
+
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
76
|
+
# the attributes for a single row and must have the same keys.
|
77
|
+
#
|
78
|
+
# Rows are considered to be unique by every unique index on the table. Any
|
79
|
+
# duplicate rows are skipped.
|
80
|
+
# Override with <tt>:unique_by</tt> (see below).
|
81
|
+
#
|
82
|
+
# Returns an <tt>ActiveRecord::Result</tt> with its contents based on
|
83
|
+
# <tt>:returning</tt> (see below).
|
84
|
+
#
|
85
|
+
# ==== Options
|
86
|
+
#
|
87
|
+
# [:returning]
|
88
|
+
# (PostgreSQL only) An array of attributes to return for all successfully
|
89
|
+
# inserted records, which by default is the primary key.
|
90
|
+
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
91
|
+
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
92
|
+
# clause entirely.
|
93
|
+
#
|
94
|
+
# [:unique_by]
|
95
|
+
# (PostgreSQL and SQLite only) By default rows are considered to be unique
|
96
|
+
# by every unique index on the table. Any duplicate rows are skipped.
|
97
|
+
#
|
98
|
+
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
|
99
|
+
#
|
100
|
+
# Consider a Book model where no duplicate ISBNs make sense, but if any
|
101
|
+
# row has an existing id, or is not unique by another unique index,
|
102
|
+
# <tt>ActiveRecord::RecordNotUnique</tt> is raised.
|
103
|
+
#
|
104
|
+
# Unique indexes can be identified by columns or name:
|
105
|
+
#
|
106
|
+
# unique_by: :isbn
|
107
|
+
# unique_by: %i[ author_id name ]
|
108
|
+
# unique_by: :index_books_on_isbn
|
109
|
+
#
|
110
|
+
# Because it relies on the index information from the database
|
111
|
+
# <tt>:unique_by</tt> is recommended to be paired with
|
112
|
+
# Active Record's schema_cache.
|
113
|
+
#
|
114
|
+
# ==== Example
|
115
|
+
#
|
116
|
+
# # Insert records and skip inserting any duplicates.
|
117
|
+
# # Here "Eloquent Ruby" is skipped because its id is not unique.
|
118
|
+
#
|
119
|
+
# Book.insert_all([
|
120
|
+
# { id: 1, title: "Rework", author: "David" },
|
121
|
+
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
|
122
|
+
# ])
|
123
|
+
def insert_all(attributes, returning: nil, unique_by: nil)
|
124
|
+
InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by).execute
|
125
|
+
end
|
126
|
+
|
127
|
+
# Inserts a single record into the database in a single SQL INSERT
|
128
|
+
# statement. It does not instantiate any models nor does it trigger
|
129
|
+
# Active Record callbacks or validations. Though passed values
|
130
|
+
# go through Active Record's type casting and serialization.
|
131
|
+
#
|
132
|
+
# See <tt>ActiveRecord::Persistence#insert_all!</tt> for more.
|
133
|
+
def insert!(attributes, returning: nil)
|
134
|
+
insert_all!([ attributes ], returning: returning)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Inserts multiple records into the database in a single SQL INSERT
|
138
|
+
# statement. It does not instantiate any models nor does it trigger
|
139
|
+
# Active Record callbacks or validations. Though passed values
|
140
|
+
# go through Active Record's type casting and serialization.
|
141
|
+
#
|
142
|
+
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
143
|
+
# the attributes for a single row and must have the same keys.
|
144
|
+
#
|
145
|
+
# Raises <tt>ActiveRecord::RecordNotUnique</tt> if any rows violate a
|
146
|
+
# unique index on the table. In that case, no rows are inserted.
|
147
|
+
#
|
148
|
+
# To skip duplicate rows, see <tt>ActiveRecord::Persistence#insert_all</tt>.
|
149
|
+
# To replace them, see <tt>ActiveRecord::Persistence#upsert_all</tt>.
|
150
|
+
#
|
151
|
+
# Returns an <tt>ActiveRecord::Result</tt> with its contents based on
|
152
|
+
# <tt>:returning</tt> (see below).
|
153
|
+
#
|
154
|
+
# ==== Options
|
155
|
+
#
|
156
|
+
# [:returning]
|
157
|
+
# (PostgreSQL only) An array of attributes to return for all successfully
|
158
|
+
# inserted records, which by default is the primary key.
|
159
|
+
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
160
|
+
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
161
|
+
# clause entirely.
|
162
|
+
#
|
163
|
+
# ==== Examples
|
164
|
+
#
|
165
|
+
# # Insert multiple records
|
166
|
+
# Book.insert_all!([
|
167
|
+
# { title: "Rework", author: "David" },
|
168
|
+
# { title: "Eloquent Ruby", author: "Russ" }
|
169
|
+
# ])
|
170
|
+
#
|
171
|
+
# # Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
|
172
|
+
# # does not have a unique id.
|
173
|
+
# Book.insert_all!([
|
174
|
+
# { id: 1, title: "Rework", author: "David" },
|
175
|
+
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
|
176
|
+
# ])
|
177
|
+
def insert_all!(attributes, returning: nil)
|
178
|
+
InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning).execute
|
179
|
+
end
|
180
|
+
|
181
|
+
# Updates or inserts (upserts) a single record into the database in a
|
182
|
+
# single SQL INSERT statement. It does not instantiate any models nor does
|
183
|
+
# it trigger Active Record callbacks or validations. Though passed values
|
184
|
+
# go through Active Record's type casting and serialization.
|
185
|
+
#
|
186
|
+
# See <tt>ActiveRecord::Persistence#upsert_all</tt> for documentation.
|
187
|
+
def upsert(attributes, returning: nil, unique_by: nil)
|
188
|
+
upsert_all([ attributes ], returning: returning, unique_by: unique_by)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Updates or inserts (upserts) multiple records into the database in a
|
192
|
+
# single SQL INSERT statement. It does not instantiate any models nor does
|
193
|
+
# it trigger Active Record callbacks or validations. Though passed values
|
194
|
+
# go through Active Record's type casting and serialization.
|
195
|
+
#
|
196
|
+
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
197
|
+
# the attributes for a single row and must have the same keys.
|
198
|
+
#
|
199
|
+
# Returns an <tt>ActiveRecord::Result</tt> with its contents based on
|
200
|
+
# <tt>:returning</tt> (see below).
|
201
|
+
#
|
202
|
+
# ==== Options
|
203
|
+
#
|
204
|
+
# [:returning]
|
205
|
+
# (PostgreSQL only) An array of attributes to return for all successfully
|
206
|
+
# inserted records, which by default is the primary key.
|
207
|
+
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
208
|
+
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
209
|
+
# clause entirely.
|
210
|
+
#
|
211
|
+
# [:unique_by]
|
212
|
+
# (PostgreSQL and SQLite only) By default rows are considered to be unique
|
213
|
+
# by every unique index on the table. Any duplicate rows are skipped.
|
214
|
+
#
|
215
|
+
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
|
216
|
+
#
|
217
|
+
# Consider a Book model where no duplicate ISBNs make sense, but if any
|
218
|
+
# row has an existing id, or is not unique by another unique index,
|
219
|
+
# <tt>ActiveRecord::RecordNotUnique</tt> is raised.
|
220
|
+
#
|
221
|
+
# Unique indexes can be identified by columns or name:
|
222
|
+
#
|
223
|
+
# unique_by: :isbn
|
224
|
+
# unique_by: %i[ author_id name ]
|
225
|
+
# unique_by: :index_books_on_isbn
|
226
|
+
#
|
227
|
+
# Because it relies on the index information from the database
|
228
|
+
# <tt>:unique_by</tt> is recommended to be paired with
|
229
|
+
# Active Record's schema_cache.
|
230
|
+
#
|
231
|
+
# ==== Examples
|
232
|
+
#
|
233
|
+
# # Inserts multiple records, performing an upsert when records have duplicate ISBNs.
|
234
|
+
# # Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
|
235
|
+
#
|
236
|
+
# Book.upsert_all([
|
237
|
+
# { title: "Rework", author: "David", isbn: "1" },
|
238
|
+
# { title: "Eloquent Ruby", author: "Russ", isbn: "1" }
|
239
|
+
# ], unique_by: :isbn)
|
240
|
+
#
|
241
|
+
# Book.find_by(isbn: "1").title # => "Eloquent Ruby"
|
242
|
+
def upsert_all(attributes, returning: nil, unique_by: nil)
|
243
|
+
InsertAll.new(self, attributes, on_duplicate: :update, returning: returning, unique_by: unique_by).execute
|
244
|
+
end
|
245
|
+
|
58
246
|
# Given an attributes hash, +instantiate+ returns a new instance of
|
59
247
|
# the appropriate class. Accepts only keys as strings.
|
60
248
|
#
|
@@ -67,8 +255,7 @@ module ActiveRecord
|
|
67
255
|
# how this "single-table" inheritance mapping is implemented.
|
68
256
|
def instantiate(attributes, column_types = {}, &block)
|
69
257
|
klass = discriminate_class_for_record(attributes)
|
70
|
-
|
71
|
-
klass.allocate.init_with("attributes" => attributes, "new_record" => false, &block)
|
258
|
+
instantiate_instance_of(klass, attributes, column_types, &block)
|
72
259
|
end
|
73
260
|
|
74
261
|
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
|
@@ -143,7 +330,7 @@ module ActiveRecord
|
|
143
330
|
end
|
144
331
|
end
|
145
332
|
|
146
|
-
# Deletes the row with a primary key matching the +id+ argument, using
|
333
|
+
# Deletes the row with a primary key matching the +id+ argument, using an
|
147
334
|
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
|
148
335
|
# Record objects are not instantiated, so the object's callbacks are not
|
149
336
|
# executed, including any <tt>:dependent</tt> association options.
|
@@ -162,10 +349,11 @@ module ActiveRecord
|
|
162
349
|
# # Delete multiple rows
|
163
350
|
# Todo.delete([2,3,4])
|
164
351
|
def delete(id_or_array)
|
165
|
-
|
352
|
+
delete_by(primary_key => id_or_array)
|
166
353
|
end
|
167
354
|
|
168
355
|
def _insert_record(values) # :nodoc:
|
356
|
+
primary_key = self.primary_key
|
169
357
|
primary_key_value = nil
|
170
358
|
|
171
359
|
if primary_key && Hash === values
|
@@ -178,7 +366,7 @@ module ActiveRecord
|
|
178
366
|
end
|
179
367
|
|
180
368
|
if values.empty?
|
181
|
-
im = arel_table.compile_insert(connection.empty_insert_statement_value)
|
369
|
+
im = arel_table.compile_insert(connection.empty_insert_statement_value(primary_key))
|
182
370
|
im.into arel_table
|
183
371
|
else
|
184
372
|
im = arel_table.compile_insert(_substitute_values(values))
|
@@ -208,6 +396,13 @@ module ActiveRecord
|
|
208
396
|
end
|
209
397
|
|
210
398
|
private
|
399
|
+
# Given a class, an attributes hash, +instantiate_instance_of+ returns a
|
400
|
+
# new instance of the class. Accepts only keys as strings.
|
401
|
+
def instantiate_instance_of(klass, attributes, column_types = {}, &block)
|
402
|
+
attributes = klass.attributes_builder.build_from_database(attributes, column_types)
|
403
|
+
klass.allocate.init_with_attributes(attributes, &block)
|
404
|
+
end
|
405
|
+
|
211
406
|
# Called by +instantiate+ to decide which class to use for a new
|
212
407
|
# record instance.
|
213
408
|
#
|
@@ -219,8 +414,8 @@ module ActiveRecord
|
|
219
414
|
|
220
415
|
def _substitute_values(values)
|
221
416
|
values.map do |name, value|
|
222
|
-
attr =
|
223
|
-
bind = predicate_builder.build_bind_attribute(name, value)
|
417
|
+
attr = arel_table[name]
|
418
|
+
bind = predicate_builder.build_bind_attribute(attr.name, value)
|
224
419
|
[attr, bind]
|
225
420
|
end
|
226
421
|
end
|
@@ -229,26 +424,30 @@ module ActiveRecord
|
|
229
424
|
# Returns true if this object hasn't been saved yet -- that is, a record
|
230
425
|
# for the object doesn't exist in the database yet; otherwise, returns false.
|
231
426
|
def new_record?
|
232
|
-
sync_with_transaction_state
|
233
427
|
@new_record
|
234
428
|
end
|
235
429
|
|
430
|
+
# Returns true if this object was just created -- that is, prior to the last
|
431
|
+
# save, the object didn't exist in the database and new_record? would have
|
432
|
+
# returned true.
|
433
|
+
def previously_new_record?
|
434
|
+
@previously_new_record
|
435
|
+
end
|
436
|
+
|
236
437
|
# Returns true if this object has been destroyed, otherwise returns false.
|
237
438
|
def destroyed?
|
238
|
-
sync_with_transaction_state
|
239
439
|
@destroyed
|
240
440
|
end
|
241
441
|
|
242
442
|
# Returns true if the record is persisted, i.e. it's not a new record and it was
|
243
443
|
# not destroyed, otherwise returns false.
|
244
444
|
def persisted?
|
245
|
-
sync_with_transaction_state
|
246
445
|
!(@new_record || @destroyed)
|
247
446
|
end
|
248
447
|
|
249
448
|
##
|
250
449
|
# :call-seq:
|
251
|
-
# save(
|
450
|
+
# save(**options)
|
252
451
|
#
|
253
452
|
# Saves the model.
|
254
453
|
#
|
@@ -271,15 +470,15 @@ module ActiveRecord
|
|
271
470
|
#
|
272
471
|
# Attributes marked as readonly are silently ignored if the record is
|
273
472
|
# being updated.
|
274
|
-
def save(
|
275
|
-
create_or_update(
|
473
|
+
def save(**options, &block)
|
474
|
+
create_or_update(**options, &block)
|
276
475
|
rescue ActiveRecord::RecordInvalid
|
277
476
|
false
|
278
477
|
end
|
279
478
|
|
280
479
|
##
|
281
480
|
# :call-seq:
|
282
|
-
# save!(
|
481
|
+
# save!(**options)
|
283
482
|
#
|
284
483
|
# Saves the model.
|
285
484
|
#
|
@@ -304,8 +503,8 @@ module ActiveRecord
|
|
304
503
|
# being updated.
|
305
504
|
#
|
306
505
|
# Unless an error is raised, returns true.
|
307
|
-
def save!(
|
308
|
-
create_or_update(
|
506
|
+
def save!(**options, &block)
|
507
|
+
create_or_update(**options, &block) || raise(RecordNotSaved.new("Failed to save the record", self))
|
309
508
|
end
|
310
509
|
|
311
510
|
# Deletes the record in the database and freezes this instance to
|
@@ -319,7 +518,7 @@ module ActiveRecord
|
|
319
518
|
#
|
320
519
|
# To enforce the object's +before_destroy+ and +after_destroy+
|
321
520
|
# callbacks or any <tt>:dependent</tt> association
|
322
|
-
# options, use
|
521
|
+
# options, use #destroy.
|
323
522
|
def delete
|
324
523
|
_delete_row if persisted?
|
325
524
|
@destroyed = true
|
@@ -336,7 +535,6 @@ module ActiveRecord
|
|
336
535
|
def destroy
|
337
536
|
_raise_readonly_record_error if readonly?
|
338
537
|
destroy_associations
|
339
|
-
self.class.connection.add_transaction_record(self)
|
340
538
|
@_trigger_destroy_callback = if persisted?
|
341
539
|
destroy_row > 0
|
342
540
|
else
|
@@ -371,13 +569,15 @@ module ActiveRecord
|
|
371
569
|
# If you want to change the sti column as well, use #becomes! instead.
|
372
570
|
def becomes(klass)
|
373
571
|
became = klass.allocate
|
374
|
-
|
375
|
-
became.
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
572
|
+
|
573
|
+
became.send(:initialize) do |becoming|
|
574
|
+
becoming.instance_variable_set(:@attributes, @attributes)
|
575
|
+
becoming.instance_variable_set(:@mutations_from_database, @mutations_from_database ||= nil)
|
576
|
+
becoming.instance_variable_set(:@new_record, new_record?)
|
577
|
+
becoming.instance_variable_set(:@destroyed, destroyed?)
|
578
|
+
becoming.errors.copy!(errors)
|
579
|
+
end
|
580
|
+
|
381
581
|
became
|
382
582
|
end
|
383
583
|
|
@@ -429,8 +629,6 @@ module ActiveRecord
|
|
429
629
|
end
|
430
630
|
end
|
431
631
|
|
432
|
-
alias update_attributes update
|
433
|
-
|
434
632
|
# Updates its receiver just like #update but calls #save! instead
|
435
633
|
# of +save+, so an exception is raised if the record is invalid and saving will fail.
|
436
634
|
def update!(attributes)
|
@@ -442,8 +640,6 @@ module ActiveRecord
|
|
442
640
|
end
|
443
641
|
end
|
444
642
|
|
445
|
-
alias update_attributes! update!
|
446
|
-
|
447
643
|
# Equivalent to <code>update_columns(name => value)</code>.
|
448
644
|
def update_column(name, value)
|
449
645
|
update_columns(name => value)
|
@@ -469,8 +665,10 @@ module ActiveRecord
|
|
469
665
|
raise ActiveRecordError, "cannot update a new record" if new_record?
|
470
666
|
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
|
471
667
|
|
472
|
-
attributes.
|
473
|
-
|
668
|
+
attributes = attributes.transform_keys do |key|
|
669
|
+
name = key.to_s
|
670
|
+
name = self.class.attribute_aliases[name] || name
|
671
|
+
verify_readonly_attribute(name) || name
|
474
672
|
end
|
475
673
|
|
476
674
|
id_in_database = self.id_in_database
|
@@ -480,7 +678,7 @@ module ActiveRecord
|
|
480
678
|
|
481
679
|
affected_rows = self.class._update_record(
|
482
680
|
attributes,
|
483
|
-
|
681
|
+
@primary_key => id_in_database
|
484
682
|
)
|
485
683
|
|
486
684
|
affected_rows == 1
|
@@ -503,9 +701,9 @@ module ActiveRecord
|
|
503
701
|
# Returns +self+.
|
504
702
|
def increment!(attribute, by = 1, touch: nil)
|
505
703
|
increment(attribute, by)
|
506
|
-
change = public_send(attribute) - (
|
704
|
+
change = public_send(attribute) - (public_send(:"#{attribute}_in_database") || 0)
|
507
705
|
self.class.update_counters(id, attribute => change, touch: touch)
|
508
|
-
|
706
|
+
public_send(:"clear_#{attribute}_change")
|
509
707
|
self
|
510
708
|
end
|
511
709
|
|
@@ -609,8 +807,9 @@ module ActiveRecord
|
|
609
807
|
self.class.unscoped { self.class.find(id) }
|
610
808
|
end
|
611
809
|
|
612
|
-
@attributes = fresh_object.instance_variable_get(
|
810
|
+
@attributes = fresh_object.instance_variable_get(:@attributes)
|
613
811
|
@new_record = false
|
812
|
+
@previously_new_record = false
|
614
813
|
self
|
615
814
|
end
|
616
815
|
|
@@ -649,15 +848,13 @@ module ActiveRecord
|
|
649
848
|
# ball.touch(:updated_at) # => raises ActiveRecordError
|
650
849
|
#
|
651
850
|
def touch(*names, time: nil)
|
652
|
-
unless persisted?
|
653
|
-
raise ActiveRecordError, <<-MSG.squish
|
654
|
-
cannot touch on a new or destroyed record object. Consider using
|
655
|
-
persisted?, new_record?, or destroyed? before touching
|
656
|
-
MSG
|
657
|
-
end
|
851
|
+
_raise_record_not_touched_error unless persisted?
|
658
852
|
|
659
853
|
attribute_names = timestamp_attributes_for_update_in_model
|
660
|
-
attribute_names |= names.map
|
854
|
+
attribute_names |= names.map! do |name|
|
855
|
+
name = name.to_s
|
856
|
+
self.class.attribute_aliases[name] || name
|
857
|
+
end unless names.empty?
|
661
858
|
|
662
859
|
unless attribute_names.empty?
|
663
860
|
affected_rows = _touch_row(attribute_names, time)
|
@@ -668,7 +865,6 @@ module ActiveRecord
|
|
668
865
|
end
|
669
866
|
|
670
867
|
private
|
671
|
-
|
672
868
|
# A hook to be overridden by association modules.
|
673
869
|
def destroy_associations
|
674
870
|
end
|
@@ -678,15 +874,14 @@ module ActiveRecord
|
|
678
874
|
end
|
679
875
|
|
680
876
|
def _delete_row
|
681
|
-
self.class._delete_record(
|
877
|
+
self.class._delete_record(@primary_key => id_in_database)
|
682
878
|
end
|
683
879
|
|
684
880
|
def _touch_row(attribute_names, time)
|
685
881
|
time ||= current_time_from_proper_timezone
|
686
882
|
|
687
883
|
attribute_names.each do |attr_name|
|
688
|
-
|
689
|
-
clear_attribute_change(attr_name)
|
884
|
+
_write_attribute(attr_name, time)
|
690
885
|
end
|
691
886
|
|
692
887
|
_update_row(attribute_names, "touch")
|
@@ -695,21 +890,20 @@ module ActiveRecord
|
|
695
890
|
def _update_row(attribute_names, attempted_action = "update")
|
696
891
|
self.class._update_record(
|
697
892
|
attributes_with_values(attribute_names),
|
698
|
-
|
893
|
+
@primary_key => id_in_database
|
699
894
|
)
|
700
895
|
end
|
701
896
|
|
702
|
-
def create_or_update(
|
897
|
+
def create_or_update(**, &block)
|
703
898
|
_raise_readonly_record_error if readonly?
|
704
899
|
return false if destroyed?
|
705
|
-
result = new_record? ? _create_record(&block) : _update_record(
|
900
|
+
result = new_record? ? _create_record(&block) : _update_record(&block)
|
706
901
|
result != false
|
707
902
|
end
|
708
903
|
|
709
904
|
# Updates the associated record with values matching those of the instance attributes.
|
710
905
|
# Returns the number of affected rows.
|
711
906
|
def _update_record(attribute_names = self.attribute_names)
|
712
|
-
attribute_names &= self.class.column_names
|
713
907
|
attribute_names = attributes_for_update(attribute_names)
|
714
908
|
|
715
909
|
if attribute_names.empty?
|
@@ -720,6 +914,8 @@ module ActiveRecord
|
|
720
914
|
@_trigger_update_callback = affected_rows == 1
|
721
915
|
end
|
722
916
|
|
917
|
+
@previously_new_record = false
|
918
|
+
|
723
919
|
yield(self) if block_given?
|
724
920
|
|
725
921
|
affected_rows
|
@@ -728,13 +924,16 @@ module ActiveRecord
|
|
728
924
|
# Creates a record with values matching those of the instance attributes
|
729
925
|
# and returns its id.
|
730
926
|
def _create_record(attribute_names = self.attribute_names)
|
731
|
-
attribute_names
|
732
|
-
attributes_values = attributes_with_values_for_create(attribute_names)
|
927
|
+
attribute_names = attributes_for_create(attribute_names)
|
733
928
|
|
734
|
-
new_id = self.class._insert_record(
|
735
|
-
|
929
|
+
new_id = self.class._insert_record(
|
930
|
+
attributes_with_values(attribute_names)
|
931
|
+
)
|
932
|
+
|
933
|
+
self.id ||= new_id if @primary_key
|
736
934
|
|
737
935
|
@new_record = false
|
936
|
+
@previously_new_record = true
|
738
937
|
|
739
938
|
yield(self) if block_given?
|
740
939
|
|
@@ -742,7 +941,7 @@ module ActiveRecord
|
|
742
941
|
end
|
743
942
|
|
744
943
|
def verify_readonly_attribute(name)
|
745
|
-
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.
|
944
|
+
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
|
746
945
|
end
|
747
946
|
|
748
947
|
def _raise_record_not_destroyed
|
@@ -752,12 +951,21 @@ module ActiveRecord
|
|
752
951
|
@_association_destroy_exception = nil
|
753
952
|
end
|
754
953
|
|
755
|
-
def belongs_to_touch_method
|
756
|
-
:touch
|
757
|
-
end
|
758
|
-
|
759
954
|
def _raise_readonly_record_error
|
760
955
|
raise ReadOnlyRecord, "#{self.class} is marked as readonly"
|
761
956
|
end
|
957
|
+
|
958
|
+
def _raise_record_not_touched_error
|
959
|
+
raise ActiveRecordError, <<~MSG.squish
|
960
|
+
Cannot touch on a new or destroyed record object. Consider using
|
961
|
+
persisted?, new_record?, or destroyed? before touching.
|
962
|
+
MSG
|
963
|
+
end
|
964
|
+
|
965
|
+
# The name of the method used to touch a +belongs_to+ association when the
|
966
|
+
# +:touch+ option is used.
|
967
|
+
def belongs_to_touch_method
|
968
|
+
:touch
|
969
|
+
end
|
762
970
|
end
|
763
971
|
end
|