activerecord 5.2.8.1 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +849 -630
- data/MIT-LICENSE +3 -1
- data/README.rdoc +7 -5
- data/examples/performance.rb +1 -1
- data/lib/active_record/aggregations.rb +5 -4
- data/lib/active_record/association_relation.rb +22 -12
- 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 +21 -21
- data/lib/active_record/associations/belongs_to_association.rb +50 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -5
- data/lib/active_record/associations/builder/association.rb +23 -21
- 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/join_association.rb +41 -20
- data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
- data/lib/active_record/associations/join_dependency.rb +91 -60
- 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/preloader.rb +47 -34
- data/lib/active_record/associations/singular_association.rb +3 -17
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +133 -25
- data/lib/active_record/attribute_assignment.rb +17 -19
- 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/attribute_methods.rb +81 -143
- data/lib/active_record/attributes.rb +45 -8
- 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 +2 -15
- 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 +203 -90
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +381 -146
- data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -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 +44 -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 +14 -6
- 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 +63 -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/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/money.rb +2 -2
- 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/oid.rb +2 -0
- 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 +222 -112
- 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_adapters.rb +50 -0
- data/lib/active_record/connection_handling.rb +285 -33
- data/lib/active_record/core.rb +304 -106
- data/lib/active_record/counter_cache.rb +8 -30
- 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/database_configurations.rb +272 -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 +71 -17
- 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 +197 -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 +26 -22
- data/lib/active_record/locking/pessimistic.rb +9 -5
- data/lib/active_record/log_subscriber.rb +34 -35
- data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +77 -0
- data/lib/active_record/migration/command_recorder.rb +96 -44
- data/lib/active_record/migration/compatibility.rb +141 -64
- data/lib/active_record/migration/join_table.rb +0 -1
- data/lib/active_record/migration.rb +205 -156
- 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 +113 -74
- data/lib/active_record/railties/controller_runtime.rb +30 -35
- data/lib/active_record/railties/databases.rake +402 -78
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +109 -93
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/batches.rb +44 -35
- 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/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 +4 -7
- 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/predicate_builder.rb +58 -40
- data/lib/active_record/relation/query_attribute.rb +13 -8
- data/lib/active_record/relation/query_methods.rb +472 -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 +108 -58
- data/lib/active_record/relation.rb +375 -104
- 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/default.rb +4 -6
- data/lib/active_record/scoping/named.rb +17 -24
- data/lib/active_record/scoping.rb +8 -9
- 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 +39 -43
- data/lib/active_record/tasks/database_tasks.rb +276 -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 +58 -116
- data/lib/active_record/translation.rb +1 -1
- 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.rb +10 -5
- 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/associated.rb +1 -2
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +38 -30
- data/lib/active_record/validations.rb +4 -3
- data/lib/active_record.rb +13 -12
- 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/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/nodes.rb +70 -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/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/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +54 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- 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/migration.rb +19 -2
- 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 +120 -35
- 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
@@ -0,0 +1,209 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/inquiry"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
# == Delegated types
|
7
|
+
#
|
8
|
+
# Class hierarchies can map to relational database tables in many ways. Active Record, for example, offers
|
9
|
+
# purely abstract classes, where the superclass doesn't persist any attributes, and single-table inheritance,
|
10
|
+
# where all attributes from all levels of the hierarchy are represented in a single table. Both have their
|
11
|
+
# places, but neither are without their drawbacks.
|
12
|
+
#
|
13
|
+
# The problem with purely abstract classes is that all concrete subclasses must persist all the shared
|
14
|
+
# attributes themselves in their own tables (also known as class-table inheritance). This makes it hard to
|
15
|
+
# do queries across the hierarchy. For example, imagine you have the following hierarchy:
|
16
|
+
#
|
17
|
+
# Entry < ApplicationRecord
|
18
|
+
# Message < Entry
|
19
|
+
# Comment < Entry
|
20
|
+
#
|
21
|
+
# How do you show a feed that has both +Message+ and +Comment+ records, which can be easily paginated?
|
22
|
+
# Well, you can't! Messages are backed by a messages table and comments by a comments table. You can't
|
23
|
+
# pull from both tables at once and use a consistent OFFSET/LIMIT scheme.
|
24
|
+
#
|
25
|
+
# You can get around the pagination problem by using single-table inheritance, but now you're forced into
|
26
|
+
# a single mega table with all the attributes from all subclasses. No matter how divergent. If a Message
|
27
|
+
# has a subject, but the comment does not, well, now the comment does anyway! So STI works best when there's
|
28
|
+
# little divergence between the subclasses and their attributes.
|
29
|
+
#
|
30
|
+
# But there's a third way: Delegated types. With this approach, the "superclass" is a concrete class
|
31
|
+
# that is represented by its own table, where all the superclass attributes that are shared amongst all the
|
32
|
+
# "subclasses" are stored. And then each of the subclasses have their own individual tables for additional
|
33
|
+
# attributes that are particular to their implementation. This is similar to what's called multi-table
|
34
|
+
# inheritance in Django, but instead of actual inheritance, this approach uses delegation to form the
|
35
|
+
# hierarchy and share responsibilities.
|
36
|
+
#
|
37
|
+
# Let's look at that entry/message/comment example using delegated types:
|
38
|
+
#
|
39
|
+
# # Schema: entries[ id, account_id, creator_id, created_at, updated_at, entryable_type, entryable_id ]
|
40
|
+
# class Entry < ApplicationRecord
|
41
|
+
# belongs_to :account
|
42
|
+
# belongs_to :creator
|
43
|
+
# delegated_type :entryable, types: %w[ Message Comment ]
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# module Entryable
|
47
|
+
# extend ActiveSupport::Concern
|
48
|
+
#
|
49
|
+
# included do
|
50
|
+
# has_one :entry, as: :entryable, touch: true
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# # Schema: messages[ id, subject ]
|
55
|
+
# class Message < ApplicationRecord
|
56
|
+
# include Entryable
|
57
|
+
# has_rich_text :content
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# # Schema: comments[ id, content ]
|
61
|
+
# class Comment < ApplicationRecord
|
62
|
+
# include Entryable
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# As you can see, neither +Message+ nor +Comment+ are meant to stand alone. Crucial metadata for both classes
|
66
|
+
# resides in the +Entry+ "superclass". But the +Entry+ absolutely can stand alone in terms of querying capacity
|
67
|
+
# in particular. You can now easily do things like:
|
68
|
+
#
|
69
|
+
# Account.entries.order(created_at: :desc).limit(50)
|
70
|
+
#
|
71
|
+
# Which is exactly what you want when displaying both comments and messages together. The entry itself can
|
72
|
+
# be rendered as its delegated type easily, like so:
|
73
|
+
#
|
74
|
+
# # entries/_entry.html.erb
|
75
|
+
# <%= render "entries/entryables/#{entry.entryable_name}", entry: entry %>
|
76
|
+
#
|
77
|
+
# # entries/entryables/_message.html.erb
|
78
|
+
# <div class="message">
|
79
|
+
# Posted on <%= entry.created_at %> by <%= entry.creator.name %>: <%= entry.message.content %>
|
80
|
+
# </div>
|
81
|
+
#
|
82
|
+
# # entries/entryables/_comment.html.erb
|
83
|
+
# <div class="comment">
|
84
|
+
# <%= entry.creator.name %> said: <%= entry.comment.content %>
|
85
|
+
# </div>
|
86
|
+
#
|
87
|
+
# == Sharing behavior with concerns and controllers
|
88
|
+
#
|
89
|
+
# The entry "superclass" also serves as a perfect place to put all that shared logic that applies to both
|
90
|
+
# messages and comments, and which acts primarily on the shared attributes. Imagine:
|
91
|
+
#
|
92
|
+
# class Entry < ApplicationRecord
|
93
|
+
# include Eventable, Forwardable, Redeliverable
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# Which allows you to have controllers for things like +ForwardsController+ and +RedeliverableController+
|
97
|
+
# that both act on entries, and thus provide the shared functionality to both messages and comments.
|
98
|
+
#
|
99
|
+
# == Creating new records
|
100
|
+
#
|
101
|
+
# You create a new record that uses delegated typing by creating the delegator and delegatee at the same time,
|
102
|
+
# like so:
|
103
|
+
#
|
104
|
+
# Entry.create! entryable: Comment.new(content: "Hello!"), creator: Current.user
|
105
|
+
#
|
106
|
+
# If you need more complicated composition, or you need to perform dependent validation, you should build a factory
|
107
|
+
# method or class to take care of the complicated needs. This could be as simple as:
|
108
|
+
#
|
109
|
+
# class Entry < ApplicationRecord
|
110
|
+
# def self.create_with_comment(content, creator: Current.user)
|
111
|
+
# create! entryable: Comment.new(content: content), creator: creator
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# == Adding further delegation
|
116
|
+
#
|
117
|
+
# The delegated type shouldn't just answer the question of what the underlying class is called. In fact, that's
|
118
|
+
# an anti-pattern most of the time. The reason you're building this hierarchy is to take advantage of polymorphism.
|
119
|
+
# So here's a simple example of that:
|
120
|
+
#
|
121
|
+
# class Entry < ApplicationRecord
|
122
|
+
# delegated_type :entryable, types: %w[ Message Comment ]
|
123
|
+
# delegate :title, to: :entryable
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# class Message < ApplicationRecord
|
127
|
+
# def title
|
128
|
+
# subject
|
129
|
+
# end
|
130
|
+
# end
|
131
|
+
#
|
132
|
+
# class Comment < ApplicationRecord
|
133
|
+
# def title
|
134
|
+
# content.truncate(20)
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
# Now you can list a bunch of entries, call +Entry#title+, and polymorphism will provide you with the answer.
|
139
|
+
module DelegatedType
|
140
|
+
# Defines this as a class that'll delegate its type for the passed +role+ to the class references in +types+.
|
141
|
+
# That'll create a polymorphic +belongs_to+ relationship to that +role+, and it'll add all the delegated
|
142
|
+
# type convenience methods:
|
143
|
+
#
|
144
|
+
# class Entry < ApplicationRecord
|
145
|
+
# delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
|
146
|
+
# end
|
147
|
+
#
|
148
|
+
# Entry#entryable_class # => +Message+ or +Comment+
|
149
|
+
# Entry#entryable_name # => "message" or "comment"
|
150
|
+
# Entry.messages # => Entry.where(entryable_type: "Message")
|
151
|
+
# Entry#message? # => true when entryable_type == "Message"
|
152
|
+
# Entry#message # => returns the message record, when entryable_type == "Message", otherwise nil
|
153
|
+
# Entry#message_id # => returns entryable_id, when entryable_type == "Message", otherwise nil
|
154
|
+
# Entry.comments # => Entry.where(entryable_type: "Comment")
|
155
|
+
# Entry#comment? # => true when entryable_type == "Comment"
|
156
|
+
# Entry#comment # => returns the comment record, when entryable_type == "Comment", otherwise nil
|
157
|
+
# Entry#comment_id # => returns entryable_id, when entryable_type == "Comment", otherwise nil
|
158
|
+
#
|
159
|
+
# The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
|
160
|
+
#
|
161
|
+
# You can also declare namespaced types:
|
162
|
+
#
|
163
|
+
# class Entry < ApplicationRecord
|
164
|
+
# delegated_type :entryable, types: %w[ Message Comment Access::NoticeMessage ], dependent: :destroy
|
165
|
+
# end
|
166
|
+
#
|
167
|
+
# Entry.access_notice_messages
|
168
|
+
# entry.access_notice_message
|
169
|
+
# entry.access_notice_message?
|
170
|
+
def delegated_type(role, types:, **options)
|
171
|
+
belongs_to role, options.delete(:scope), **options.merge(polymorphic: true)
|
172
|
+
define_delegated_type_methods role, types: types
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
def define_delegated_type_methods(role, types:)
|
177
|
+
role_type = "#{role}_type"
|
178
|
+
role_id = "#{role}_id"
|
179
|
+
|
180
|
+
define_method "#{role}_class" do
|
181
|
+
public_send("#{role}_type").constantize
|
182
|
+
end
|
183
|
+
|
184
|
+
define_method "#{role}_name" do
|
185
|
+
public_send("#{role}_class").model_name.singular.inquiry
|
186
|
+
end
|
187
|
+
|
188
|
+
types.each do |type|
|
189
|
+
scope_name = type.tableize.gsub("/", "_")
|
190
|
+
singular = scope_name.singularize
|
191
|
+
query = "#{singular}?"
|
192
|
+
|
193
|
+
scope scope_name, -> { where(role_type => type) }
|
194
|
+
|
195
|
+
define_method query do
|
196
|
+
public_send(role_type) == type
|
197
|
+
end
|
198
|
+
|
199
|
+
define_method singular do
|
200
|
+
public_send(role) if public_send(query)
|
201
|
+
end
|
202
|
+
|
203
|
+
define_method "#{singular}_id" do
|
204
|
+
public_send(role_id) if public_send(query)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
class DestroyAssociationAsyncError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
# Job to destroy the records associated with a destroyed record in background.
|
8
|
+
class DestroyAssociationAsyncJob < ActiveJob::Base
|
9
|
+
queue_as { ActiveRecord::Base.queues[:destroy] }
|
10
|
+
|
11
|
+
discard_on ActiveJob::DeserializationError
|
12
|
+
|
13
|
+
def perform(
|
14
|
+
owner_model_name: nil, owner_id: nil,
|
15
|
+
association_class: nil, association_ids: nil, association_primary_key_column: nil,
|
16
|
+
ensuring_owner_was_method: nil
|
17
|
+
)
|
18
|
+
association_model = association_class.constantize
|
19
|
+
owner_class = owner_model_name.constantize
|
20
|
+
owner = owner_class.find_by(owner_class.primary_key.to_sym => owner_id)
|
21
|
+
|
22
|
+
if !owner_destroyed?(owner, ensuring_owner_was_method)
|
23
|
+
raise DestroyAssociationAsyncError, "owner record not destroyed"
|
24
|
+
end
|
25
|
+
|
26
|
+
association_model.where(association_primary_key_column => association_ids).find_each do |r|
|
27
|
+
r.destroy
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def owner_destroyed?(owner, ensuring_owner_was_method)
|
33
|
+
!owner || (ensuring_owner_was_method && owner.public_send(ensuring_owner_was_method))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -49,11 +49,11 @@ module ActiveRecord
|
|
49
49
|
|
50
50
|
attr_reader :model, :name, :attribute_names
|
51
51
|
|
52
|
-
def initialize(model,
|
52
|
+
def initialize(model, method_name)
|
53
53
|
@model = model
|
54
|
-
@name =
|
54
|
+
@name = method_name.to_s
|
55
55
|
@attribute_names = @name.match(self.class.pattern)[1].split("_and_")
|
56
|
-
@attribute_names.map! { |
|
56
|
+
@attribute_names.map! { |name| @model.attribute_aliases[name] || name }
|
57
57
|
end
|
58
58
|
|
59
59
|
def valid?
|
@@ -69,7 +69,6 @@ module ActiveRecord
|
|
69
69
|
end
|
70
70
|
|
71
71
|
private
|
72
|
-
|
73
72
|
def body
|
74
73
|
"#{finder}(#{attributes_hash})"
|
75
74
|
end
|
data/lib/active_record/enum.rb
CHANGED
@@ -31,7 +31,9 @@ module ActiveRecord
|
|
31
31
|
# as well. With the above example:
|
32
32
|
#
|
33
33
|
# Conversation.active
|
34
|
+
# Conversation.not_active
|
34
35
|
# Conversation.archived
|
36
|
+
# Conversation.not_archived
|
35
37
|
#
|
36
38
|
# Of course, you can also query them directly if the scopes don't fit your
|
37
39
|
# needs:
|
@@ -39,13 +41,20 @@ module ActiveRecord
|
|
39
41
|
# Conversation.where(status: [:active, :archived])
|
40
42
|
# Conversation.where.not(status: :active)
|
41
43
|
#
|
42
|
-
#
|
44
|
+
# Defining scopes can be disabled by setting +:_scopes+ to +false+.
|
43
45
|
#
|
44
|
-
#
|
45
|
-
#
|
46
|
+
# class Conversation < ActiveRecord::Base
|
47
|
+
# enum status: [ :active, :archived ], _scopes: false
|
46
48
|
# end
|
47
49
|
#
|
48
|
-
#
|
50
|
+
# You can set the default enum value by setting +:_default+, like:
|
51
|
+
#
|
52
|
+
# class Conversation < ActiveRecord::Base
|
53
|
+
# enum status: [ :active, :archived ], _default: "active"
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# conversation = Conversation.new
|
57
|
+
# conversation.status # => "active"
|
49
58
|
#
|
50
59
|
# Finally, it's also possible to explicitly map the relation between attribute and
|
51
60
|
# database integer with a hash:
|
@@ -115,63 +124,72 @@ module ActiveRecord
|
|
115
124
|
end
|
116
125
|
|
117
126
|
def cast(value)
|
118
|
-
return if value.blank?
|
119
|
-
|
120
127
|
if mapping.has_key?(value)
|
121
128
|
value.to_s
|
122
129
|
elsif mapping.has_value?(value)
|
123
130
|
mapping.key(value)
|
131
|
+
elsif value.blank?
|
132
|
+
nil
|
124
133
|
else
|
125
134
|
assert_valid_value(value)
|
126
135
|
end
|
127
136
|
end
|
128
137
|
|
129
138
|
def deserialize(value)
|
130
|
-
return if value.nil?
|
131
139
|
mapping.key(subtype.deserialize(value))
|
132
140
|
end
|
133
141
|
|
142
|
+
def serializable?(value)
|
143
|
+
(value.blank? || mapping.has_key?(value) || mapping.has_value?(value)) && super
|
144
|
+
end
|
145
|
+
|
134
146
|
def serialize(value)
|
135
147
|
mapping.fetch(value, value)
|
136
148
|
end
|
137
149
|
|
138
150
|
def assert_valid_value(value)
|
139
|
-
unless
|
151
|
+
unless serializable?(value)
|
140
152
|
raise ArgumentError, "'#{value}' is not a valid #{name}"
|
141
153
|
end
|
142
154
|
end
|
143
155
|
|
144
|
-
|
145
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
146
|
-
protected
|
147
|
-
|
156
|
+
private
|
148
157
|
attr_reader :name, :mapping, :subtype
|
149
158
|
end
|
150
159
|
|
151
160
|
def enum(definitions)
|
152
161
|
klass = self
|
162
|
+
|
153
163
|
enum_prefix = definitions.delete(:_prefix)
|
154
164
|
enum_suffix = definitions.delete(:_suffix)
|
165
|
+
enum_scopes = definitions.delete(:_scopes)
|
166
|
+
|
167
|
+
default = {}
|
168
|
+
default[:default] = definitions.delete(:_default) if definitions.key?(:_default)
|
169
|
+
|
155
170
|
definitions.each do |name, values|
|
171
|
+
assert_valid_enum_definition_values(values)
|
156
172
|
# statuses = { }
|
157
173
|
enum_values = ActiveSupport::HashWithIndifferentAccess.new
|
158
174
|
name = name.to_s
|
159
175
|
|
160
176
|
# def self.statuses() statuses end
|
161
177
|
detect_enum_conflict!(name, name.pluralize, true)
|
162
|
-
singleton_class.
|
178
|
+
singleton_class.define_method(name.pluralize) { enum_values }
|
163
179
|
defined_enums[name] = enum_values
|
164
180
|
|
165
181
|
detect_enum_conflict!(name, name)
|
166
182
|
detect_enum_conflict!(name, "#{name}=")
|
167
183
|
|
168
184
|
attr = attribute_alias?(name) ? attribute_alias(name) : name
|
169
|
-
|
185
|
+
|
186
|
+
decorate_attribute_type(attr, **default) do |subtype|
|
170
187
|
EnumType.new(attr, enum_values, subtype)
|
171
188
|
end
|
172
189
|
|
173
190
|
_enum_methods_module.module_eval do
|
174
191
|
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
|
192
|
+
value_method_names = []
|
175
193
|
pairs.each do |label, value|
|
176
194
|
if enum_prefix == true
|
177
195
|
prefix = "#{name}_"
|
@@ -184,7 +202,9 @@ module ActiveRecord
|
|
184
202
|
suffix = "_#{enum_suffix}"
|
185
203
|
end
|
186
204
|
|
187
|
-
|
205
|
+
method_friendly_label = label.to_s.gsub(/\W+/, "_")
|
206
|
+
value_method_name = "#{prefix}#{method_friendly_label}#{suffix}"
|
207
|
+
value_method_names << value_method_name
|
188
208
|
enum_values[label] = value
|
189
209
|
label = label.to_s
|
190
210
|
|
@@ -197,9 +217,16 @@ module ActiveRecord
|
|
197
217
|
define_method("#{value_method_name}!") { update!(attr => value) }
|
198
218
|
|
199
219
|
# scope :active, -> { where(status: 0) }
|
200
|
-
|
201
|
-
|
220
|
+
# scope :not_active, -> { where.not(status: 0) }
|
221
|
+
if enum_scopes != false
|
222
|
+
klass.send(:detect_enum_conflict!, name, value_method_name, true)
|
223
|
+
klass.scope value_method_name, -> { where(attr => value) }
|
224
|
+
|
225
|
+
klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
|
226
|
+
klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
|
227
|
+
end
|
202
228
|
end
|
229
|
+
klass.send(:detect_negative_enum_conditions!, value_method_names) if enum_scopes != false
|
203
230
|
end
|
204
231
|
enum_values.freeze
|
205
232
|
end
|
@@ -214,10 +241,24 @@ module ActiveRecord
|
|
214
241
|
end
|
215
242
|
end
|
216
243
|
|
244
|
+
def assert_valid_enum_definition_values(values)
|
245
|
+
unless values.is_a?(Hash) || values.all? { |v| v.is_a?(Symbol) } || values.all? { |v| v.is_a?(String) }
|
246
|
+
error_message = <<~MSG
|
247
|
+
Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
|
248
|
+
MSG
|
249
|
+
raise ArgumentError, error_message
|
250
|
+
end
|
251
|
+
|
252
|
+
if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
|
253
|
+
raise ArgumentError, "Enum label name must not be blank."
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
217
257
|
ENUM_CONFLICT_MESSAGE = \
|
218
258
|
"You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
|
219
259
|
"this will generate a %{type} method \"%{method}\", which is already defined " \
|
220
260
|
"by %{source}."
|
261
|
+
private_constant :ENUM_CONFLICT_MESSAGE
|
221
262
|
|
222
263
|
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
|
223
264
|
if klass_method && dangerous_class_method?(method_name)
|
@@ -240,5 +281,18 @@ module ActiveRecord
|
|
240
281
|
source: source
|
241
282
|
}
|
242
283
|
end
|
284
|
+
|
285
|
+
def detect_negative_enum_conditions!(method_names)
|
286
|
+
return unless logger
|
287
|
+
|
288
|
+
method_names.select { |m| m.start_with?("not_") }.each do |potential_not|
|
289
|
+
inverted_form = potential_not.sub("not_", "")
|
290
|
+
if method_names.include?(inverted_form)
|
291
|
+
logger.warn "Enum element '#{potential_not}' in #{self.name} uses the prefix 'not_'." \
|
292
|
+
" This has caused a conflict with auto generated negative scopes." \
|
293
|
+
" Avoid using enum elements starting with 'not' where the positive form is also an element."
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
243
297
|
end
|
244
298
|
end
|
data/lib/active_record/errors.rb
CHANGED
@@ -7,6 +7,10 @@ module ActiveRecord
|
|
7
7
|
class ActiveRecordError < StandardError
|
8
8
|
end
|
9
9
|
|
10
|
+
# Raised when trying to use a feature in Active Record which requires Active Job but the gem is not present.
|
11
|
+
class ActiveJobRequiredError < ActiveRecordError
|
12
|
+
end
|
13
|
+
|
10
14
|
# Raised when the single-table inheritance mechanism fails to locate the subclass
|
11
15
|
# (for example due to improper usage of column that
|
12
16
|
# {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
|
@@ -38,6 +42,10 @@ module ActiveRecord
|
|
38
42
|
class AdapterNotSpecified < ActiveRecordError
|
39
43
|
end
|
40
44
|
|
45
|
+
# Raised when a model makes a query but it has not specified an associated table.
|
46
|
+
class TableNotSpecified < ActiveRecordError
|
47
|
+
end
|
48
|
+
|
41
49
|
# Raised when Active Record cannot find database adapter specified in
|
42
50
|
# +config/database.yml+ or programmatically.
|
43
51
|
class AdapterNotFound < ActiveRecordError
|
@@ -49,6 +57,23 @@ module ActiveRecord
|
|
49
57
|
class ConnectionNotEstablished < ActiveRecordError
|
50
58
|
end
|
51
59
|
|
60
|
+
# Raised when a connection could not be obtained within the connection
|
61
|
+
# acquisition timeout period: because max connections in pool
|
62
|
+
# are in use.
|
63
|
+
class ConnectionTimeoutError < ConnectionNotEstablished
|
64
|
+
end
|
65
|
+
|
66
|
+
# Raised when a pool was unable to get ahold of all its connections
|
67
|
+
# to perform a "group" action such as
|
68
|
+
# {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
|
69
|
+
# or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
|
70
|
+
class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
|
71
|
+
end
|
72
|
+
|
73
|
+
# Raised when a write to the database is attempted on a read only connection.
|
74
|
+
class ReadOnlyError < ActiveRecordError
|
75
|
+
end
|
76
|
+
|
52
77
|
# Raised when Active Record cannot find a record by given id or set of ids.
|
53
78
|
class RecordNotFound < ActiveRecordError
|
54
79
|
attr_reader :model, :primary_key, :id
|
@@ -64,7 +89,7 @@ module ActiveRecord
|
|
64
89
|
|
65
90
|
# Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
|
66
91
|
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
|
67
|
-
# methods when a record is invalid and
|
92
|
+
# methods when a record is invalid and cannot be saved.
|
68
93
|
class RecordNotSaved < ActiveRecordError
|
69
94
|
attr_reader :record
|
70
95
|
|
@@ -97,9 +122,13 @@ module ActiveRecord
|
|
97
122
|
#
|
98
123
|
# Wraps the underlying database error as +cause+.
|
99
124
|
class StatementInvalid < ActiveRecordError
|
100
|
-
def initialize(message = nil)
|
101
|
-
super(message ||
|
125
|
+
def initialize(message = nil, sql: nil, binds: nil)
|
126
|
+
super(message || $!&.message)
|
127
|
+
@sql = sql
|
128
|
+
@binds = binds
|
102
129
|
end
|
130
|
+
|
131
|
+
attr_reader :sql, :binds
|
103
132
|
end
|
104
133
|
|
105
134
|
# Defunct wrapper class kept for compatibility.
|
@@ -111,14 +140,14 @@ module ActiveRecord
|
|
111
140
|
class RecordNotUnique < WrappedDatabaseException
|
112
141
|
end
|
113
142
|
|
114
|
-
# Raised when a record cannot be inserted or updated because it references a non-existent record
|
143
|
+
# Raised when a record cannot be inserted or updated because it references a non-existent record,
|
144
|
+
# or when a record cannot be deleted because a parent record references it.
|
115
145
|
class InvalidForeignKey < WrappedDatabaseException
|
116
146
|
end
|
117
147
|
|
118
148
|
# Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
|
119
149
|
class MismatchedForeignKey < StatementInvalid
|
120
150
|
def initialize(
|
121
|
-
adapter = nil,
|
122
151
|
message: nil,
|
123
152
|
sql: nil,
|
124
153
|
binds: nil,
|
@@ -130,14 +159,14 @@ module ActiveRecord
|
|
130
159
|
)
|
131
160
|
if table
|
132
161
|
type = primary_key_column.bigint? ? :bigint : primary_key_column.type
|
133
|
-
msg =
|
162
|
+
msg = <<~EOM.squish
|
134
163
|
Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
|
135
164
|
which has type `#{primary_key_column.sql_type}`.
|
136
165
|
To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
|
137
166
|
(For example `t.#{type} :#{foreign_key}`).
|
138
167
|
EOM
|
139
168
|
else
|
140
|
-
msg =
|
169
|
+
msg = <<~EOM.squish
|
141
170
|
There is a mismatch between the foreign key and primary key column types.
|
142
171
|
Verify that the foreign key column type and the primary key of the associated table match types.
|
143
172
|
EOM
|
@@ -145,7 +174,7 @@ module ActiveRecord
|
|
145
174
|
if message
|
146
175
|
msg << "\nOriginal message: #{message}"
|
147
176
|
end
|
148
|
-
super(msg)
|
177
|
+
super(msg, sql: sql, binds: binds)
|
149
178
|
end
|
150
179
|
end
|
151
180
|
|
@@ -161,9 +190,9 @@ module ActiveRecord
|
|
161
190
|
class RangeError < StatementInvalid
|
162
191
|
end
|
163
192
|
|
164
|
-
# Raised when number of
|
165
|
-
#
|
166
|
-
# does not match number of
|
193
|
+
# Raised when the number of placeholders in an SQL fragment passed to
|
194
|
+
# {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]
|
195
|
+
# does not match the number of values supplied.
|
167
196
|
#
|
168
197
|
# For example, when there are two placeholders with only one value supplied:
|
169
198
|
#
|
@@ -175,6 +204,10 @@ module ActiveRecord
|
|
175
204
|
class NoDatabaseError < StatementInvalid
|
176
205
|
end
|
177
206
|
|
207
|
+
# Raised when creating a database if it exists.
|
208
|
+
class DatabaseAlreadyExists < StatementInvalid
|
209
|
+
end
|
210
|
+
|
178
211
|
# Raised when PostgreSQL returns 'cached plan must not change result type' and
|
179
212
|
# we cannot retry gracefully (e.g. inside a transaction)
|
180
213
|
class PreparedStatementCacheExpired < StatementInvalid
|
@@ -212,6 +245,10 @@ module ActiveRecord
|
|
212
245
|
class ReadOnlyRecord < ActiveRecordError
|
213
246
|
end
|
214
247
|
|
248
|
+
# Raised on attempt to lazily load records that are marked as strict loading.
|
249
|
+
class StrictLoadingViolationError < ActiveRecordError
|
250
|
+
end
|
251
|
+
|
215
252
|
# {ActiveRecord::Base.transaction}[rdoc-ref:Transactions::ClassMethods#transaction]
|
216
253
|
# uses this exception to distinguish a deliberate rollback from other exceptional situations.
|
217
254
|
# Normally, raising an exception will cause the
|
@@ -322,7 +359,7 @@ module ActiveRecord
|
|
322
359
|
# See the following:
|
323
360
|
#
|
324
361
|
# * https://www.postgresql.org/docs/current/static/transaction-iso.html
|
325
|
-
# * https://dev.mysql.com/doc/
|
362
|
+
# * https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html#error_er_lock_deadlock
|
326
363
|
class TransactionRollbackError < StatementInvalid
|
327
364
|
end
|
328
365
|
|
@@ -341,30 +378,36 @@ module ActiveRecord
|
|
341
378
|
class IrreversibleOrderError < ActiveRecordError
|
342
379
|
end
|
343
380
|
|
381
|
+
# Superclass for errors that have been aborted (either by client or server).
|
382
|
+
class QueryAborted < StatementInvalid
|
383
|
+
end
|
384
|
+
|
344
385
|
# LockWaitTimeout will be raised when lock wait timeout exceeded.
|
345
386
|
class LockWaitTimeout < StatementInvalid
|
346
387
|
end
|
347
388
|
|
348
389
|
# StatementTimeout will be raised when statement timeout exceeded.
|
349
|
-
class StatementTimeout <
|
390
|
+
class StatementTimeout < QueryAborted
|
350
391
|
end
|
351
392
|
|
352
393
|
# QueryCanceled will be raised when canceling statement due to user request.
|
353
|
-
class QueryCanceled <
|
394
|
+
class QueryCanceled < QueryAborted
|
395
|
+
end
|
396
|
+
|
397
|
+
# AdapterTimeout will be raised when database clients times out while waiting from the server.
|
398
|
+
class AdapterTimeout < QueryAborted
|
354
399
|
end
|
355
400
|
|
356
401
|
# UnknownAttributeReference is raised when an unknown and potentially unsafe
|
357
|
-
# value is passed to a query method
|
358
|
-
#
|
359
|
-
# #order method might cause this exception.
|
402
|
+
# value is passed to a query method. For example, passing a non column name
|
403
|
+
# value to a relation's #order method might cause this exception.
|
360
404
|
#
|
361
405
|
# When working around this exception, caution should be taken to avoid SQL
|
362
406
|
# injection vulnerabilities when passing user-provided values to query
|
363
407
|
# methods. Known-safe values can be passed to query methods by wrapping them
|
364
408
|
# in Arel.sql.
|
365
409
|
#
|
366
|
-
# For example,
|
367
|
-
# code would raise this exception:
|
410
|
+
# For example, the following code would raise this exception:
|
368
411
|
#
|
369
412
|
# Post.order("length(title)").first
|
370
413
|
#
|