activerecord 6.0.0 → 7.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +996 -594
- data/MIT-LICENSE +1 -1
- data/README.rdoc +34 -34
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +22 -20
- data/lib/active_record/association_relation.rb +22 -12
- data/lib/active_record/associations/alias_tracker.rb +41 -30
- data/lib/active_record/associations/association.rb +106 -41
- data/lib/active_record/associations/association_scope.rb +30 -21
- data/lib/active_record/associations/belongs_to_association.rb +69 -14
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +20 -6
- data/lib/active_record/associations/builder/association.rb +39 -6
- data/lib/active_record/associations/builder/belongs_to.rb +47 -17
- data/lib/active_record/associations/builder/collection_association.rb +14 -6
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -10
- data/lib/active_record/associations/builder/has_many.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +13 -16
- data/lib/active_record/associations/builder/singular_association.rb +7 -3
- data/lib/active_record/associations/collection_association.rb +90 -53
- data/lib/active_record/associations/collection_proxy.rb +54 -19
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +21 -1
- data/lib/active_record/associations/has_many_association.rb +41 -10
- data/lib/active_record/associations/has_many_through_association.rb +29 -12
- data/lib/active_record/associations/has_one_association.rb +33 -9
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +41 -17
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/join_dependency.rb +97 -54
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +237 -54
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +51 -17
- data/lib/active_record/associations/preloader.rb +55 -121
- data/lib/active_record/associations/singular_association.rb +16 -4
- data/lib/active_record/associations/through_association.rb +26 -15
- data/lib/active_record/associations.rb +454 -440
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +11 -14
- data/lib/active_record/attribute_methods/before_type_cast.rb +36 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +75 -34
- data/lib/active_record/attribute_methods/primary_key.rb +53 -31
- data/lib/active_record/attribute_methods/query.rb +31 -22
- data/lib/active_record/attribute_methods/read.rb +16 -17
- data/lib/active_record/attribute_methods/serialization.rb +177 -35
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +18 -15
- data/lib/active_record/attribute_methods/write.rb +16 -28
- data/lib/active_record/attribute_methods.rb +227 -100
- data/lib/active_record/attributes.rb +94 -56
- data/lib/active_record/autosave_association.rb +119 -73
- data/lib/active_record/base.rb +31 -21
- data/lib/active_record/callbacks.rb +168 -55
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -25
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +367 -565
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -57
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +277 -89
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +241 -69
- data/lib/active_record/connection_adapters/abstract/quoting.rb +122 -134
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +324 -72
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +17 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +611 -211
- data/lib/active_record/connection_adapters/abstract/transaction.rb +425 -82
- data/lib/active_record/connection_adapters/abstract_adapter.rb +698 -211
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +464 -239
- data/lib/active_record/connection_adapters/column.rb +28 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +2 -1
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +32 -137
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
- data/lib/active_record/connection_adapters/mysql/quoting.rb +90 -43
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +41 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +18 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +13 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +53 -15
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +127 -63
- data/lib/active_record/connection_adapters/pool_config.rb +83 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +54 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +127 -100
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +9 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -15
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +35 -8
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -4
- data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +139 -106
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +98 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +176 -4
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -118
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +585 -295
- data/lib/active_record/connection_adapters/schema_cache.rb +399 -60
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +99 -48
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +80 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +27 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +102 -24
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +425 -174
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -1
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +176 -0
- data/lib/active_record/connection_handling.rb +243 -115
- data/lib/active_record/core.rb +481 -199
- data/lib/active_record/counter_cache.rb +69 -32
- data/lib/active_record/database_configurations/connection_url_resolver.rb +107 -0
- data/lib/active_record/database_configurations/database_config.rb +77 -10
- data/lib/active_record/database_configurations/hash_config.rb +148 -26
- data/lib/active_record/database_configurations/url_config.rb +44 -45
- data/lib/active_record/database_configurations.rb +190 -114
- data/lib/active_record/delegated_type.rb +279 -0
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +38 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +5 -6
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +171 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +100 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +224 -73
- data/lib/active_record/errors.rb +254 -36
- data/lib/active_record/explain.rb +30 -17
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +2 -2
- data/lib/active_record/fixture_set/file.rb +22 -15
- data/lib/active_record/fixture_set/model_metadata.rb +15 -6
- data/lib/active_record/fixture_set/render_context.rb +3 -1
- data/lib/active_record/fixture_set/table_row.rb +88 -16
- data/lib/active_record/fixture_set/table_rows.rb +4 -5
- data/lib/active_record/fixtures.rb +229 -116
- data/lib/active_record/future_result.rb +178 -0
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +121 -48
- data/lib/active_record/insert_all.rb +178 -29
- data/lib/active_record/integration.rb +16 -14
- data/lib/active_record/internal_metadata.rb +132 -21
- data/lib/active_record/legacy_yaml_adapter.rb +3 -36
- data/lib/active_record/locking/optimistic.rb +64 -33
- data/lib/active_record/locking/pessimistic.rb +21 -8
- data/lib/active_record/log_subscriber.rb +61 -30
- data/lib/active_record/marshalling.rb +59 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +19 -19
- data/lib/active_record/middleware/database_selector.rb +25 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +160 -55
- data/lib/active_record/migration/compatibility.rb +286 -43
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/join_table.rb +1 -2
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +421 -193
- data/lib/active_record/model_schema.rb +217 -125
- data/lib/active_record/nested_attributes.rb +62 -27
- data/lib/active_record/no_touching.rb +4 -4
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +322 -319
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -15
- data/lib/active_record/query_logs.rb +193 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +54 -14
- data/lib/active_record/railtie.rb +250 -72
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/controller_runtime.rb +25 -11
- data/lib/active_record/railties/databases.rake +312 -197
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +45 -3
- data/lib/active_record/reflection.rb +389 -146
- data/lib/active_record/relation/batches/batch_enumerator.rb +61 -16
- data/lib/active_record/relation/batches.rb +214 -73
- data/lib/active_record/relation/calculations.rb +379 -124
- data/lib/active_record/relation/delegation.rb +36 -23
- data/lib/active_record/relation/finder_methods.rb +159 -49
- data/lib/active_record/relation/from_clause.rb +5 -1
- data/lib/active_record/relation/merger.rb +41 -33
- data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +42 -7
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +20 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +79 -53
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +1156 -279
- data/lib/active_record/relation/record_fetch_warning.rb +12 -11
- data/lib/active_record/relation/spawn_methods.rb +10 -9
- data/lib/active_record/relation/where_clause.rb +100 -66
- data/lib/active_record/relation.rb +829 -194
- data/lib/active_record/result.rb +76 -56
- data/lib/active_record/runtime_registry.rb +71 -13
- data/lib/active_record/sanitization.rb +86 -47
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +140 -33
- data/lib/active_record/schema_migration.rb +74 -29
- data/lib/active_record/scoping/default.rb +73 -19
- data/lib/active_record/scoping/named.rb +10 -28
- data/lib/active_record/scoping.rb +65 -35
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +34 -8
- data/lib/active_record/serialization.rb +11 -4
- data/lib/active_record/signed_id.rb +138 -0
- data/lib/active_record/statement_cache.rb +26 -10
- data/lib/active_record/store.rb +19 -14
- data/lib/active_record/suppressor.rb +15 -17
- data/lib/active_record/table_metadata.rb +46 -36
- data/lib/active_record/tasks/database_tasks.rb +371 -205
- data/lib/active_record/tasks/mysql_database_tasks.rb +43 -36
- data/lib/active_record/tasks/postgresql_database_tasks.rb +54 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -13
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +189 -104
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +35 -25
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +31 -27
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +131 -99
- data/lib/active_record/translation.rb +3 -5
- data/lib/active_record/type/adapter_specific_registry.rb +33 -18
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +11 -6
- data/lib/active_record/type/time.rb +14 -0
- data/lib/active_record/type/type_map.rb +17 -21
- data/lib/active_record/type/unsigned_integer.rb +0 -1
- data/lib/active_record/type.rb +7 -2
- data/lib/active_record/type_caster/connection.rb +4 -5
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +13 -8
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +88 -18
- data/lib/active_record/validations.rb +15 -8
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +446 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/attributes/attribute.rb +4 -8
- data/lib/arel/collectors/bind.rb +8 -1
- data/lib/arel/collectors/composite.rb +15 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/crud.rb +30 -22
- data/lib/arel/delete_manager.rb +23 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- data/lib/arel/nodes/binary.rb +82 -9
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/casted.rb +22 -10
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +14 -13
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +122 -11
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +16 -0
- data/lib/arel/nodes/table_alias.rb +11 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/nodes/update_statement.rb +11 -4
- data/lib/arel/nodes.rb +10 -3
- data/lib/arel/predications.rb +31 -28
- data/lib/arel/select_manager.rb +18 -9
- data/lib/arel/table.rb +21 -10
- data/lib/arel/tree_manager.rb +8 -15
- data/lib/arel/update_manager.rb +25 -5
- data/lib/arel/visitors/dot.rb +94 -90
- data/lib/arel/visitors/mysql.rb +34 -6
- data/lib/arel/visitors/postgresql.rb +5 -16
- data/lib/arel/visitors/sqlite.rb +25 -1
- data/lib/arel/visitors/to_sql.rb +227 -81
- data/lib/arel/visitors/visitor.rb +2 -3
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel.rb +37 -15
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +6 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
- data/lib/rails/generators/active_record/migration.rb +9 -3
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +49 -4
- 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 +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +117 -30
- data/lib/active_record/attribute_decorators.rb +0 -90
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/null_relation.rb +0 -68
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -204
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -157
- data/lib/arel/visitors/oracle.rb +0 -159
- data/lib/arel/visitors/oracle12.rb +0 -66
- data/lib/arel/visitors/where_sql.rb +0 -23
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/core_ext/array"
|
|
4
|
-
require "active_support/core_ext/hash/except"
|
|
5
|
-
require "active_support/core_ext/kernel/singleton_class"
|
|
6
|
-
|
|
7
3
|
module ActiveRecord
|
|
8
4
|
# = Active Record \Named \Scopes
|
|
9
5
|
module Scoping
|
|
@@ -23,25 +19,17 @@ module ActiveRecord
|
|
|
23
19
|
#
|
|
24
20
|
# You can define a scope that applies to all finders using
|
|
25
21
|
# {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope].
|
|
26
|
-
def all
|
|
22
|
+
def all(all_queries: nil)
|
|
27
23
|
scope = current_scope
|
|
28
24
|
|
|
29
25
|
if scope
|
|
30
|
-
if scope._deprecated_scope_source
|
|
31
|
-
ActiveSupport::Deprecation.warn(<<~MSG.squish)
|
|
32
|
-
Class level methods will no longer inherit scoping from `#{scope._deprecated_scope_source}`
|
|
33
|
-
in Rails 6.1. To continue using the scoped relation, pass it into the block directly.
|
|
34
|
-
To instead access the full set of models, as Rails 6.1 will, use `#{name}.unscoped`.
|
|
35
|
-
MSG
|
|
36
|
-
end
|
|
37
|
-
|
|
38
26
|
if self == scope.klass
|
|
39
27
|
scope.clone
|
|
40
28
|
else
|
|
41
29
|
relation.merge!(scope)
|
|
42
30
|
end
|
|
43
31
|
else
|
|
44
|
-
default_scoped
|
|
32
|
+
default_scoped(all_queries: all_queries)
|
|
45
33
|
end
|
|
46
34
|
end
|
|
47
35
|
|
|
@@ -53,8 +41,9 @@ module ActiveRecord
|
|
|
53
41
|
end
|
|
54
42
|
end
|
|
55
43
|
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
# Returns a scope for the model with default scopes.
|
|
45
|
+
def default_scoped(scope = relation, all_queries: nil)
|
|
46
|
+
build_default_scope(scope, all_queries: all_queries) || scope
|
|
58
47
|
end
|
|
59
48
|
|
|
60
49
|
def default_extensions # :nodoc:
|
|
@@ -83,10 +72,6 @@ module ActiveRecord
|
|
|
83
72
|
# <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, in effect,
|
|
84
73
|
# represents the query <tt>Shirt.where(color: 'red')</tt>.
|
|
85
74
|
#
|
|
86
|
-
# You should always pass a callable object to the scopes defined
|
|
87
|
-
# with #scope. This ensures that the scope is re-evaluated each
|
|
88
|
-
# time it is called.
|
|
89
|
-
#
|
|
90
75
|
# Note that this is simply 'syntactic sugar' for defining an actual
|
|
91
76
|
# class method:
|
|
92
77
|
#
|
|
@@ -183,12 +168,11 @@ module ActiveRecord
|
|
|
183
168
|
"an instance method with the same name."
|
|
184
169
|
end
|
|
185
170
|
|
|
186
|
-
valid_scope_name?(name)
|
|
187
171
|
extension = Module.new(&block) if block
|
|
188
172
|
|
|
189
173
|
if body.respond_to?(:to_proc)
|
|
190
174
|
singleton_class.define_method(name) do |*args|
|
|
191
|
-
scope = all._exec_scope(
|
|
175
|
+
scope = all._exec_scope(*args, &body)
|
|
192
176
|
scope = scope.extending(extension) if extension
|
|
193
177
|
scope
|
|
194
178
|
end
|
|
@@ -199,17 +183,15 @@ module ActiveRecord
|
|
|
199
183
|
scope
|
|
200
184
|
end
|
|
201
185
|
end
|
|
186
|
+
singleton_class.send(:ruby2_keywords, name)
|
|
202
187
|
|
|
203
188
|
generate_relation_method(name)
|
|
204
189
|
end
|
|
205
190
|
|
|
206
191
|
private
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if respond_to?(name
|
|
210
|
-
logger.warn "Creating scope :#{name}. " \
|
|
211
|
-
"Overwriting existing method #{self.name}.#{name}."
|
|
212
|
-
end
|
|
192
|
+
def singleton_method_added(name)
|
|
193
|
+
super
|
|
194
|
+
generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
|
|
213
195
|
end
|
|
214
196
|
end
|
|
215
197
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/
|
|
3
|
+
require "active_support/core_ext/module/delegation"
|
|
4
4
|
|
|
5
5
|
module ActiveRecord
|
|
6
6
|
module Scoping
|
|
@@ -24,11 +24,23 @@ module ActiveRecord
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def current_scope(skip_inherited_scope = false)
|
|
27
|
-
ScopeRegistry.
|
|
27
|
+
ScopeRegistry.current_scope(self, skip_inherited_scope)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def current_scope=(scope)
|
|
31
|
-
ScopeRegistry.
|
|
31
|
+
ScopeRegistry.set_current_scope(self, scope)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def global_current_scope(skip_inherited_scope = false)
|
|
35
|
+
ScopeRegistry.global_current_scope(self, skip_inherited_scope)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def global_current_scope=(scope)
|
|
39
|
+
ScopeRegistry.set_global_current_scope(self, scope)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def scope_registry
|
|
43
|
+
ScopeRegistry.instance
|
|
32
44
|
end
|
|
33
45
|
end
|
|
34
46
|
|
|
@@ -45,8 +57,8 @@ module ActiveRecord
|
|
|
45
57
|
end
|
|
46
58
|
|
|
47
59
|
# This class stores the +:current_scope+ and +:ignore_default_scope+ values
|
|
48
|
-
# for different classes. The registry is stored as a thread
|
|
49
|
-
#
|
|
60
|
+
# for different classes. The registry is stored as either a thread or fiber
|
|
61
|
+
# local depending on the application configuration.
|
|
50
62
|
#
|
|
51
63
|
# This class allows you to store and get the scope values on different
|
|
52
64
|
# classes and different types of scopes. For example, if you are attempting
|
|
@@ -54,52 +66,70 @@ module ActiveRecord
|
|
|
54
66
|
# following code:
|
|
55
67
|
#
|
|
56
68
|
# registry = ActiveRecord::Scoping::ScopeRegistry
|
|
57
|
-
# registry.
|
|
69
|
+
# registry.set_current_scope(Board, some_new_scope)
|
|
58
70
|
#
|
|
59
71
|
# Now when you run:
|
|
60
72
|
#
|
|
61
|
-
# registry.
|
|
73
|
+
# registry.current_scope(Board)
|
|
62
74
|
#
|
|
63
|
-
# You will obtain whatever was defined in +some_new_scope+.
|
|
64
|
-
# and #set_value_for methods are delegated to the current ScopeRegistry
|
|
65
|
-
# object, so the above example code can also be called as:
|
|
66
|
-
#
|
|
67
|
-
# ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
|
|
68
|
-
# Board, some_new_scope)
|
|
75
|
+
# You will obtain whatever was defined in +some_new_scope+.
|
|
69
76
|
class ScopeRegistry # :nodoc:
|
|
70
|
-
|
|
77
|
+
class << self
|
|
78
|
+
delegate :current_scope, :set_current_scope, :ignore_default_scope, :set_ignore_default_scope,
|
|
79
|
+
:global_current_scope, :set_global_current_scope, to: :instance
|
|
71
80
|
|
|
72
|
-
|
|
81
|
+
def instance
|
|
82
|
+
ActiveSupport::IsolatedExecutionState[:active_record_scope_registry] ||= new
|
|
83
|
+
end
|
|
84
|
+
end
|
|
73
85
|
|
|
74
86
|
def initialize
|
|
75
|
-
@
|
|
87
|
+
@current_scope = {}
|
|
88
|
+
@ignore_default_scope = {}
|
|
89
|
+
@global_current_scope = {}
|
|
76
90
|
end
|
|
77
91
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
raise_invalid_scope_type!(scope_type)
|
|
81
|
-
return @registry[scope_type][model.name] if skip_inherited_scope
|
|
82
|
-
klass = model
|
|
83
|
-
base = model.base_class
|
|
84
|
-
while klass <= base
|
|
85
|
-
value = @registry[scope_type][klass.name]
|
|
86
|
-
return value if value
|
|
87
|
-
klass = klass.superclass
|
|
88
|
-
end
|
|
92
|
+
def current_scope(model, skip_inherited_scope = false)
|
|
93
|
+
value_for(@current_scope, model, skip_inherited_scope)
|
|
89
94
|
end
|
|
90
95
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
raise_invalid_scope_type!(scope_type)
|
|
94
|
-
@registry[scope_type][model.name] = value
|
|
96
|
+
def set_current_scope(model, value)
|
|
97
|
+
set_value_for(@current_scope, model, value)
|
|
95
98
|
end
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
def ignore_default_scope(model, skip_inherited_scope = false)
|
|
101
|
+
value_for(@ignore_default_scope, model, skip_inherited_scope)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def set_ignore_default_scope(model, value)
|
|
105
|
+
set_value_for(@ignore_default_scope, model, value)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def global_current_scope(model, skip_inherited_scope = false)
|
|
109
|
+
value_for(@global_current_scope, model, skip_inherited_scope)
|
|
110
|
+
end
|
|
98
111
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
def set_global_current_scope(model, value)
|
|
113
|
+
set_value_for(@global_current_scope, model, value)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
# Obtains the value for a given +scope_type+ and +model+.
|
|
118
|
+
def value_for(scope_type, model, skip_inherited_scope = false)
|
|
119
|
+
return scope_type[model.name] if skip_inherited_scope
|
|
120
|
+
klass = model
|
|
121
|
+
base = model.base_class
|
|
122
|
+
while klass != base
|
|
123
|
+
value = scope_type[klass.name]
|
|
124
|
+
return value if value
|
|
125
|
+
klass = klass.superclass
|
|
102
126
|
end
|
|
127
|
+
scope_type[klass.name]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Sets the +value+ for a given +scope_type+ and +model+.
|
|
131
|
+
def set_value_for(scope_type, model, value)
|
|
132
|
+
scope_type[model.name] = value
|
|
103
133
|
end
|
|
104
134
|
end
|
|
105
135
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module SecurePassword
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
include ActiveModel::SecurePassword
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# Given a set of attributes, finds a record using the non-password
|
|
11
|
+
# attributes, and then authenticates that record using the password
|
|
12
|
+
# attributes. Returns the record if authentication succeeds; otherwise,
|
|
13
|
+
# returns +nil+.
|
|
14
|
+
#
|
|
15
|
+
# Regardless of whether a record is found, +authenticate_by+ will
|
|
16
|
+
# cryptographically digest the given password attributes. This behavior
|
|
17
|
+
# helps mitigate timing-based enumeration attacks, wherein an attacker can
|
|
18
|
+
# determine if a passworded record exists even without knowing the
|
|
19
|
+
# password.
|
|
20
|
+
#
|
|
21
|
+
# Raises an ArgumentError if the set of attributes doesn't contain at
|
|
22
|
+
# least one password and one non-password attribute.
|
|
23
|
+
#
|
|
24
|
+
# ==== Examples
|
|
25
|
+
#
|
|
26
|
+
# class User < ActiveRecord::Base
|
|
27
|
+
# has_secure_password
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# User.create(name: "John Doe", email: "jdoe@example.com", password: "abc123")
|
|
31
|
+
#
|
|
32
|
+
# User.authenticate_by(email: "jdoe@example.com", password: "abc123").name # => "John Doe" (in 373.4ms)
|
|
33
|
+
# User.authenticate_by(email: "jdoe@example.com", password: "wrong") # => nil (in 373.9ms)
|
|
34
|
+
# User.authenticate_by(email: "wrong@example.com", password: "abc123") # => nil (in 373.6ms)
|
|
35
|
+
#
|
|
36
|
+
# User.authenticate_by(email: "jdoe@example.com", password: nil) # => nil (no queries executed)
|
|
37
|
+
# User.authenticate_by(email: "jdoe@example.com", password: "") # => nil (no queries executed)
|
|
38
|
+
#
|
|
39
|
+
# User.authenticate_by(email: "jdoe@example.com") # => ArgumentError
|
|
40
|
+
# User.authenticate_by(password: "abc123") # => ArgumentError
|
|
41
|
+
def authenticate_by(attributes)
|
|
42
|
+
passwords, identifiers = attributes.to_h.partition do |name, value|
|
|
43
|
+
!has_attribute?(name) && has_attribute?("#{name}_digest")
|
|
44
|
+
end.map(&:to_h)
|
|
45
|
+
|
|
46
|
+
raise ArgumentError, "One or more password arguments are required" if passwords.empty?
|
|
47
|
+
raise ArgumentError, "One or more finder arguments are required" if identifiers.empty?
|
|
48
|
+
|
|
49
|
+
return if passwords.any? { |name, value| value.nil? || value.empty? }
|
|
50
|
+
|
|
51
|
+
if record = find_by(identifiers)
|
|
52
|
+
record if passwords.count { |name, value| record.public_send(:"authenticate_#{name}", value) } == passwords.size
|
|
53
|
+
else
|
|
54
|
+
new(passwords)
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module SecureToken
|
|
5
|
+
class MinimumLengthError < StandardError; end
|
|
6
|
+
|
|
7
|
+
MINIMUM_TOKEN_LENGTH = 24
|
|
8
|
+
|
|
5
9
|
extend ActiveSupport::Concern
|
|
6
10
|
|
|
7
11
|
module ClassMethods
|
|
@@ -10,30 +14,52 @@ module ActiveRecord
|
|
|
10
14
|
# # Schema: User(token:string, auth_token:string)
|
|
11
15
|
# class User < ActiveRecord::Base
|
|
12
16
|
# has_secure_token
|
|
13
|
-
# has_secure_token :auth_token
|
|
17
|
+
# has_secure_token :auth_token, length: 36
|
|
14
18
|
# end
|
|
15
19
|
#
|
|
16
20
|
# user = User.new
|
|
17
21
|
# user.save
|
|
18
22
|
# user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
|
|
19
|
-
# user.auth_token # => "
|
|
23
|
+
# user.auth_token # => "tU9bLuZseefXQ4yQxQo8wjtBvsAfPc78os6R"
|
|
20
24
|
# user.regenerate_token # => true
|
|
21
25
|
# user.regenerate_auth_token # => true
|
|
22
26
|
#
|
|
23
|
-
#
|
|
27
|
+
# +SecureRandom::base58+ is used to generate at minimum a 24-character unique token, so collisions are highly unlikely.
|
|
24
28
|
#
|
|
25
29
|
# Note that it's still possible to generate a race condition in the database in the same way that
|
|
26
30
|
# {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
|
|
27
31
|
# You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
|
|
28
|
-
|
|
32
|
+
#
|
|
33
|
+
# ==== Options
|
|
34
|
+
#
|
|
35
|
+
# [+:length+]
|
|
36
|
+
# Length of the Secure Random, with a minimum of 24 characters. It will
|
|
37
|
+
# default to 24.
|
|
38
|
+
#
|
|
39
|
+
# [+:on+]
|
|
40
|
+
# The callback when the value is generated. When called with <tt>on:
|
|
41
|
+
# :initialize</tt>, the value is generated in an
|
|
42
|
+
# <tt>after_initialize</tt> callback, otherwise the value will be used
|
|
43
|
+
# in a <tt>before_</tt> callback. When not specified, +:on+ will use the value of
|
|
44
|
+
# <tt>config.active_record.generate_secure_token_on</tt>, which defaults to +:initialize+
|
|
45
|
+
# starting in \Rails 7.1.
|
|
46
|
+
def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH, on: ActiveRecord.generate_secure_token_on)
|
|
47
|
+
if length < MINIMUM_TOKEN_LENGTH
|
|
48
|
+
raise MinimumLengthError, "Token requires a minimum length of #{MINIMUM_TOKEN_LENGTH} characters."
|
|
49
|
+
end
|
|
50
|
+
|
|
29
51
|
# Load securerandom only when has_secure_token is used.
|
|
30
52
|
require "active_support/core_ext/securerandom"
|
|
31
|
-
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token }
|
|
32
|
-
|
|
53
|
+
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(length: length) }
|
|
54
|
+
set_callback on, on == :initialize ? :after : :before do
|
|
55
|
+
if new_record? && !query_attribute(attribute)
|
|
56
|
+
send("#{attribute}=", self.class.generate_unique_secure_token(length: length))
|
|
57
|
+
end
|
|
58
|
+
end
|
|
33
59
|
end
|
|
34
60
|
|
|
35
|
-
def generate_unique_secure_token
|
|
36
|
-
SecureRandom.base58(
|
|
61
|
+
def generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH)
|
|
62
|
+
SecureRandom.base58(length)
|
|
37
63
|
end
|
|
38
64
|
end
|
|
39
65
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module ActiveRecord
|
|
3
|
+
module ActiveRecord # :nodoc:
|
|
4
4
|
# = Active Record \Serialization
|
|
5
5
|
module Serialization
|
|
6
6
|
extend ActiveSupport::Concern
|
|
@@ -11,12 +11,19 @@ module ActiveRecord #:nodoc:
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def serializable_hash(options = nil)
|
|
14
|
-
|
|
14
|
+
if self.class._has_attribute?(self.class.inheritance_column)
|
|
15
|
+
options = options ? options.dup : {}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
options[:except] = Array(options[:except]).map(&:to_s)
|
|
18
|
+
options[:except] |= Array(self.class.inheritance_column)
|
|
19
|
+
end
|
|
18
20
|
|
|
19
21
|
super(options)
|
|
20
22
|
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
def attribute_names_for_serialization
|
|
26
|
+
attribute_names
|
|
27
|
+
end
|
|
21
28
|
end
|
|
22
29
|
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
# = Active Record Signed Id
|
|
5
|
+
module SignedId
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
##
|
|
10
|
+
# :singleton-method:
|
|
11
|
+
# Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
|
|
12
|
+
# Within \Rails, this is automatically set using the \Rails application key generator.
|
|
13
|
+
class_attribute :signed_id_verifier_secret, instance_writer: false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module RelationMethods # :nodoc:
|
|
17
|
+
def find_signed(...)
|
|
18
|
+
scoping { model.find_signed(...) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def find_signed!(...)
|
|
22
|
+
scoping { model.find_signed!(...) }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module ClassMethods
|
|
27
|
+
# Lets you find a record based on a signed id that's safe to put into the world without risk of tampering.
|
|
28
|
+
# This is particularly useful for things like password reset or email verification, where you want
|
|
29
|
+
# the bearer of the signed id to be able to interact with the underlying record, but usually only within
|
|
30
|
+
# a certain time period.
|
|
31
|
+
#
|
|
32
|
+
# You set the time period that the signed id is valid for during generation, using the instance method
|
|
33
|
+
# <tt>signed_id(expires_in: 15.minutes)</tt>. If the time has elapsed before a signed find is attempted,
|
|
34
|
+
# the signed id will no longer be valid, and nil is returned.
|
|
35
|
+
#
|
|
36
|
+
# It's possible to further restrict the use of a signed id with a purpose. This helps when you have a
|
|
37
|
+
# general base model, like a User, which might have signed ids for several things, like password reset
|
|
38
|
+
# or email verification. The purpose that was set during generation must match the purpose set when
|
|
39
|
+
# finding. If there's a mismatch, nil is again returned.
|
|
40
|
+
#
|
|
41
|
+
# ==== Examples
|
|
42
|
+
#
|
|
43
|
+
# signed_id = User.first.signed_id expires_in: 15.minutes, purpose: :password_reset
|
|
44
|
+
#
|
|
45
|
+
# User.find_signed signed_id # => nil, since the purpose does not match
|
|
46
|
+
#
|
|
47
|
+
# travel 16.minutes
|
|
48
|
+
# User.find_signed signed_id, purpose: :password_reset # => nil, since the signed id has expired
|
|
49
|
+
#
|
|
50
|
+
# travel_back
|
|
51
|
+
# User.find_signed signed_id, purpose: :password_reset # => User.first
|
|
52
|
+
def find_signed(signed_id, purpose: nil)
|
|
53
|
+
raise UnknownPrimaryKey.new(self) if primary_key.nil?
|
|
54
|
+
|
|
55
|
+
if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose))
|
|
56
|
+
find_by primary_key => id
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Works like find_signed, but will raise an ActiveSupport::MessageVerifier::InvalidSignature
|
|
61
|
+
# exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
|
|
62
|
+
# or has been tampered with. It will also raise an ActiveRecord::RecordNotFound exception if
|
|
63
|
+
# the valid signed id can't find a record.
|
|
64
|
+
#
|
|
65
|
+
# ==== Examples
|
|
66
|
+
#
|
|
67
|
+
# User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
|
|
68
|
+
#
|
|
69
|
+
# signed_id = User.first.signed_id
|
|
70
|
+
# User.first.destroy
|
|
71
|
+
# User.find_signed! signed_id # => ActiveRecord::RecordNotFound
|
|
72
|
+
def find_signed!(signed_id, purpose: nil)
|
|
73
|
+
if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose))
|
|
74
|
+
find(id)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
|
|
79
|
+
# with the class-level +signed_id_verifier_secret+, which within Rails comes from
|
|
80
|
+
# {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator].
|
|
81
|
+
# By default, it's SHA256 for the digest and JSON for the serialization.
|
|
82
|
+
def signed_id_verifier
|
|
83
|
+
@signed_id_verifier ||= begin
|
|
84
|
+
secret = signed_id_verifier_secret
|
|
85
|
+
secret = secret.call if secret.respond_to?(:call)
|
|
86
|
+
|
|
87
|
+
if secret.nil?
|
|
88
|
+
raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
|
|
89
|
+
else
|
|
90
|
+
ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
|
|
96
|
+
# verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
|
|
97
|
+
# your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
|
|
98
|
+
def signed_id_verifier=(verifier)
|
|
99
|
+
@signed_id_verifier = verifier
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# :nodoc:
|
|
103
|
+
def combine_signed_id_purposes(purpose)
|
|
104
|
+
[ base_class.name.underscore, purpose.to_s ].compact_blank.join("/")
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
|
|
110
|
+
#
|
|
111
|
+
# This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
|
|
112
|
+
# However, as with any message signed with a +ActiveSupport::MessageVerifier+,
|
|
113
|
+
# {the signed id is not encrypted}[link:classes/ActiveSupport/MessageVerifier.html#class-ActiveSupport::MessageVerifier-label-Signing+is+not+encryption].
|
|
114
|
+
# It's just encoded and protected against tampering.
|
|
115
|
+
#
|
|
116
|
+
# This means that the ID can be decoded by anyone; however, if tampered with (so to point to a different ID),
|
|
117
|
+
# the cryptographic signature will no longer match, and the signed id will be considered invalid and return nil
|
|
118
|
+
# when passed to +find_signed+ (or raise with +find_signed!+).
|
|
119
|
+
#
|
|
120
|
+
# It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
|
|
121
|
+
# If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
|
|
122
|
+
# record. If a purpose is set, this too must match.
|
|
123
|
+
#
|
|
124
|
+
# If you accidentally let a signed id out in the wild that you wish to retract sooner than its expiration date
|
|
125
|
+
# (or maybe you forgot to set an expiration date while meaning to!), you can use the purpose to essentially
|
|
126
|
+
# version the signed_id, like so:
|
|
127
|
+
#
|
|
128
|
+
# user.signed_id purpose: :v2
|
|
129
|
+
#
|
|
130
|
+
# And you then change your +find_signed+ calls to require this new purpose. Any old signed ids that were not
|
|
131
|
+
# created with the purpose will no longer find the record.
|
|
132
|
+
def signed_id(expires_in: nil, expires_at: nil, purpose: nil)
|
|
133
|
+
raise ArgumentError, "Cannot get a signed_id for a new record" if new_record?
|
|
134
|
+
|
|
135
|
+
self.class.signed_id_verifier.generate id, expires_in: expires_in, expires_at: expires_at, purpose: self.class.combine_signed_id_purposes(purpose)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -4,14 +4,14 @@ module ActiveRecord
|
|
|
4
4
|
# Statement cache is used to cache a single statement in order to avoid creating the AST again.
|
|
5
5
|
# Initializing the cache is done by passing the statement in the create block:
|
|
6
6
|
#
|
|
7
|
-
# cache = StatementCache.create(
|
|
7
|
+
# cache = StatementCache.create(ClothingItem.lease_connection) do |params|
|
|
8
8
|
# Book.where(name: "my book").where("author_id > 3")
|
|
9
9
|
# end
|
|
10
10
|
#
|
|
11
11
|
# The cached statement is executed by using the
|
|
12
12
|
# {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method:
|
|
13
13
|
#
|
|
14
|
-
# cache.execute([],
|
|
14
|
+
# cache.execute([], ClothingItem.lease_connection)
|
|
15
15
|
#
|
|
16
16
|
# The relation returned by the block is cached, and for each
|
|
17
17
|
# {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute]
|
|
@@ -20,13 +20,13 @@ module ActiveRecord
|
|
|
20
20
|
# If you want to cache the statement without the values you can use the +bind+ method of the
|
|
21
21
|
# block parameter.
|
|
22
22
|
#
|
|
23
|
-
# cache = StatementCache.create(
|
|
23
|
+
# cache = StatementCache.create(ClothingItem.lease_connection) do |params|
|
|
24
24
|
# Book.where(name: params.bind)
|
|
25
25
|
# end
|
|
26
26
|
#
|
|
27
27
|
# And pass the bind values as the first argument of +execute+ call.
|
|
28
28
|
#
|
|
29
|
-
# cache.execute(["my book"],
|
|
29
|
+
# cache.execute(["my book"], ClothingItem.lease_connection)
|
|
30
30
|
class StatementCache # :nodoc:
|
|
31
31
|
class Substitute; end # :nodoc:
|
|
32
32
|
|
|
@@ -50,13 +50,20 @@ module ActiveRecord
|
|
|
50
50
|
|
|
51
51
|
def sql_for(binds, connection)
|
|
52
52
|
val = @values.dup
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
@indexes.each do |i|
|
|
54
|
+
value = binds.shift
|
|
55
|
+
if ActiveModel::Attribute === value
|
|
56
|
+
value = value.value_for_database
|
|
57
|
+
end
|
|
58
|
+
val[i] = connection.quote(value)
|
|
59
|
+
end
|
|
55
60
|
val.join
|
|
56
61
|
end
|
|
57
62
|
end
|
|
58
63
|
|
|
59
64
|
class PartialQueryCollector
|
|
65
|
+
attr_accessor :preparable, :retryable
|
|
66
|
+
|
|
60
67
|
def initialize
|
|
61
68
|
@parts = []
|
|
62
69
|
@binds = []
|
|
@@ -73,6 +80,15 @@ module ActiveRecord
|
|
|
73
80
|
self
|
|
74
81
|
end
|
|
75
82
|
|
|
83
|
+
def add_binds(binds, proc_for_binds = nil)
|
|
84
|
+
@binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds
|
|
85
|
+
binds.size.times do |i|
|
|
86
|
+
@parts << ", " unless i == 0
|
|
87
|
+
@parts << Substitute.new
|
|
88
|
+
end
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
76
92
|
def value
|
|
77
93
|
[@parts, @binds]
|
|
78
94
|
end
|
|
@@ -100,7 +116,7 @@ module ActiveRecord
|
|
|
100
116
|
@bound_attributes = bound_attributes
|
|
101
117
|
|
|
102
118
|
bound_attributes.each_with_index do |attr, i|
|
|
103
|
-
if Substitute === attr.value
|
|
119
|
+
if ActiveModel::Attribute === attr && Substitute === attr.value
|
|
104
120
|
@indexes << i
|
|
105
121
|
end
|
|
106
122
|
end
|
|
@@ -126,14 +142,14 @@ module ActiveRecord
|
|
|
126
142
|
@klass = klass
|
|
127
143
|
end
|
|
128
144
|
|
|
129
|
-
def execute(params, connection, &block)
|
|
145
|
+
def execute(params, connection, allow_retry: false, &block)
|
|
130
146
|
bind_values = bind_map.bind params
|
|
131
147
|
|
|
132
148
|
sql = query_builder.sql_for bind_values, connection
|
|
133
149
|
|
|
134
|
-
klass.find_by_sql(sql, bind_values, preparable: true, &block)
|
|
150
|
+
klass.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
|
|
135
151
|
rescue ::RangeError
|
|
136
|
-
|
|
152
|
+
[]
|
|
137
153
|
end
|
|
138
154
|
|
|
139
155
|
def self.unsupported_value?(value)
|