aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.codeclimate.yml2
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml42
-rw-r--r--Gemfile28
-rw-r--r--Gemfile.lock120
-rw-r--r--actioncable/CHANGELOG.md8
-rw-r--r--actioncable/README.md6
-rw-r--r--actioncable/lib/action_cable.rb2
-rw-r--r--actioncable/lib/action_cable/connection/subscriptions.rb2
-rw-r--r--actioncable/lib/action_cable/engine.rb2
-rw-r--r--actioncable/lib/action_cable/server/configuration.rb19
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/async.rb2
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/evented_redis.rb89
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/redis.rb4
-rw-r--r--actioncable/test/subscription_adapter/evented_redis_test.rb61
-rw-r--r--actionmailer/CHANGELOG.md8
-rw-r--r--actionmailer/lib/action_mailer.rb2
-rw-r--r--actionmailer/lib/action_mailer/base.rb6
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb42
-rw-r--r--actionmailer/test/test_helper_test.rb42
-rw-r--r--actionpack/CHANGELOG.md64
-rw-r--r--actionpack/lib/abstract_controller/base.rb4
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb4
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb25
-rw-r--r--actionpack/lib/action_controller.rb4
-rw-r--r--actionpack/lib/action_controller/api.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb4
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb3
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb8
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb23
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb2
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb6
-rw-r--r--actionpack/lib/action_controller/railtie.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb20
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb31
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb5
-rw-r--r--actionpack/lib/action_dispatch/journey.rb10
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/builder.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/gtg/transition_table.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/builder.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/transition_table.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nodes/node.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.y2
-rw-r--r--actionpack/lib/action_dispatch/journey/parser_extras.rb4
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb216
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb36
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb69
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb10
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb14
-rw-r--r--actionpack/lib/action_dispatch/system_testing/driver.rb18
-rw-r--r--actionpack/lib/action_dispatch/system_testing/server.rb14
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb26
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb4
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb2
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/test/abstract_unit.rb4
-rw-r--r--actionpack/test/controller/flash_test.rb4
-rw-r--r--actionpack/test/controller/new_base/base_test.rb3
-rw-r--r--actionpack/test/controller/redirect_test.rb21
-rw-r--r--actionpack/test/controller/render_test.rb33
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb4
-rw-r--r--actionpack/test/controller/rescue_test.rb2
-rw-r--r--actionpack/test/dispatch/cookies_test.rb275
-rw-r--r--actionpack/test/dispatch/request_test.rb15
-rw-r--r--actionpack/test/dispatch/routing_test.rb4
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb4
-rw-r--r--actionpack/test/dispatch/system_testing/driver_test.rb8
-rw-r--r--actionpack/test/dispatch/system_testing/server_test.rb4
-rw-r--r--actionpack/test/dispatch/system_testing/system_test_case_test.rb21
-rw-r--r--actionview/CHANGELOG.md4
-rw-r--r--actionview/Rakefile4
-rw-r--r--actionview/app/assets/javascripts/README.md41
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/features/remote.coffee2
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee9
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee7
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/event.coffee18
-rw-r--r--actionview/lib/action_view.rb2
-rw-r--r--actionview/lib/action_view/base.rb10
-rw-r--r--actionview/lib/action_view/context.rb2
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb2
-rw-r--r--actionview/lib/action_view/digestor.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb34
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb14
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb12
-rw-r--r--actionview/lib/action_view/helpers/tags/check_box.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/checkable.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_select.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/color_field.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/date_select.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/datetime_field.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/grouped_collection_select.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/radio_button.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/text_area.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/text_field.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/time_zone_select.rb2
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb2
-rw-r--r--actionview/lib/action_view/layouts.rb2
-rw-r--r--actionview/lib/action_view/lookup_context.rb2
-rw-r--r--actionview/lib/action_view/record_identifier.rb2
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb2
-rw-r--r--actionview/lib/action_view/rendering.rb8
-rw-r--r--actionview/lib/action_view/template.rb2
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb3
-rw-r--r--actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb11
-rw-r--r--actionview/lib/action_view/template/handlers/erb/erubis.rb83
-rw-r--r--actionview/lib/action_view/template/resolver.rb2
-rw-r--r--actionview/lib/action_view/test_case.rb4
-rw-r--r--actionview/lib/action_view/testing/resolvers.rb2
-rw-r--r--actionview/test/abstract_unit.rb53
-rw-r--r--actionview/test/activerecord/render_partial_with_record_identification_test.rb10
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb31
-rw-r--r--actionview/test/template/digestor_test.rb11
-rw-r--r--actionview/test/template/erb/deprecated_erubis_implementation_test.rb15
-rw-r--r--actionview/test/template/javascript_helper_test.rb4
-rw-r--r--actionview/test/template/sanitize_helper_test.rb4
-rw-r--r--actionview/test/ujs/public/test/call-remote-callbacks.js92
-rw-r--r--activejob/CHANGELOG.md7
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/base.rb20
-rw-r--r--activejob/lib/active_job/enqueuing.rb2
-rw-r--r--activejob/lib/active_job/exceptions.rb2
-rw-r--r--activejob/lib/active_job/execution.rb2
-rw-r--r--activejob/test/cases/exceptions_test.rb2
-rw-r--r--activejob/test/jobs/retry_job.rb2
-rw-r--r--activejob/test/support/integration/adapters/resque.rb11
-rw-r--r--activemodel/lib/active_model.rb2
-rw-r--r--activemodel/lib/active_model/attributes.rb89
-rw-r--r--activemodel/lib/active_model/type.rb32
-rw-r--r--activemodel/lib/active_model/type/big_integer.rb2
-rw-r--r--activemodel/lib/active_model/type/helpers.rb8
-rw-r--r--activemodel/lib/active_model/type/string.rb2
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb2
-rw-r--r--activemodel/test/cases/attributes_dirty_test.rb193
-rw-r--r--activemodel/test/cases/attributes_test.rb94
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb66
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb40
-rw-r--r--activerecord/CHANGELOG.md131
-rw-r--r--activerecord/lib/active_record.rb4
-rw-r--r--activerecord/lib/active_record/associations.rb17
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb33
-rw-r--r--activerecord/lib/active_record/associations/association.rb4
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb67
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb5
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb14
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb19
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_base.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb6
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb54
-rw-r--r--activerecord/lib/active_record/associations/preloader/belongs_to.rb7
-rw-r--r--activerecord/lib/active_record/associations/preloader/collection_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_many.rb7
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_one.rb7
-rw-r--r--activerecord/lib/active_record/associations/preloader/singular_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb46
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb34
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb5
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb5
-rw-r--r--activerecord/lib/active_record/attribute_set.rb4
-rw-r--r--activerecord/lib/active_record/attribute_set/builder.rb2
-rw-r--r--activerecord/lib/active_record/attributes.rb2
-rw-r--r--activerecord/lib/active_record/base.rb16
-rw-r--r--activerecord/lib/active_record/callbacks.rb8
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb35
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb42
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb49
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb16
-rw-r--r--activerecord/lib/active_record/core.rb20
-rw-r--r--activerecord/lib/active_record/explain.rb2
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/fixtures.rb4
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb4
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb9
-rw-r--r--activerecord/lib/active_record/migration.rb9
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb4
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb67
-rw-r--r--activerecord/lib/active_record/model_schema.rb32
-rw-r--r--activerecord/lib/active_record/persistence.rb96
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb10
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb2
-rw-r--r--activerecord/lib/active_record/railties/databases.rake10
-rw-r--r--activerecord/lib/active_record/reflection.rb123
-rw-r--r--activerecord/lib/active_record/relation.rb109
-rw-r--r--activerecord/lib/active_record/relation/batches.rb17
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb9
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb3
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb39
-rw-r--r--activerecord/lib/active_record/relation/merger.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb15
-rw-r--r--activerecord/lib/active_record/relation/query_attribute.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb66
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb7
-rw-r--r--activerecord/lib/active_record/schema_migration.rb4
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb21
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/transactions.rb2
-rw-r--r--activerecord/lib/active_record/type.rb24
-rw-r--r--activerecord/lib/active_record/type_caster.rb4
-rw-r--r--activerecord/lib/active_record/validations.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql2/auto_increment_test.rb34
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb110
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/extension_migration_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb590
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb51
-rw-r--r--activerecord/test/cases/adapters/postgresql/serial_test.rb68
-rw-r--r--activerecord/test/cases/adapters/postgresql/type_lookup_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb203
-rw-r--r--activerecord/test/cases/adapters/sqlite3/json_test.rb29
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb8
-rw-r--r--activerecord/test/cases/associations/eager_test.rb26
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb13
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb3
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb18
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb13
-rw-r--r--activerecord/test/cases/attributes_test.rb2
-rw-r--r--activerecord/test/cases/base_test.rb19
-rw-r--r--activerecord/test/cases/batches_test.rb31
-rw-r--r--activerecord/test/cases/calculations_test.rb7
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb18
-rw-r--r--activerecord/test/cases/comment_test.rb22
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb34
-rw-r--r--activerecord/test/cases/instrumentation_test.rb72
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb2
-rw-r--r--activerecord/test/cases/json_shared_test_cases.rb2
-rw-r--r--activerecord/test/cases/locking_test.rb22
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb5
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb117
-rw-r--r--activerecord/test/cases/migration/index_test.rb6
-rw-r--r--activerecord/test/cases/migration_test.rb13
-rw-r--r--activerecord/test/cases/persistence_test.rb20
-rw-r--r--activerecord/test/cases/primary_keys_test.rb16
-rw-r--r--activerecord/test/cases/query_cache_test.rb10
-rw-r--r--activerecord/test/cases/quoting_test.rb37
-rw-r--r--activerecord/test/cases/reflection_test.rb51
-rw-r--r--activerecord/test/cases/relation/or_test.rb2
-rw-r--r--activerecord/test/cases/relation_test.rb5
-rw-r--r--activerecord/test/cases/relations_test.rb55
-rw-r--r--activerecord/test/cases/sanitize_test.rb12
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb48
-rw-r--r--activerecord/test/cases/tasks/postgresql_rake_test.rb2
-rw-r--r--activerecord/test/models/author.rb6
-rw-r--r--activerecord/test/models/developer.rb11
-rw-r--r--activerecord/test/models/drink_designer.rb3
-rw-r--r--activerecord/test/models/mocktail_designer.rb4
-rw-r--r--activerecord/test/models/post.rb2
-rw-r--r--activestorage/README.md8
-rw-r--r--activestorage/Rakefile2
-rw-r--r--activestorage/app/assets/javascripts/activestorage.js2
-rw-r--r--activestorage/app/controllers/active_storage/blobs_controller.rb15
-rw-r--r--activestorage/app/controllers/active_storage/disk_controller.rb6
-rw-r--r--activestorage/app/controllers/active_storage/previews_controller.rb12
-rw-r--r--activestorage/app/controllers/active_storage/variants_controller.rb19
-rw-r--r--activestorage/app/javascript/activestorage/blob_upload.js1
-rw-r--r--activestorage/app/javascript/activestorage/ujs.js1
-rw-r--r--activestorage/app/jobs/active_storage/analyze_job.rb8
-rw-r--r--activestorage/app/jobs/active_storage/purge_job.rb2
-rw-r--r--activestorage/app/models/active_storage/attachment.rb7
-rw-r--r--activestorage/app/models/active_storage/blob.rb134
-rw-r--r--activestorage/app/models/active_storage/filename.rb20
-rw-r--r--activestorage/app/models/active_storage/preview.rb90
-rw-r--r--activestorage/app/models/active_storage/variant.rb12
-rw-r--r--activestorage/app/models/active_storage/variation.rb18
-rw-r--r--activestorage/config/routes.rb23
-rw-r--r--activestorage/lib/active_storage.rb7
-rw-r--r--activestorage/lib/active_storage/analyzer.rb33
-rw-r--r--activestorage/lib/active_storage/analyzer/image_analyzer.rb36
-rw-r--r--activestorage/lib/active_storage/analyzer/null_analyzer.rb13
-rw-r--r--activestorage/lib/active_storage/analyzer/video_analyzer.rb79
-rw-r--r--activestorage/lib/active_storage/attached/macros.rb6
-rw-r--r--activestorage/lib/active_storage/attached/many.rb2
-rw-r--r--activestorage/lib/active_storage/attached/one.rb8
-rw-r--r--activestorage/lib/active_storage/downloading.rb21
-rw-r--r--activestorage/lib/active_storage/engine.rb28
-rw-r--r--activestorage/lib/active_storage/log_subscriber.rb2
-rw-r--r--activestorage/lib/active_storage/previewer.rb56
-rw-r--r--activestorage/lib/active_storage/previewer/pdf_previewer.rb17
-rw-r--r--activestorage/lib/active_storage/previewer/video_previewer.rb23
-rw-r--r--activestorage/lib/active_storage/service.rb7
-rw-r--r--activestorage/lib/active_storage/service/azure_storage_service.rb2
-rw-r--r--activestorage/lib/active_storage/service/disk_service.rb5
-rw-r--r--activestorage/lib/active_storage/service/gcs_service.rb21
-rw-r--r--activestorage/lib/active_storage/service/s3_service.rb8
-rw-r--r--activestorage/package.json2
-rw-r--r--activestorage/test/analyzer/image_analyzer_test.rb16
-rw-r--r--activestorage/test/analyzer/video_analyzer_test.rb35
-rw-r--r--activestorage/test/controllers/blobs_controller_test.rb2
-rw-r--r--activestorage/test/controllers/previews_controller_test.rb24
-rw-r--r--activestorage/test/controllers/variants_controller_test.rb4
-rw-r--r--activestorage/test/database/setup.rb2
-rwxr-xr-xactivestorage/test/dummy/bin/bundle2
-rw-r--r--activestorage/test/fixtures/files/report.pdfbin0 -> 13469 bytes
-rw-r--r--activestorage/test/fixtures/files/rotated_video.mp4bin0 -> 275090 bytes
-rw-r--r--activestorage/test/fixtures/files/video.mp4bin0 -> 275433 bytes
-rw-r--r--activestorage/test/fixtures/files/video_without_video_stream.mp4bin0 -> 16252 bytes
-rw-r--r--activestorage/test/models/attachments_test.rb72
-rw-r--r--activestorage/test/models/preview_test.rb40
-rw-r--r--activestorage/test/models/representation_test.rb41
-rw-r--r--activestorage/test/models/variant_test.rb11
-rw-r--r--activestorage/test/previewer/pdf_previewer_test.rb23
-rw-r--r--activestorage/test/previewer/video_previewer_test.rb23
-rw-r--r--activestorage/test/service/azure_storage_service_test.rb4
-rw-r--r--activestorage/test/service/configurations.example.yml (renamed from activestorage/test/service/configurations.yml)0
-rw-r--r--activestorage/test/service/disk_service_test.rb2
-rw-r--r--activestorage/test/service/gcs_service_test.rb4
-rw-r--r--activestorage/test/service/mirror_service_test.rb6
-rw-r--r--activestorage/test/service/s3_service_test.rb2
-rw-r--r--activestorage/test/template/image_tag_test.rb8
-rw-r--r--activestorage/test/test_helper.rb19
-rw-r--r--activesupport/CHANGELOG.md78
-rw-r--r--activesupport/lib/active_support.rb22
-rw-r--r--activesupport/lib/active_support/all.rb4
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb4
-rw-r--r--activesupport/lib/active_support/cache.rb14
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb11
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb4
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb6
-rw-r--r--activesupport/lib/active_support/callbacks.rb71
-rw-r--r--activesupport/lib/active_support/configurable.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/array.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/array/inquiry.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/date.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/date/acts_like.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/date/zones.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/acts_like.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/file.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/integer.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/integer/inflections.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/integer/time.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/kernel.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/concern.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/reachable.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb30
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/object/acts_like.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/conversions.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/range/conversions.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/inquiry.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/string/zones.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/time/acts_like.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb14
-rw-r--r--activesupport/lib/active_support/dependencies.rb22
-rw-r--r--activesupport/lib/active_support/dependencies/autoload.rb2
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb2
-rw-r--r--activesupport/lib/active_support/deprecation.rb14
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/constant_accessor.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/instance_delegator.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb4
-rw-r--r--activesupport/lib/active_support/duration.rb10
-rw-r--r--activesupport/lib/active_support/duration/iso8601_parser.rb2
-rw-r--r--activesupport/lib/active_support/duration/iso8601_serializer.rb4
-rw-r--r--activesupport/lib/active_support/encrypted_configuration.rb42
-rw-r--r--activesupport/lib/active_support/encrypted_file.rb101
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb2
-rw-r--r--activesupport/lib/active_support/executor.rb2
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb19
-rw-r--r--activesupport/lib/active_support/i18n.rb8
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb4
-rw-r--r--activesupport/lib/active_support/inflections.rb2
-rw-r--r--activesupport/lib/active_support/inflector.rb10
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb6
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb4
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb4
-rw-r--r--activesupport/lib/active_support/json.rb4
-rw-r--r--activesupport/lib/active_support/json/decoding.rb4
-rw-r--r--activesupport/lib/active_support/json/encoding.rb4
-rw-r--r--activesupport/lib/active_support/key_generator.rb2
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb6
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb6
-rw-r--r--activesupport/lib/active_support/logger.rb4
-rw-r--r--activesupport/lib/active_support/logger_silence.rb4
-rw-r--r--activesupport/lib/active_support/logger_thread_safe_level.rb2
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb34
-rw-r--r--activesupport/lib/active_support/message_verifier.rb39
-rw-r--r--activesupport/lib/active_support/messages/rotation_configuration.rb22
-rw-r--r--activesupport/lib/active_support/messages/rotator.rb56
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb10
-rw-r--r--activesupport/lib/active_support/notifications.rb6
-rw-r--r--activesupport/lib/active_support/number_helper/number_converter.rb10
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb12
-rw-r--r--activesupport/lib/active_support/option_merger.rb2
-rw-r--r--activesupport/lib/active_support/ordered_options.rb6
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb2
-rw-r--r--activesupport/lib/active_support/rails.rb14
-rw-r--r--activesupport/lib/active_support/railtie.rb17
-rw-r--r--activesupport/lib/active_support/reloader.rb2
-rw-r--r--activesupport/lib/active_support/rescuable.rb6
-rw-r--r--activesupport/lib/active_support/security_utils.rb2
-rw-r--r--activesupport/lib/active_support/subscriber.rb4
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb6
-rw-r--r--activesupport/lib/active_support/test_case.rb20
-rw-r--r--activesupport/lib/active_support/testing/constant_lookup.rb4
-rw-r--r--activesupport/lib/active_support/testing/deprecation.rb4
-rw-r--r--activesupport/lib/active_support/testing/setup_and_teardown.rb4
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb4
-rw-r--r--activesupport/lib/active_support/time.rb14
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb8
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini.rb8
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/libxmlsax.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogirisax.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb4
-rw-r--r--activesupport/test/cache/stores/file_store_test.rb1
-rw-r--r--activesupport/test/callbacks_test.rb22
-rw-r--r--activesupport/test/core_ext/module/reachable_test.rb28
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb5
-rw-r--r--activesupport/test/encrypted_configuration_test.rb57
-rw-r--r--activesupport/test/encrypted_file_test.rb50
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb30
-rw-r--r--activesupport/test/json/encoding_test.rb4
-rw-r--r--activesupport/test/message_encryptor_test.rb70
-rw-r--r--activesupport/test/message_verifier_test.rb46
-rw-r--r--activesupport/test/messages/rotation_configuration_test.rb25
-rw-r--r--activesupport/test/ordered_options_test.rb13
-rw-r--r--ci/phantomjs.js149
-rw-r--r--ci/qunit-selenium-runner.rb13
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb4
-rw-r--r--guides/bug_report_templates/action_controller_master.rb4
-rw-r--r--guides/bug_report_templates/active_job_gem.rb3
-rw-r--r--guides/bug_report_templates/active_job_master.rb3
-rw-r--r--guides/bug_report_templates/active_record_gem.rb3
-rw-r--r--guides/bug_report_templates/active_record_master.rb3
-rw-r--r--guides/bug_report_templates/active_record_migrations_gem.rb5
-rw-r--r--guides/bug_report_templates/active_record_migrations_master.rb5
-rw-r--r--guides/bug_report_templates/benchmark.rb3
-rw-r--r--guides/bug_report_templates/generic_gem.rb3
-rw-r--r--guides/bug_report_templates/generic_master.rb3
-rw-r--r--guides/source/2_3_release_notes.md4
-rw-r--r--guides/source/3_2_release_notes.md2
-rw-r--r--guides/source/4_0_release_notes.md2
-rw-r--r--guides/source/4_2_release_notes.md2
-rw-r--r--guides/source/5_0_release_notes.md2
-rw-r--r--guides/source/5_1_release_notes.md2
-rw-r--r--guides/source/_welcome.html.erb1
-rw-r--r--guides/source/action_cable_overview.md5
-rw-r--r--guides/source/action_controller_overview.md38
-rw-r--r--guides/source/action_mailer_basics.md84
-rw-r--r--guides/source/action_view_overview.md4
-rw-r--r--guides/source/active_job_basics.md20
-rw-r--r--guides/source/active_model_basics.md2
-rw-r--r--guides/source/active_record_basics.md11
-rw-r--r--guides/source/active_record_querying.md6
-rw-r--r--guides/source/active_support_core_extensions.md81
-rw-r--r--guides/source/active_support_instrumentation.md114
-rw-r--r--guides/source/api_app.md1
-rw-r--r--guides/source/asset_pipeline.md14
-rw-r--r--guides/source/association_basics.md8
-rw-r--r--guides/source/autoloading_and_reloading_constants.md8
-rw-r--r--guides/source/caching_with_rails.md4
-rw-r--r--guides/source/command_line.md2
-rw-r--r--guides/source/configuring.md17
-rw-r--r--guides/source/engines.md20
-rw-r--r--guides/source/form_helpers.md18
-rw-r--r--guides/source/getting_started.md56
-rw-r--r--guides/source/i18n.md4
-rw-r--r--guides/source/initialization.md2
-rw-r--r--guides/source/layouts_and_rendering.md40
-rw-r--r--guides/source/plugins.md8
-rw-r--r--guides/source/security.md155
-rw-r--r--guides/source/testing.md2
-rw-r--r--guides/source/upgrading_ruby_on_rails.md6
-rw-r--r--guides/source/working_with_javascript_in_rails.md4
-rw-r--r--railties/CHANGELOG.md14
-rw-r--r--railties/Rakefile62
-rw-r--r--railties/lib/rails.rb8
-rw-r--r--railties/lib/rails/api/task.rb2
-rw-r--r--railties/lib/rails/app_loader.rb4
-rw-r--r--railties/lib/rails/application.rb74
-rw-r--r--railties/lib/rails/application/bootstrap.rb2
-rw-r--r--railties/lib/rails/application/configuration.rb4
-rw-r--r--railties/lib/rails/cli.rb6
-rw-r--r--railties/lib/rails/code_statistics.rb2
-rw-r--r--railties/lib/rails/command/base.rb2
-rw-r--r--railties/lib/rails/commands.rb2
-rw-r--r--railties/lib/rails/commands/application/application_command.rb4
-rw-r--r--railties/lib/rails/commands/console/console_command.rb2
-rw-r--r--railties/lib/rails/commands/credentials/USAGE40
-rw-r--r--railties/lib/rails/commands/credentials/credentials_command.rb84
-rw-r--r--railties/lib/rails/commands/dbconsole/dbconsole_command.rb2
-rw-r--r--railties/lib/rails/commands/destroy/destroy_command.rb2
-rw-r--r--railties/lib/rails/commands/generate/generate_command.rb2
-rw-r--r--railties/lib/rails/commands/plugin/plugin_command.rb4
-rw-r--r--railties/lib/rails/commands/runner/runner_command.rb4
-rw-r--r--railties/lib/rails/commands/secrets/secrets_command.rb6
-rw-r--r--railties/lib/rails/commands/server/server_command.rb10
-rw-r--r--railties/lib/rails/commands/test/test_command.rb6
-rw-r--r--railties/lib/rails/configuration.rb4
-rw-r--r--railties/lib/rails/engine.rb10
-rw-r--r--railties/lib/rails/engine/commands.rb2
-rw-r--r--railties/lib/rails/engine/configuration.rb2
-rw-r--r--railties/lib/rails/engine/updater.rb4
-rw-r--r--railties/lib/rails/generators.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb6
-rw-r--r--railties/lib/rails/generators/css/assets/assets_generator.rb2
-rw-r--r--railties/lib/rails/generators/css/scaffold/scaffold_generator.rb8
-rw-r--r--railties/lib/rails/generators/erb.rb2
-rw-r--r--railties/lib/rails/generators/erb/controller/controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb4
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb8
-rw-r--r--railties/lib/rails/generators/js/assets/assets_generator.rb2
-rw-r--r--railties/lib/rails/generators/migration.rb2
-rw-r--r--railties/lib/rails/generators/model_helpers.rb2
-rw-r--r--railties/lib/rails/generators/named_base.rb39
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb35
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/yarn4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/storage.yml10
-rw-r--r--railties/lib/rails/generators/rails/credentials/credentials_generator.rb46
-rw-r--r--railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/master_key/master_key_generator.rb51
-rw-r--r--railties/lib/rails/generators/rails/model/model_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb5
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb1
-rw-r--r--railties/lib/rails/generators/rails/resource/USAGE2
-rw-r--r--railties/lib/rails/generators/rails/resource/resource_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb4
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb4
-rw-r--r--railties/lib/rails/generators/test_case.rb8
-rw-r--r--railties/lib/rails/generators/test_unit.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/controller/controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/generator/generator_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/helper/helper_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/integration/integration_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/job/job_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/preview.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/model/model_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/unit_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb4
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/system/system_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/system/templates/system_test.rb2
-rw-r--r--railties/lib/rails/generators/testing/behaviour.rb2
-rw-r--r--railties/lib/rails/info_controller.rb2
-rw-r--r--railties/lib/rails/mailers_controller.rb2
-rw-r--r--railties/lib/rails/plugin/test.rb4
-rw-r--r--railties/lib/rails/railtie.rb2
-rw-r--r--railties/lib/rails/railtie/configuration.rb2
-rw-r--r--railties/lib/rails/tasks/annotations.rake2
-rw-r--r--railties/lib/rails/tasks/dev.rake2
-rw-r--r--railties/lib/rails/tasks/engine.rake2
-rw-r--r--railties/lib/rails/tasks/framework.rake6
-rw-r--r--railties/lib/rails/tasks/statistics.rake2
-rw-r--r--railties/lib/rails/test_help.rb2
-rw-r--r--railties/lib/rails/test_unit/railtie.rb2
-rw-r--r--railties/lib/rails/test_unit/testing.rake2
-rw-r--r--railties/lib/rails/welcome_controller.rb2
-rw-r--r--railties/test/abstract_unit.rb1
-rw-r--r--railties/test/application/asset_debugging_test.rb13
-rw-r--r--railties/test/application/assets_test.rb5
-rw-r--r--railties/test/application/bin_setup_test.rb2
-rw-r--r--railties/test/application/configuration_test.rb101
-rw-r--r--railties/test/application/current_attributes_integration_test.rb2
-rw-r--r--railties/test/application/dbconsole_test.rb52
-rw-r--r--railties/test/application/generators_test.rb9
-rw-r--r--railties/test/application/help_test.rb4
-rw-r--r--railties/test/application/initializers/frameworks_test.rb14
-rw-r--r--railties/test/application/integration_test_case_test.rb8
-rw-r--r--railties/test/application/loading_test.rb4
-rw-r--r--railties/test/application/middleware/cookies_test.rb145
-rw-r--r--railties/test/application/middleware/sendfile_test.rb4
-rw-r--r--railties/test/application/middleware/session_test.rb68
-rw-r--r--railties/test/application/rake/dbs_test.rb208
-rw-r--r--railties/test/application/rake/dev_test.rb10
-rw-r--r--railties/test/application/rake/log_test.rb2
-rw-r--r--railties/test/application/rake/migrations_test.rb293
-rw-r--r--railties/test/application/rake/restart_test.rb6
-rw-r--r--railties/test/application/rake/tmp_test.rb6
-rw-r--r--railties/test/application/rake_test.rb195
-rw-r--r--railties/test/application/runner_test.rb43
-rw-r--r--railties/test/application/server_test.rb2
-rw-r--r--railties/test/application/test_runner_test.rb54
-rw-r--r--railties/test/application/test_test.rb16
-rw-r--r--railties/test/application/url_generation_test.rb1
-rw-r--r--railties/test/application/version_test.rb4
-rw-r--r--railties/test/commands/console_test.rb19
-rw-r--r--railties/test/commands/credentials_test.rb49
-rw-r--r--railties/test/commands/secrets_test.rb11
-rw-r--r--railties/test/commands/server_test.rb24
-rw-r--r--railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml1
-rw-r--r--railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb3
-rw-r--r--railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb3
-rw-r--r--railties/test/generators/api_app_generator_test.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb13
-rw-r--r--railties/test/generators/argv_scrubber_test.rb3
-rw-r--r--railties/test/generators/named_base_test.rb17
-rw-r--r--railties/test/generators/namespaced_generators_test.rb2
-rw-r--r--railties/test/generators/plugin_test_runner_test.rb6
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb23
-rw-r--r--railties/test/generators/shared_generator_tests.rb2
-rw-r--r--railties/test/isolation/abstract_unit.rb116
-rw-r--r--railties/test/path_generation_test.rb4
-rw-r--r--railties/test/rails_info_test.rb13
-rw-r--r--railties/test/secrets_test.rb34
-rw-r--r--tasks/release.rb1
715 files changed, 7692 insertions, 4723 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 63d3562e9b..1f4a1bb201 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,7 +1,7 @@
engines:
rubocop:
enabled: true
- channel: rubocop-0-49
+ channel: rubocop-0-50
ratings:
paths:
diff --git a/.gitignore b/.gitignore
index 32939b7bfd..da3b42cfbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ pkg
/activerecord/sqlnet.log
/activemodel/test/fixtures/fixture_database.sqlite3
/activesupport/test/fixtures/isolation_test
+/activestorage/test/service/configurations.yml
/railties/test/500.html
/railties/test/fixtures/tmp
/railties/test/initializer/root/log
diff --git a/.travis.yml b/.travis.yml
index 723c999fd5..851365acbd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -13,20 +13,28 @@ services:
- memcached
addons:
- postgresql: "9.4"
+ postgresql: "9.6"
+ chrome: stable
+ apt:
+ sources:
+ - sourceline: "ppa:mc3man/trusty-media"
+ - sourceline: "ppa:ubuntuhandbook1/apps"
+ packages:
+ - ffmpeg
+ - mupdf
+ - mupdf-tools
bundler_args: --without test --jobs 3 --retry 3
before_install:
- "rm ${BUNDLE_GEMFILE}.lock"
- - "gem update --system"
- - "gem update bundler"
+ - "travis_retry gem update --system"
+ - "travis_retry gem update bundler"
- "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
- "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
- "[[ -z $encrypted_8a915ebdd931_key && -z $encrypted_8a915ebdd931_iv ]] || openssl aes-256-cbc -K $encrypted_8a915ebdd931_key -iv $encrypted_8a915ebdd931_iv -in activestorage/test/service/configurations.yml.enc -out activestorage/test/service/configurations.yml -d"
- "[[ $GEM != 'av:ujs' ]] || nvm install node"
- "[[ $GEM != 'av:ujs' ]] || node --version"
- "[[ $GEM != 'av:ujs' ]] || (cd actionview && npm install)"
- - "[[ $GEM != 'av:ujs' ]] || [[ $(phantomjs --version) > '2' ]] || npm install -g phantomjs-prebuilt"
before_script:
# Set Sauce Labs username and access key. Obfuscated, purposefully not encrypted.
@@ -52,26 +60,26 @@ env:
- "GEM=ac:integration"
rvm:
- - 2.2.7
- - 2.3.4
- - 2.4.1
+ - 2.2.8
+ - 2.3.5
+ - 2.4.2
- ruby-head
matrix:
include:
- - rvm: 2.4.1
+ - rvm: 2.4.2
env: "GEM=av:ujs"
- - rvm: 2.2.7
+ - rvm: 2.2.8
env: "GEM=aj:integration"
services:
- memcached
- rabbitmq
- - rvm: 2.3.4
+ - rvm: 2.3.5
env: "GEM=aj:integration"
services:
- memcached
- rabbitmq
- - rvm: 2.4.1
+ - rvm: 2.4.2
env: "GEM=aj:integration"
services:
- memcached
@@ -81,30 +89,30 @@ matrix:
services:
- memcached
- rabbitmq
- - rvm: 2.3.4
+ - rvm: 2.3.5
env:
- "GEM=ar:mysql2 MYSQL=mariadb"
addons:
mariadb: 10.2
- - rvm: 2.3.4
+ - rvm: 2.3.5
env:
- "GEM=ar:sqlite3_mem"
- - rvm: 2.3.4
+ - rvm: 2.3.5
env:
- "GEM=ar:postgresql POSTGRES=9.2"
addons:
postgresql: "9.2"
- - rvm: jruby-9.1.12.0
+ - rvm: jruby-9.1.13.0
jdk: oraclejdk8
env:
- "GEM=ap"
- - rvm: jruby-9.1.12.0
+ - rvm: jruby-9.1.13.0
jdk: oraclejdk8
env:
- "GEM=am,amo,aj"
allow_failures:
- rvm: ruby-head
- - rvm: jruby-9.1.12.0
+ - rvm: jruby-9.1.13.0
- env: "GEM=ac:integration"
fast_finish: true
diff --git a/Gemfile b/Gemfile
index ece09d0731..2704e1e806 100644
--- a/Gemfile
+++ b/Gemfile
@@ -13,12 +13,11 @@ gem "rake", ">= 11.1"
# This needs to be with require false to ensure correct loading order, as it has to
# be loaded after loading the test library.
-gem "mocha", "~> 0.14", require: false
+gem "mocha", require: false
-gem "capybara", "~> 2.13"
+gem "capybara", "~> 2.15"
gem "rack-cache", "~> 1.2"
-gem "jquery-rails"
gem "coffee-rails"
gem "sass-rails", github: "rails/sass-rails", branch: "5-0-stable"
gem "turbolinks", "~> 5"
@@ -40,9 +39,6 @@ gem "rubocop", ">= 0.47", require: false
# https://github.com/guard/rb-inotify/pull/79
gem "rb-inotify", github: "matthewd/rb-inotify", branch: "close-handling", require: false
-# https://github.com/puma/puma/pull/1345
-gem "stopgap_13632", platforms: :mri if %w(2.2.7 2.3.4 2.4.1).include? RUBY_VERSION
-
group :doc do
gem "sdoc", github: "robin850/sdoc", branch: "upgrade"
gem "redcarpet", "~> 3.2.3", platforms: :ruby
@@ -55,16 +51,13 @@ gem "dalli", ">= 2.2.1"
gem "listen", ">= 3.0.5", "< 3.2", require: false
gem "libxml-ruby", platforms: :ruby
-# Action View. For testing Erubis handler deprecation.
-gem "erubis", "~> 2.7.0", require: false
-
# for railties app_generator_test
gem "bootsnap", ">= 1.1.0", require: false
# Active Job.
group :job do
gem "resque", require: false
- gem "resque-scheduler", require: false
+ gem "resque-scheduler", github: "jeremy/resque-scheduler", branch: "redis-rb-4.0", require: false
gem "sidekiq", require: false
gem "sucker_punch", require: false
gem "delayed_job", require: false
@@ -74,7 +67,7 @@ group :job do
gem "backburner", require: false
#TODO: add qu after it support Rails 5.1
# gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false
- gem "qu-redis", require: false
+ # gem "qu-redis", require: false
gem "delayed_job_active_record", require: false
gem "sequel", require: false
end
@@ -85,7 +78,10 @@ group :cable do
gem "em-hiredis", require: false
gem "hiredis", require: false
- gem "redis", require: false
+ gem "redis", "~> 4.0", require: false
+
+ # For Redis 4.0 support. Unreleased 9cb81bf.
+ gem "redis-namespace", github: "resque/redis-namespace"
gem "websocket-client-simple", github: "matthewd/websocket-client-simple", branch: "close-race", require: false
@@ -94,14 +90,20 @@ group :cable do
gem "sprockets-export", require: false
end
+# Active Storage
group :storage do
- gem "aws-sdk", "~> 2", require: false
+ gem "aws-sdk-s3", require: false
gem "google-cloud-storage", "~> 1.3", require: false
gem "azure-storage", require: false
gem "mini_magick"
end
+group :ujs do
+ gem "qunit-selenium"
+ gem "chromedriver-helper"
+end
+
# Add your own local bundler stuff.
local_gemfile = File.expand_path(".Gemfile", __dir__)
instance_eval File.read local_gemfile if File.exist? local_gemfile
diff --git a/Gemfile.lock b/Gemfile.lock
index 9da1aef756..3301694415 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -7,6 +7,17 @@ GIT
pg (>= 0.17, < 0.20)
GIT
+ remote: https://github.com/jeremy/resque-scheduler.git
+ revision: 7cccf7d8db4dbbf54bf549a98b6cbb38106dbed2
+ branch: redis-rb-4.0
+ specs:
+ resque-scheduler (4.3.0)
+ mono_logger (~> 1.0)
+ redis (>= 3.3, < 5)
+ resque (~> 1.26)
+ rufus-scheduler (~> 3.2)
+
+GIT
remote: https://github.com/matthewd/rb-inotify.git
revision: 856730aad4b285969e8dd621e44808a7c5af4242
branch: close-handling
@@ -42,6 +53,13 @@ GIT
tilt (>= 1.1, < 3)
GIT
+ remote: https://github.com/resque/redis-namespace.git
+ revision: 1403f511f6ae1ec9a8f330298a4cacf73fb10afc
+ specs:
+ redis-namespace (1.5.3)
+ redis (>= 3.0.4)
+
+GIT
remote: https://github.com/robin850/sdoc.git
revision: 0e340352f3ab2f196c8a8743f83c2ee286e4f71c
branch: upgrade
@@ -115,17 +133,35 @@ PATH
GEM
remote: https://rubygems.org/
specs:
+ activerecord-jdbc-adapter (1.3.24)
+ activerecord (>= 2.2, < 5.0)
+ activerecord-jdbcmysql-adapter (1.3.24)
+ activerecord-jdbc-adapter (~> 1.3.24)
+ jdbc-mysql (>= 5.1.22)
+ activerecord-jdbcpostgresql-adapter (1.3.24)
+ activerecord-jdbc-adapter (~> 1.3.24)
+ jdbc-postgres (~> 9.1, <= 9.4.1206)
+ activerecord-jdbcsqlite3-adapter (1.3.24)
+ activerecord-jdbc-adapter (~> 1.3.24)
+ jdbc-sqlite3 (>= 3.7.2, < 3.9)
addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
amq-protocol (2.2.0)
+ archive-zip (0.7.0)
+ io-like (~> 0.3.0)
ast (2.3.0)
- aws-sdk (2.10.27)
- aws-sdk-resources (= 2.10.27)
- aws-sdk-core (2.10.27)
+ aws-partitions (1.20.0)
+ aws-sdk-core (3.3.0)
+ aws-partitions (~> 1.0)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
- aws-sdk-resources (2.10.27)
- aws-sdk-core (= 2.10.27)
+ aws-sdk-kms (1.1.0)
+ aws-sdk-core (~> 3)
+ aws-sigv4 (~> 1.0)
+ aws-sdk-s3 (1.2.0)
+ aws-sdk-core (~> 3)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.0)
aws-sigv4 (1.0.1)
azure-core (0.1.11)
faraday (~> 0.9)
@@ -140,6 +176,7 @@ GEM
concurrent-ruby (~> 1.0.1)
dante (> 0.1.5)
bcrypt (3.1.11)
+ bcrypt (3.1.11-java)
bcrypt (3.1.11-x64-mingw32)
bcrypt (3.1.11-x86-mingw32)
beaneater (1.0.0)
@@ -163,6 +200,8 @@ GEM
selenium-webdriver
bootsnap (1.1.2)
msgpack (~> 1.0)
+ bootsnap (1.1.2-java)
+ msgpack (~> 1.0)
builder (3.2.3)
bunny (2.6.6)
amq-protocol (>= 2.1.0)
@@ -176,6 +215,9 @@ GEM
xpath (~> 2.0)
childprocess (0.7.1)
ffi (~> 1.0, >= 1.0.11)
+ chromedriver-helper (1.1.0)
+ archive-zip (~> 0.7.0)
+ nokogiri (~> 1.6)
coffee-rails (4.2.2)
coffee-script (>= 2.2.0)
railties (>= 4.0.0)
@@ -184,6 +226,7 @@ GEM
execjs
coffee-script-source (1.12.2)
concurrent-ruby (1.0.5)
+ concurrent-ruby (1.0.5-java)
connection_pool (2.2.1)
cookiejar (0.3.3)
curses (1.0.2)
@@ -210,11 +253,11 @@ GEM
em-socksify (0.3.1)
eventmachine (>= 1.0.0.beta.4)
erubi (1.6.1)
- erubis (2.7.0)
et-orbi (1.0.5)
tzinfo
event_emitter (0.2.6)
eventmachine (1.2.5)
+ eventmachine (1.2.5-java)
eventmachine (1.2.5-x64-mingw32)
eventmachine (1.2.5-x86-mingw32)
execjs (2.7.0)
@@ -234,6 +277,7 @@ GEM
eventmachine (>= 0.12.0)
websocket-driver (>= 0.5.1)
ffi (1.9.18)
+ ffi (1.9.18-java)
ffi (1.9.18-x64-mingw32)
ffi (1.9.18-x86-mingw32)
globalid (0.4.0)
@@ -263,15 +307,17 @@ GEM
os (~> 0.9)
signet (~> 0.7)
hiredis (0.6.1)
+ hiredis (0.6.1-java)
http_parser.rb (0.6.0)
httpclient (2.8.3)
i18n (0.8.6)
+ io-like (0.3.0)
+ jdbc-mysql (5.1.44)
+ jdbc-postgres (9.4.1206)
+ jdbc-sqlite3 (3.8.11.2)
jmespath (1.3.1)
- jquery-rails (4.3.1)
- rails-dom-testing (>= 1, < 3)
- railties (>= 4.2.0)
- thor (>= 0.14, < 2.0)
json (2.1.0)
+ json (2.1.0-java)
jwt (1.5.6)
kindlerb (1.2.0)
mustache
@@ -304,10 +350,11 @@ GEM
path_expander (~> 1.0)
minitest-server (1.0.4)
minitest (~> 5.0)
- mocha (0.14.0)
+ mocha (1.3.0)
metaclass (~> 0.0.1)
mono_logger (1.1.0)
msgpack (1.1.0)
+ msgpack (1.1.0-java)
msgpack (1.1.0-x64-mingw32)
msgpack (1.1.0-x86-mingw32)
multi_json (1.12.1)
@@ -320,6 +367,7 @@ GEM
nio4r (2.1.0)
nokogiri (1.8.0)
mini_portile2 (~> 2.2.0)
+ nokogiri (1.8.0-java)
nokogiri (1.8.0-x64-mingw32)
mini_portile2 (~> 2.2.0)
nokogiri (1.8.0-x86-mingw32)
@@ -336,13 +384,11 @@ GEM
psych (2.2.4)
public_suffix (2.0.5)
puma (3.9.1)
- qu (0.2.0)
- multi_json
- qu-redis (0.2.0)
- qu (= 0.2.0)
- redis-namespace
- simple_uuid
+ puma (3.9.1-java)
que (0.14.0)
+ qunit-selenium (0.0.4)
+ selenium-webdriver
+ thor
racc (1.4.14)
rack (2.0.3)
rack-cache (1.7.0)
@@ -362,9 +408,7 @@ GEM
rb-fsevent (0.10.2)
rdoc (5.1.0)
redcarpet (3.2.3)
- redis (3.3.3)
- redis-namespace (1.5.3)
- redis (~> 3.0, >= 3.0.4)
+ redis (4.0.1)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
@@ -375,20 +419,15 @@ GEM
redis-namespace (~> 1.3)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
- resque-scheduler (4.3.0)
- mono_logger (~> 1.0)
- redis (~> 3.3)
- resque (~> 1.26)
- rufus-scheduler (~> 3.2)
retriable (3.1.1)
- rubocop (0.49.1)
+ rubocop (0.50.0)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
- rainbow (>= 1.99.1, < 3.0)
+ rainbow (>= 2.2.2, < 3.0)
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
- ruby-progressbar (1.8.1)
+ ruby-progressbar (1.9.0)
ruby_dep (1.5.0)
rubyzip (1.2.1)
rufus-scheduler (3.4.2)
@@ -404,18 +443,17 @@ GEM
sequel (4.49.0)
serverengine (1.5.11)
sigdump (~> 0.2.2)
- sidekiq (5.0.4)
+ sidekiq (5.0.5)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
- redis (~> 3.3, >= 3.3.3)
+ redis (>= 3.3.4, < 5)
sigdump (0.2.4)
signet (0.7.3)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (~> 1.5)
multi_json (~> 1.10)
- simple_uuid (0.4.0)
sinatra (2.0.0)
mustermann (~> 1.0)
rack (~> 2.0)
@@ -438,7 +476,6 @@ GEM
sqlite3 (1.3.13-x64-mingw32)
sqlite3 (1.3.13-x86-mingw32)
stackprof (0.2.10)
- stopgap_13632 (1.0.1)
sucker_punch (2.0.2)
concurrent-ruby (~> 1.0.0)
thin (1.7.2)
@@ -448,6 +485,7 @@ GEM
thor (0.20.0)
thread (0.1.7)
thread_safe (0.3.6)
+ thread_safe (0.3.6-java)
tilt (2.0.8)
turbolinks (5.0.1)
turbolinks-source (~> 5)
@@ -475,6 +513,7 @@ GEM
nokogiri (~> 1.3)
PLATFORMS
+ java
ruby
x64-mingw32
x86-mingw32
@@ -484,7 +523,7 @@ DEPENDENCIES
activerecord-jdbcpostgresql-adapter (>= 1.3.0)
activerecord-jdbcsqlite3-adapter (>= 1.3.0)
arel!
- aws-sdk (~> 2)
+ aws-sdk-s3
azure-storage
backburner
bcrypt (~> 3.1.11)
@@ -493,40 +532,40 @@ DEPENDENCIES
blade-sauce_labs_plugin
bootsnap (>= 1.1.0)
byebug
- capybara (~> 2.13)
+ capybara (~> 2.15)
+ chromedriver-helper
coffee-rails
dalli (>= 2.2.1)
delayed_job
delayed_job_active_record
em-hiredis
- erubis (~> 2.7.0)
google-cloud-storage (~> 1.3)
hiredis
- jquery-rails
json (>= 2.0.0)
kindlerb (~> 1.2.0)
libxml-ruby
listen (>= 3.0.5, < 3.2)
mini_magick
minitest-bisect
- mocha (~> 0.14)
+ mocha
mysql2 (>= 0.4.4)
nokogiri (>= 1.6.8)
pg (>= 0.18.0)
psych (~> 2.0)
puma
- qu-redis
que
queue_classic!
+ qunit-selenium
racc (>= 1.4.6)
rack-cache (~> 1.2)
rails!
rake (>= 11.1)
rb-inotify!
redcarpet (~> 3.2.3)
- redis
+ redis (~> 4.0)
+ redis-namespace!
resque
- resque-scheduler
+ resque-scheduler!
rubocop (>= 0.47)
sass-rails!
sdoc!
@@ -536,7 +575,6 @@ DEPENDENCIES
sprockets-export
sqlite3 (~> 1.3.6)
stackprof
- stopgap_13632
sucker_punch
turbolinks (~> 5)
tzinfo-data
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index 560ee89846..4355c97df0 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,3 +1,11 @@
+* Removed deprecated evented redis adapter.
+
+ *Rafael Mendonça França*
+
+* Support redis-rb 4.0.
+
+ *Jeremy Daer*
+
* Hash long stream identifiers when using PostgreSQL adapter.
PostgreSQL has a limit on identifiers length (63 chars, [docs](https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS)).
diff --git a/actioncable/README.md b/actioncable/README.md
index a060e8938e..44fb81478d 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -446,7 +446,7 @@ The WebSocket server doesn't have access to the session, but it has access to th
## Dependencies
-Action Cable provides a subscription adapter interface to process its pubsub internals. By default, asynchronous, inline, PostgreSQL, evented Redis, and non-evented Redis adapters are included. The default adapter in new Rails applications is the asynchronous (`async`) adapter. To create your own adapter, you can look at `ActionCable::SubscriptionAdapter::Base` for all methods that must be implemented, and any of the adapters included within Action Cable as example implementations.
+Action Cable provides a subscription adapter interface to process its pubsub internals. By default, asynchronous, inline, PostgreSQL, and Redis adapters are included. The default adapter in new Rails applications is the asynchronous (`async`) adapter. To create your own adapter, you can look at `ActionCable::SubscriptionAdapter::Base` for all methods that must be implemented, and any of the adapters included within Action Cable as example implementations.
The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby), [nio4r](https://github.com/celluloid/nio4r), and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
@@ -454,9 +454,9 @@ The Ruby side of things is built on top of [websocket-driver](https://github.com
## Deployment
Action Cable is powered by a combination of WebSockets and threads. All of the
-connection management is handled internally by utilizing Ruby’s native thread
+connection management is handled internally by utilizing Ruby's native thread
support, which means you can use all your regular Rails models with no problems
-as long as you haven’t committed any thread-safety sins.
+as long as you haven't committed any thread-safety sins.
The Action Cable server does _not_ need to be a multi-threaded application server.
This is because Action Cable uses the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking)
diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb
index bd828b2d0f..001a043409 100644
--- a/actioncable/lib/action_cable.rb
+++ b/actioncable/lib/action_cable.rb
@@ -25,7 +25,7 @@
require "active_support"
require "active_support/rails"
-require_relative "action_cable/version"
+require "action_cable/version"
module ActionCable
extend ActiveSupport::Autoload
diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb
index faafd6d0a6..bb8d64e27a 100644
--- a/actioncable/lib/action_cable/connection/subscriptions.rb
+++ b/actioncable/lib/action_cable/connection/subscriptions.rb
@@ -43,7 +43,7 @@ module ActionCable
def remove(data)
logger.info "Unsubscribing from channel: #{data['identifier']}"
- remove_subscription subscriptions[data["identifier"]]
+ remove_subscription find(data)
end
def remove_subscription(subscription)
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
index c961f47342..53cbb597cd 100644
--- a/actioncable/lib/action_cable/engine.rb
+++ b/actioncable/lib/action_cable/engine.rb
@@ -2,7 +2,7 @@
require "rails"
require "action_cable"
-require_relative "helpers/action_cable_helper"
+require "action_cable/helpers/action_cable_helper"
require "active_support/core_ext/hash/indifferent_access"
module ActionCable
diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb
index 82fed81a18..26209537df 100644
--- a/actioncable/lib/action_cable/server/configuration.rb
+++ b/actioncable/lib/action_cable/server/configuration.rb
@@ -25,13 +25,26 @@ module ActionCable
# Also makes sure proper dependencies are required.
def pubsub_adapter
adapter = (cable.fetch("adapter") { "redis" })
+
+ # Require the adapter itself and give useful feedback about
+ # 1. Missing adapter gems and
+ # 2. Adapter gems' missing dependencies.
path_to_adapter = "action_cable/subscription_adapter/#{adapter}"
begin
require path_to_adapter
- rescue Gem::LoadError => e
- raise Gem::LoadError, "Specified '#{adapter}' for Action Cable pubsub adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by Action Cable)."
rescue LoadError => e
- raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/cable.yml is valid. If you use an adapter other than 'postgresql' or 'redis' add the necessary adapter gem to the Gemfile.", e.backtrace
+ # We couldn't require the adapter itself. Raise an exception that
+ # points out config typos and missing gems.
+ if e.path == path_to_adapter
+ # We can assume that a non-builtin adapter was specified, so it's
+ # either misspelled or missing from Gemfile.
+ raise e.class, "Could not load the '#{adapter}' Action Cable pubsub adapter. Ensure that the adapter is spelled correctly in config/cable.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
+
+ # Bubbled up from the adapter require. Prefix the exception message
+ # with some guidance about how to address it and reraise.
+ else
+ raise e.class, "Error loading the '#{adapter}' Action Cable pubsub adapter. Missing a gem it depends on? #{e.message}", e.backtrace
+ end
end
adapter = adapter.camelize
diff --git a/actioncable/lib/action_cable/subscription_adapter/async.rb b/actioncable/lib/action_cable/subscription_adapter/async.rb
index 96c18c4a2f..c9930299c7 100644
--- a/actioncable/lib/action_cable/subscription_adapter/async.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/async.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "inline"
+require "action_cable/subscription_adapter/inline"
module ActionCable
module SubscriptionAdapter
diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
deleted file mode 100644
index 07774810ce..0000000000
--- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-# frozen_string_literal: true
-
-require "thread"
-
-gem "em-hiredis", "~> 0.3.0"
-gem "redis", "~> 3.0"
-require "em-hiredis"
-require "redis"
-
-EventMachine.epoll if EventMachine.epoll?
-EventMachine.kqueue if EventMachine.kqueue?
-
-module ActionCable
- module SubscriptionAdapter
- class EventedRedis < Base # :nodoc:
- prepend ChannelPrefix
-
- @@mutex = Mutex.new
-
- # Overwrite this factory method for EventMachine Redis connections if you want to use a different Redis connection library than EM::Hiredis.
- # This is needed, for example, when using Makara proxies for distributed Redis.
- cattr_accessor :em_redis_connector, default: ->(config) { EM::Hiredis.connect(config[:url]) }
-
- # Overwrite this factory method for Redis connections if you want to use a different Redis connection library than Redis.
- # This is needed, for example, when using Makara proxies for distributed Redis.
- cattr_accessor :redis_connector, default: ->(config) { ::Redis.new(url: config[:url]) }
-
- def initialize(*)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The "evented_redis" subscription adapter is deprecated and
- will be removed in Rails 5.2. Please use the "redis" adapter
- instead.
- MSG
-
- super
- @redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil
- end
-
- def broadcast(channel, payload)
- redis_connection_for_broadcasts.publish(channel, payload)
- end
-
- def subscribe(channel, message_callback, success_callback = nil)
- redis_connection_for_subscriptions.pubsub.subscribe(channel, &message_callback).tap do |result|
- result.callback { |reply| success_callback.call } if success_callback
- end
- end
-
- def unsubscribe(channel, message_callback)
- redis_connection_for_subscriptions.pubsub.unsubscribe_proc(channel, message_callback)
- end
-
- def shutdown
- redis_connection_for_subscriptions.pubsub.close_connection
- @redis_connection_for_subscriptions = nil
- end
-
- private
- def redis_connection_for_subscriptions
- ensure_reactor_running
- @redis_connection_for_subscriptions || @server.mutex.synchronize do
- @redis_connection_for_subscriptions ||= self.class.em_redis_connector.call(@server.config.cable).tap do |redis|
- redis.on(:reconnect_failed) do
- @logger.error "[ActionCable] Redis reconnect failed."
- end
-
- redis.on(:failed) do
- @logger.error "[ActionCable] Redis connection has failed."
- end
- end
- end
- end
-
- def redis_connection_for_broadcasts
- @redis_connection_for_broadcasts || @server.mutex.synchronize do
- @redis_connection_for_broadcasts ||= self.class.redis_connector.call(@server.config.cable)
- end
- end
-
- def ensure_reactor_running
- return if EventMachine.reactor_running? && EventMachine.reactor_thread
- @@mutex.synchronize do
- Thread.new { EventMachine.run } unless EventMachine.reactor_running?
- Thread.pass until EventMachine.reactor_running? && EventMachine.reactor_thread
- end
- end
- end
- end
-end
diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb
index facea944ff..c28951608f 100644
--- a/actioncable/lib/action_cable/subscription_adapter/redis.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb
@@ -2,7 +2,7 @@
require "thread"
-gem "redis", "~> 3.0"
+gem "redis", ">= 3", "< 5"
require "redis"
module ActionCable
@@ -76,7 +76,7 @@ module ActionCable
def listen(conn)
conn.without_reconnect do
- original_client = conn.client
+ original_client = conn.respond_to?(:_client) ? conn._client : conn.client
conn.subscribe("_action_cable_internal") do |on|
on.subscribe do |chan, count|
diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb
deleted file mode 100644
index e3e0a0c72a..0000000000
--- a/actioncable/test/subscription_adapter/evented_redis_test.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-# frozen_string_literal: true
-
-require "test_helper"
-require_relative "common"
-require_relative "channel_prefix"
-
-class EventedRedisAdapterTest < ActionCable::TestCase
- include CommonSubscriptionAdapterTest
- include ChannelPrefixTest
-
- def setup
- assert_deprecated do
- super
- end
-
- # em-hiredis is warning-rich
- @previous_verbose, $VERBOSE = $VERBOSE, nil
- end
-
- def teardown
- super
-
- # Ensure EM is shut down before we re-enable warnings
- EventMachine.reactor_thread.tap do |thread|
- EventMachine.stop
- thread.join
- end
-
- $VERBOSE = @previous_verbose
- end
-
- def test_slow_eventmachine
- require "eventmachine"
- require "thread"
-
- lock = Mutex.new
-
- EventMachine.singleton_class.class_eval do
- alias_method :delayed_initialize_event_machine, :initialize_event_machine
- define_method(:initialize_event_machine) do
- lock.synchronize do
- sleep 0.5
- delayed_initialize_event_machine
- end
- end
- end
-
- test_basic_broadcast
- ensure
- lock.synchronize do
- EventMachine.singleton_class.class_eval do
- alias_method :initialize_event_machine, :delayed_initialize_event_machine
- remove_method :delayed_initialize_event_machine
- end
- end
- end
-
- def cable_config
- { adapter: "evented_redis", url: "redis://:password@127.0.0.1:6379/12" }
- end
-end
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 9993a11c9d..49afec9a12 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,11 @@
+* Add `assert_enqueued_email_with` test helper.
+
+ assert_enqueued_email_with ContactMailer, :welcome do
+ ContactMailer.welcome.deliver_later
+ end
+
+ *Mikkel Malmberg*
+
* Allow Action Mailer classes to configure their delivery job.
class MyMailer < ApplicationMailer
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index a170eb7917..ade3fe39a2 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -24,7 +24,7 @@
#++
require "abstract_controller"
-require_relative "action_mailer/version"
+require "action_mailer/version"
# Common Active Support usage in Action Mailer
require "active_support"
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 1c1e1a9a3b..eb8ae59533 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
require "mail"
-require_relative "collector"
+require "action_mailer/collector"
require "active_support/core_ext/string/inflections"
require "active_support/core_ext/hash/except"
require "active_support/core_ext/module/anonymous"
-require_relative "log_subscriber"
-require_relative "rescuable"
+require "action_mailer/log_subscriber"
+require "action_mailer/rescuable"
module ActionMailer
# Action Mailer allows you to send email from your application using a mailer model and views.
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index ac8b944743..8ee4d06915 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -93,6 +93,48 @@ module ActionMailer
assert_enqueued_jobs number, only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block
end
+ # Asserts that a specific email has been enqueued, optionally
+ # matching arguments.
+ #
+ # def test_email
+ # ContactMailer.welcome.deliver_later
+ # assert_enqueued_email_with ContactMailer, :welcome
+ # end
+ #
+ # def test_email_with_arguments
+ # ContactMailer.welcome("Hello", "Goodbye").deliver_later
+ # assert_enqueued_email_with ContactMailer, :welcome, args: ["Hello", "Goodbye"]
+ # end
+ #
+ # If a block is passed, that block should cause the specified email
+ # to be enqueued.
+ #
+ # def test_email_in_block
+ # assert_enqueued_email_with ContactMailer, :welcome do
+ # ContactMailer.welcome.deliver_later
+ # end
+ # end
+ #
+ # If `args` is provided as a Hash, a parameterized email is matched.
+ #
+ # def test_parameterized_email
+ # assert_enqueued_email_with ContactMailer, :welcome,
+ # args: {email: 'user@example.com} do
+ # ContactMailer.with(email: 'user@example.com').welcome.deliver_later
+ # end
+ # end
+ def assert_enqueued_email_with(mailer, method, args: nil, queue: "mailers", &block)
+ if args.is_a? Hash
+ job = ActionMailer::Parameterized::DeliveryJob
+ args = [mailer.to_s, method.to_s, "deliver_now", args]
+ else
+ job = ActionMailer::DeliveryJob
+ args = [mailer.to_s, method.to_s, "deliver_now", *args]
+ end
+
+ assert_enqueued_with(job: job, args: args, queue: queue, &block)
+ end
+
# Asserts that no emails are enqueued for later delivery.
#
# def test_no_emails
diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb
index abf50cf4da..5470d51599 100644
--- a/actionmailer/test/test_helper_test.rb
+++ b/actionmailer/test/test_helper_test.rb
@@ -10,6 +10,18 @@ class TestHelperMailer < ActionMailer::Base
to: "test@example.com",
from: "tester@example.com"
end
+
+ def test_args(recipient, name)
+ mail body: render(inline: "Hello, #{name}"),
+ to: recipient,
+ from: "tester@example.com"
+ end
+
+ def test_parameter_args
+ mail body: render(inline: "All is #{params[:all]}"),
+ to: "test@example.com",
+ from: "tester@example.com"
+ end
end
class TestHelperMailerTest < ActionMailer::TestCase
@@ -207,6 +219,36 @@ class TestHelperMailerTest < ActionMailer::TestCase
assert_match(/0 .* but 1/, error.message)
end
+
+ def test_assert_enqueued_email_with
+ assert_nothing_raised do
+ assert_enqueued_email_with TestHelperMailer, :test do
+ silence_stream($stdout) do
+ TestHelperMailer.test.deliver_later
+ end
+ end
+ end
+ end
+
+ def test_assert_enqueued_email_with_args
+ assert_nothing_raised do
+ assert_enqueued_email_with TestHelperMailer, :test_args, args: ["some_email", "some_name"] do
+ silence_stream($stdout) do
+ TestHelperMailer.test_args("some_email", "some_name").deliver_later
+ end
+ end
+ end
+ end
+
+ def test_assert_enqueued_email_with_parameterized_args
+ assert_nothing_raised do
+ assert_enqueued_email_with TestHelperMailer, :test_parameter_args, args: { all: "good" } do
+ silence_stream($stdout) do
+ TestHelperMailer.with(all: "good").test_parameter_args.deliver_later
+ end
+ end
+ end
+ end
end
class AnotherTestHelperMailerTest < ActionMailer::TestCase
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index b56a1c5d4b..32239d202c 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,63 @@
+* Remove deprecated `ActionController::ParamsParser::ParseError`.
+
+ *Rafael Mendonça França*
+
+* Add `:allow_other_host` option to `redirect_back` method.
+ When `allow_other_host` is set to `false`, the `redirect_back`
+ will not allow a redirecting from a different host.
+ `allow_other_host` is `true` by default.
+
+ *Tim Masliuchenko*
+
+* Add headless chrome support to System Tests.
+
+ *Yuji Yaginuma*
+
+* Add ability to enable Early Hints for HTTP/2
+
+ If supported by the server, and enabled in Puma this allows H2 Early Hints to be used.
+
+ The `javascript_include_tag` and the `stylesheet_link_tag` automatically add Early Hints if requested.
+
+ *Eileen M. Uchitelle*, *Aaron Patterson*
+
+* Simplify cookies middleware with key rotation support
+
+ Use the `rotate` method for both `MessageEncryptor` and
+ `MessageVerifier` to add key rotation support for encrypted and
+ signed cookies. This also helps simplify support for legacy cookie
+ security.
+
+ *Michael J Coyne*
+
+* Use Capybara registered `:puma` server config.
+
+ The Capybara registered `:puma` server ensures the puma server is run in process so
+ connection sharing and open request detection work correctly by default.
+
+ *Thomas Walpole*
+
+* Cookies `:expires` option supports `ActiveSupport::Duration` object.
+
+ cookies[:user_name] = { value: "assain", expires: 1.hour }
+ cookies[:key] = { value: "a yummy cookie", expires: 6.months }
+
+ Pull Request: #30121
+
+ *Assain Jaleel*
+
+* Enforce signed/encrypted cookie expiry server side.
+
+ Rails can thwart attacks by malicious clients that don't honor a cookie's expiry.
+
+ It does so by stashing the expiry within the written cookie and relying on the
+ signing/encrypting to vouch that it hasn't been tampered with. Then on a
+ server-side read, the expiry is verified and any expired cookie is discarded.
+
+ Pull Request: #30121
+
+ *Assain Jaleel*
+
* Make `take_failed_screenshot` work within engine.
Fixes #30405.
@@ -26,7 +86,7 @@
*Kir Shatrov*
-* `driven_by` now registers poltergeist and capybara-webkit
+* `driven_by` now registers poltergeist and capybara-webkit.
If poltergeist or capybara-webkit are set as drivers is set for System Tests,
`driven_by` will register the driver and set additional options passed via
@@ -36,7 +96,7 @@
*Mario Chavez*
-* AEAD encrypted cookies and sessions with GCM
+* AEAD encrypted cookies and sessions with GCM.
Encrypted cookies now use AES-GCM which couples authentication and
encryption in one faster step and produces shorter ciphertexts. Cookies
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 3761054bb7..a312af6715 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "error"
+require "abstract_controller/error"
require "active_support/configurable"
require "active_support/descendants_tracker"
require "active_support/core_ext/module/anonymous"
@@ -180,8 +180,6 @@ module AbstractController
#
# ==== Parameters
# * <tt>name</tt> - The name of an action to be tested
- #
- # :api: private
def action_method?(name)
self.class.action_methods.include?(name)
end
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 715d043b4e..146d17cf40 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -35,8 +35,8 @@ module AbstractController
skip_after_callbacks_if_terminated: true
end
- # Override AbstractController::Base's process_action to run the
- # process_action callbacks around the normal behavior.
+ # Override <tt>AbstractController::Base#process_action</tt> to run the
+ # <tt>process_action</tt> callbacks around the normal behavior.
def process_action(*args)
run_callbacks(:process_action) do
super
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 41898c4c2e..8ba2b25552 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "error"
+require "abstract_controller/error"
require "action_view"
require "action_view/view_paths"
require "set"
@@ -20,7 +20,6 @@ module AbstractController
# Normalizes arguments, options and then delegates render_to_body and
# sticks the result in <tt>self.response_body</tt>.
- # :api: public
def render(*args, &block)
options = _normalize_render(*args, &block)
rendered_body = render_to_body(options)
@@ -42,19 +41,16 @@ module AbstractController
# (as ActionController extends it to be anything that
# responds to the method each), this method needs to be
# overridden in order to still return a string.
- # :api: plugin
def render_to_string(*args, &block)
options = _normalize_render(*args, &block)
render_to_body(options)
end
# Performs the actual template rendering.
- # :api: public
def render_to_body(options = {})
end
- # Returns Content-Type of rendered content
- # :api: public
+ # Returns Content-Type of rendered content.
def rendered_format
Mime[:text]
end
@@ -65,7 +61,6 @@ module AbstractController
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
- # :api: public
def view_assigns
protected_vars = _protected_ivars
variables = instance_variables
@@ -76,11 +71,11 @@ module AbstractController
}
end
+ private
# Normalize args by converting <tt>render "foo"</tt> to
# <tt>render :action => "foo"</tt> and <tt>render "foo/bar"</tt> to
# <tt>render :file => "foo/bar"</tt>.
- # :api: plugin
- def _normalize_args(action = nil, options = {})
+ def _normalize_args(action = nil, options = {}) # :doc:
if action.respond_to?(:permitted?)
if action.permitted?
action
@@ -95,20 +90,17 @@ module AbstractController
end
# Normalize options.
- # :api: plugin
- def _normalize_options(options)
+ def _normalize_options(options) # :doc:
options
end
# Process extra options.
- # :api: plugin
- def _process_options(options)
+ def _process_options(options) # :doc:
options
end
# Process the rendered format.
- # :api: private
- def _process_format(format)
+ def _process_format(format) # :nodoc:
end
def _process_variant(options)
@@ -121,8 +113,7 @@ module AbstractController
end
# Normalize args and options.
- # :api: private
- def _normalize_render(*args, &block)
+ def _normalize_render(*args, &block) # :nodoc:
options = _normalize_args(*args, &block)
_process_variant(options)
_normalize_options(options)
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index e893507baa..bd19b8cd5d 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -3,8 +3,8 @@
require "active_support/rails"
require "abstract_controller"
require "action_dispatch"
-require_relative "action_controller/metal/live"
-require_relative "action_controller/metal/strong_parameters"
+require "action_controller/metal/live"
+require "action_controller/metal/strong_parameters"
module ActionController
extend ActiveSupport::Autoload
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index ba9af4767e..b192e496de 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -2,7 +2,7 @@
require "action_view"
require "action_controller"
-require_relative "log_subscriber"
+require "action_controller/log_subscriber"
module ActionController
# API Controller is a lightweight version of <tt>ActionController::Base</tt>,
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index bbc48e6eb7..b73269871b 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
require "action_view"
-require_relative "log_subscriber"
-require_relative "metal/params_wrapper"
+require "action_controller/log_subscriber"
+require "action_controller/metal/params_wrapper"
module ActionController
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 882f6f3d0a..5a82ccf668 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "exceptions"
+require "action_controller/metal/exceptions"
module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index f808295720..a65857d6ef 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -34,9 +34,6 @@ module ActionController
class NotImplemented < MethodNotAllowed #:nodoc:
end
- class UnknownController < ActionControllerError #:nodoc:
- end
-
class MissingFile < ActionControllerError #:nodoc:
end
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 476f0843b2..be9449629f 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -83,16 +83,13 @@ module ActionController
# def cleanup_view_runtime
# super - time_taken_in_something_expensive
# end
- #
- # :api: plugin
- def cleanup_view_runtime
+ def cleanup_view_runtime # :doc:
yield
end
# Every time after an action is processed, this method is invoked
# with the payload, so you can add more information.
- # :api: plugin
- def append_info_to_payload(payload)
+ def append_info_to_payload(payload) # :doc:
payload[:view_runtime] = view_runtime
end
@@ -100,7 +97,6 @@ module ActionController
# A hook which allows other frameworks to log what happened during
# controller process action. This method should return an array
# with the messages to be added.
- # :api: plugin
def log_process_action(payload) #:nodoc:
messages, view_runtime = [], payload[:view_runtime]
messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 5cd8568d8d..8de57f9199 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -79,15 +79,18 @@ module ActionController
# redirect_back fallback_location: "/images/screenshot.jpg"
# redirect_back fallback_location: posts_url
# redirect_back fallback_location: proc { edit_post_url(@post) }
+ # redirect_back fallback_location: '/', allow_other_host: false
#
- # All options that can be passed to <tt>redirect_to</tt> are accepted as
+ # ==== Options
+ # * <tt>:fallback_location</tt> - The default fallback location that will be used on missing `Referer` header.
+ # * <tt>:allow_other_host</tt> - Allows or disallow redirection to the host that is different to the current host
+ #
+ # All other options that can be passed to <tt>redirect_to</tt> are accepted as
# options and the behavior is identical.
- def redirect_back(fallback_location:, **args)
- if referer = request.headers["Referer"]
- redirect_to referer, **args
- else
- redirect_to fallback_location, **args
- end
+ def redirect_back(fallback_location:, allow_other_host: true, **args)
+ referer = request.headers["Referer"]
+ redirect_to_referer = referer && (allow_other_host || _url_host_allowed?(referer))
+ redirect_to redirect_to_referer ? referer : fallback_location, **args
end
def _compute_redirect_to_location(request, options) #:nodoc:
@@ -120,5 +123,11 @@ module ActionController
302
end
end
+
+ def _url_host_allowed?(url)
+ URI(url.to_s).host == request.host
+ rescue ArgumentError, URI::Error
+ false
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 456a7cf71c..6d181e6456 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -40,7 +40,7 @@ module ActionController
def render_to_string(*)
result = super
if result.respond_to?(:each)
- string = ""
+ string = "".dup
result.each { |r| string << r }
string
else
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 813a7e00d4..d6cd5fd9e0 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "rack/session/abstract/id"
-require_relative "exceptions"
+require "action_controller/metal/exceptions"
require "active_support/security_utils"
module ActionController #:nodoc:
diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index b07f1f3d8c..6e8a95040f 100644
--- a/actionpack/lib/action_controller/metal/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
@@ -12,11 +12,5 @@ module ActionController
self.params = nil
end
end
-
- module ClassMethods
- def before_filters
- _process_action_callbacks.find_all { |x| x.kind == :before }.map(&:name)
- end
- end
end
end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 769be39004..7d42f5d931 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -4,7 +4,7 @@ require "rails"
require "action_controller"
require "action_dispatch/railtie"
require "abstract_controller/railties/routes_helpers"
-require_relative "railties/helpers"
+require "action_controller/railties/helpers"
require "action_view/railtie"
module ActionController
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 74d557fc18..4b408750a4 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -7,7 +7,7 @@ require "active_support/core_ext/module/anonymous"
require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/hash/keys"
require "active_support/testing/constant_lookup"
-require_relative "template_assertions"
+require "action_controller/template_assertions"
require "rails-dom-testing"
module ActionController
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 8073685b78..3328ce17a0 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -166,19 +166,23 @@ module ActionDispatch
@cache_control = cache_control_headers
end
- def handle_conditional_get!
- if etag? || last_modified? || !@cache_control.empty?
- set_conditional_cache_control!(@cache_control)
- end
- end
-
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
NO_CACHE = "no-cache".freeze
PUBLIC = "public".freeze
PRIVATE = "private".freeze
MUST_REVALIDATE = "must-revalidate".freeze
- def set_conditional_cache_control!(cache_control)
+ def handle_conditional_get!
+ # Normally default cache control setting is handled by ETag
+ # middleware. But, if an etag is already set, the middleware
+ # defaults to `no-cache` unless a default `Cache-Control` value is
+ # previously set. So, set a default one here.
+ if (etag? || last_modified?) && !self._cache_control
+ self._cache_control = DEFAULT_CACHE_CONTROL
+ end
+ end
+
+ def merge_and_normalize_cache_control!(cache_control)
control = {}
cc_headers = cache_control_headers
if extras = cc_headers.delete(:extras)
@@ -191,7 +195,7 @@ module ActionDispatch
control.merge! cache_control
if control.empty?
- self._cache_control = DEFAULT_CACHE_CONTROL
+ # Let middleware handle default behavior
elsif control[:no_cache]
self._cache_control = NO_CACHE
if control[:extras]
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index b7141cc1b9..41a47f2c82 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "parameter_filter"
+require "action_dispatch/http/parameter_filter"
module ActionDispatch
module Http
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index d797e90e52..d2b2106845 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -339,4 +339,4 @@ module Mime
end
end
-require_relative "mime_types"
+require "action_dispatch/http/mime_types"
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index ae875eb830..8d7431fd6b 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -123,9 +123,4 @@ module ActionDispatch
end
end
end
-
- module ParamsParser
- include ActiveSupport::Deprecation::DeprecatedConstantAccessor
- deprecate_constant "ParseError", "ActionDispatch::Http::Parameters::ParseError"
- end
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index dee7be184a..60aa1d4e8a 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -3,15 +3,15 @@
require "stringio"
require "active_support/inflector"
-require_relative "headers"
+require "action_dispatch/http/headers"
require "action_controller/metal/exceptions"
require "rack/request"
-require_relative "cache"
-require_relative "mime_negotiation"
-require_relative "parameters"
-require_relative "filter_parameters"
-require_relative "upload"
-require_relative "url"
+require "action_dispatch/http/cache"
+require "action_dispatch/http/mime_negotiation"
+require "action_dispatch/http/parameters"
+require "action_dispatch/http/filter_parameters"
+require "action_dispatch/http/upload"
+require "action_dispatch/http/url"
require "active_support/core_ext/array/conversions"
module ActionDispatch
@@ -199,6 +199,23 @@ module ActionDispatch
@headers ||= Http::Headers.new(self)
end
+ # Early Hints is an HTTP/2 status code that indicates hints to help a client start
+ # making preparations for processing the final response.
+ #
+ # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers.
+ #
+ # The +send_early_hints+ method accepts an hash of links as follows:
+ #
+ # send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
+ #
+ # If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the
+ # Early Hints headers are included by default if supported.
+ def send_early_hints(links)
+ return unless env["rack.early_hints"]
+
+ env["rack.early_hints"].call(links)
+ end
+
# Returns a +String+ with the last requested path including their params.
#
# # get '/foo'
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index b314dbecfe..7e50cb6d23 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
require "active_support/core_ext/module/attribute_accessors"
-require_relative "filter_redirect"
-require_relative "cache"
+require "action_dispatch/http/filter_redirect"
+require "action_dispatch/http/cache"
require "monitor"
module ActionDispatch # :nodoc:
@@ -433,6 +433,7 @@ module ActionDispatch # :nodoc:
def before_committed
return if committed?
assign_default_content_type_and_charset!
+ merge_and_normalize_cache_control!(@cache_control)
handle_conditional_get!
handle_no_content!
end
diff --git a/actionpack/lib/action_dispatch/journey.rb b/actionpack/lib/action_dispatch/journey.rb
index 903063d00f..2852efa6ae 100644
--- a/actionpack/lib/action_dispatch/journey.rb
+++ b/actionpack/lib/action_dispatch/journey.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "journey/router"
-require_relative "journey/gtg/builder"
-require_relative "journey/gtg/simulator"
-require_relative "journey/nfa/builder"
-require_relative "journey/nfa/simulator"
+require "action_dispatch/journey/router"
+require "action_dispatch/journey/gtg/builder"
+require "action_dispatch/journey/gtg/simulator"
+require "action_dispatch/journey/nfa/builder"
+require "action_dispatch/journey/nfa/simulator"
diff --git a/actionpack/lib/action_dispatch/journey/gtg/builder.rb b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
index 7e3d957baa..44c31053cb 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/builder.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/builder.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "transition_table"
+require "action_dispatch/journey/gtg/transition_table"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
index 6ed478f816..ea647e051a 100644
--- a/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/gtg/transition_table.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../nfa/dot"
+require "action_dispatch/journey/nfa/dot"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/nfa/builder.rb b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
index 3135c05ffa..d22302e101 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/builder.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/builder.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "transition_table"
-require_relative "../gtg/transition_table"
+require "action_dispatch/journey/nfa/transition_table"
+require "action_dispatch/journey/gtg/transition_table"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
index bfd929357b..fe55861507 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/transition_table.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "dot"
+require "action_dispatch/journey/nfa/dot"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb
index 0a84f28c1a..08b931a3cd 100644
--- a/actionpack/lib/action_dispatch/journey/nodes/node.rb
+++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../visitors"
+require "action_dispatch/journey/visitors"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
index 6ddfe96098..e002755bcf 100644
--- a/actionpack/lib/action_dispatch/journey/parser.rb
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -8,7 +8,7 @@ require 'racc/parser.rb'
# :stopdoc:
-require_relative "parser_extras"
+require "action_dispatch/journey/parser_extras"
module ActionDispatch
module Journey
class Parser < Racc::Parser
diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y
index 850c84ea1a..f9b1a7a958 100644
--- a/actionpack/lib/action_dispatch/journey/parser.y
+++ b/actionpack/lib/action_dispatch/journey/parser.y
@@ -47,4 +47,4 @@ end
---- header
# :stopdoc:
-require_relative "parser_extras"
+require "action_dispatch/journey/parser_extras"
diff --git a/actionpack/lib/action_dispatch/journey/parser_extras.rb b/actionpack/lib/action_dispatch/journey/parser_extras.rb
index dfbc6c4529..18ec6c9b9b 100644
--- a/actionpack/lib/action_dispatch/journey/parser_extras.rb
+++ b/actionpack/lib/action_dispatch/journey/parser_extras.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "scanner"
-require_relative "nodes/node"
+require "action_dispatch/journey/scanner"
+require "action_dispatch/journey/nodes/node"
module ActionDispatch
# :stopdoc:
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 9987a9bfa1..b8fdde5475 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -1,16 +1,16 @@
# frozen_string_literal: true
-require_relative "router/utils"
-require_relative "routes"
-require_relative "formatter"
+require "action_dispatch/journey/router/utils"
+require "action_dispatch/journey/routes"
+require "action_dispatch/journey/formatter"
before = $-w
$-w = false
-require_relative "parser"
+require "action_dispatch/journey/parser"
$-w = before
-require_relative "route"
-require_relative "path/pattern"
+require "action_dispatch/journey/route"
+require "action_dispatch/journey/path/pattern"
module ActionDispatch
module Journey # :nodoc:
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index c0913715ac..86a070c6ad 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -49,6 +49,18 @@ module ActionDispatch
get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT
end
+ def use_authenticated_cookie_encryption
+ get_header Cookies::USE_AUTHENTICATED_COOKIE_ENCRYPTION
+ end
+
+ def encrypted_cookie_cipher
+ get_header Cookies::ENCRYPTED_COOKIE_CIPHER
+ end
+
+ def signed_cookie_digest
+ get_header Cookies::SIGNED_COOKIE_DIGEST
+ end
+
def secret_token
get_header Cookies::SECRET_TOKEN
end
@@ -64,6 +76,11 @@ module ActionDispatch
def cookies_digest
get_header Cookies::COOKIES_DIGEST
end
+
+ def cookies_rotations
+ get_header Cookies::COOKIES_ROTATIONS
+ end
+
# :startdoc:
end
@@ -83,16 +100,17 @@ module ActionDispatch
# cookies[:lat_lon] = JSON.generate([47.68, -122.37])
#
# # Sets a cookie that expires in 1 hour.
- # cookies[:login] = { value: "XJ-122", expires: 1.hour.from_now }
+ # cookies[:login] = { value: "XJ-122", expires: 1.hour }
+ #
+ # # Sets a cookie that expires at a specific time.
+ # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) }
#
# # Sets a signed cookie, which prevents users from tampering with its value.
- # # The cookie is signed by your app's `secrets.secret_key_base` value.
# # It can be read using the signed method `cookies.signed[:name]`
# cookies.signed[:user_id] = current_user.id
#
# # Sets an encrypted cookie value before sending it to the client which
# # prevent users from reading and tampering with its value.
- # # The cookie is signed by your app's `secrets.secret_key_base` value.
# # It can be read using the encrypted method `cookies.encrypted[:name]`
# cookies.encrypted[:discount] = 45
#
@@ -100,7 +118,7 @@ module ActionDispatch
# cookies.permanent[:login] = "XJ-122"
#
# # You can also chain these methods:
- # cookies.permanent.signed[:login] = "XJ-122"
+ # cookies.signed.permanent[:login] = "XJ-122"
#
# Examples of reading:
#
@@ -118,7 +136,7 @@ module ActionDispatch
#
# cookies[:name] = {
# value: 'a yummy cookie',
- # expires: 1.year.from_now,
+ # expires: 1.year,
# domain: 'domain.com'
# }
#
@@ -144,7 +162,7 @@ module ActionDispatch
# * <tt>:tld_length</tt> - When using <tt>:domain => :all</tt>, this option can be used to explicitly
# set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD.
# For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 1.
- # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time object.
+ # * <tt>:expires</tt> - The time at which this cookie expires, as a \Time or ActiveSupport::Duration object.
# * <tt>:secure</tt> - Whether this cookie is only transmitted to HTTPS servers.
# Default is +false+.
# * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or
@@ -156,10 +174,14 @@ module ActionDispatch
ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze
ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze
AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze
+ USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption".freeze
+ ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher".freeze
+ SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest".freeze
SECRET_TOKEN = "action_dispatch.secret_token".freeze
SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze
COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze
COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze
+ COOKIES_ROTATIONS = "action_dispatch.cookies_rotations".freeze
# Cookies can typically store 4096 bytes.
MAX_COOKIE_SIZE = 4096
@@ -188,10 +210,10 @@ module ActionDispatch
# the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed
# cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
- # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
+ # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
- # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
+ # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
#
# Example:
#
@@ -200,40 +222,28 @@ module ActionDispatch
#
# cookies.signed[:discount] # => 45
def signed
- @signed ||=
- if upgrade_legacy_signed_cookies?
- UpgradeLegacySignedCookieJar.new(self)
- else
- SignedCookieJar.new(self)
- end
+ @signed ||= SignedKeyRotatingCookieJar.new(self)
end
# Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read.
# If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned.
#
- # If +secrets.secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
+ # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set,
# legacy cookies signed with the old key generator will be transparently upgraded.
#
# If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+
# are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded.
#
- # This jar requires that you set a suitable secret for the verification on your app's +secrets.secret_key_base+.
+ # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+.
#
# Example:
#
# cookies.encrypted[:discount] = 45
- # # => Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
+ # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/
#
# cookies.encrypted[:discount] # => 45
def encrypted
- @encrypted ||=
- if upgrade_legacy_signed_cookies?
- UpgradeLegacyEncryptedCookieJar.new(self)
- elsif upgrade_legacy_hmac_aes_cbc_cookies?
- UpgradeLegacyHmacAesCbcCookieJar.new(self)
- else
- EncryptedCookieJar.new(self)
- end
+ @encrypted ||= EncryptedKeyRotatingCookieJar.new(self)
end
# Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set.
@@ -254,34 +264,18 @@ module ActionDispatch
end
def upgrade_legacy_hmac_aes_cbc_cookies?
- request.secret_key_base.present? &&
- request.authenticated_encrypted_cookie_salt.present? &&
- request.encrypted_signed_cookie_salt.present? &&
- request.encrypted_cookie_salt.present?
+ request.secret_key_base.present? &&
+ request.encrypted_signed_cookie_salt.present? &&
+ request.encrypted_cookie_salt.present? &&
+ request.use_authenticated_cookie_encryption
end
- end
-
- # Passing the ActiveSupport::MessageEncryptor::NullSerializer downstream
- # to the Message{Encryptor,Verifier} allows us to handle the
- # (de)serialization step within the cookie jar, which gives us the
- # opportunity to detect and migrate legacy cookies.
- module VerifyAndUpgradeLegacySignedMessage # :nodoc:
- def initialize(*args)
- super
- @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
- end
- def verify_and_upgrade_legacy_signed_message(name, signed_message)
- deserialize(name, @legacy_verifier.verify(signed_message)).tap do |value|
- self[name] = { value: value }
+ def encrypted_cookie_cipher
+ request.encrypted_cookie_cipher || "aes-256-gcm"
end
- rescue ActiveSupport::MessageVerifier::InvalidSignature
- nil
- end
- private
- def parse(name, signed_message)
- super || verify_and_upgrade_legacy_signed_message(name, signed_message)
+ def signed_cookie_digest
+ request.signed_cookie_digest || "SHA1"
end
end
@@ -523,6 +517,7 @@ module ActionDispatch
module SerializedCookieJars # :nodoc:
MARSHAL_SIGNATURE = "\x04\x08".freeze
+ SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer
protected
def needs_migration?(value)
@@ -533,12 +528,16 @@ module ActionDispatch
serializer.dump(value)
end
- def deserialize(name, value)
+ def deserialize(name)
+ rotate = false
+ value = yield -> { rotate = true }
+
if value
- if needs_migration?(value)
- Marshal.load(value).tap do |v|
- self[name] = { value: v }
- end
+ case
+ when needs_migration?(value)
+ self[name] = Marshal.load(value)
+ when rotate
+ self[name] = serializer.load(value)
else
serializer.load(value)
end
@@ -560,24 +559,31 @@ module ActionDispatch
def digest
request.cookies_digest || "SHA1"
end
-
- def key_generator
- request.key_generator
- end
end
- class SignedCookieJar < AbstractCookieJar # :nodoc:
+ class SignedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
include SerializedCookieJars
def initialize(parent_jar)
super
- secret = key_generator.generate_key(request.signed_cookie_salt)
- @verifier = ActiveSupport::MessageVerifier.new(secret, digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+
+ secret = request.key_generator.generate_key(request.signed_cookie_salt)
+ @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER)
+
+ request.cookies_rotations.signed.each do |*secrets, **options|
+ @verifier.rotate(*secrets, serializer: SERIALIZER, **options)
+ end
+
+ if upgrade_legacy_signed_cookies?
+ @verifier.rotate request.secret_token, serializer: SERIALIZER
+ end
end
private
def parse(name, signed_message)
- deserialize name, @verifier.verified(signed_message)
+ deserialize(name) do |rotate|
+ @verifier.verified(signed_message, on_rotation: rotate)
+ end
end
def commit(options)
@@ -587,37 +593,47 @@ module ActionDispatch
end
end
- # UpgradeLegacySignedCookieJar is used instead of SignedCookieJar if
- # secrets.secret_token and secrets.secret_key_base are both set. It reads
- # legacy cookies signed with the old dummy key generator and signs and
- # re-saves them using the new key generator to provide a smooth upgrade path.
- class UpgradeLegacySignedCookieJar < SignedCookieJar #:nodoc:
- include VerifyAndUpgradeLegacySignedMessage
- end
-
- class EncryptedCookieJar < AbstractCookieJar # :nodoc:
+ class EncryptedKeyRotatingCookieJar < AbstractCookieJar # :nodoc:
include SerializedCookieJars
def initialize(parent_jar)
super
- if ActiveSupport::LegacyKeyGenerator === key_generator
- raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " \
- "Read the upgrade documentation to learn more about this new config option."
+ if request.use_authenticated_cookie_encryption
+ key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher)
+ secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: SERIALIZER)
+ else
+ key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-cbc")
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, key_len)
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
+ @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER)
+ end
+
+ request.cookies_rotations.encrypted.each do |*secrets, **options|
+ @encryptor.rotate(*secrets, serializer: SERIALIZER, **options)
end
- cipher = "aes-256-gcm"
- key_len = ActiveSupport::MessageEncryptor.key_len(cipher)
- secret = key_generator.generate_key(request.authenticated_encrypted_cookie_salt || "")[0, key_len]
+ if upgrade_legacy_hmac_aes_cbc_cookies?
+ legacy_cipher = "aes-256-cbc"
+ secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher))
+ sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt)
+
+ @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER)
+ end
- @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
+ if upgrade_legacy_signed_cookies?
+ @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER)
+ end
end
private
def parse(name, encrypted_message)
- deserialize name, @encryptor.decrypt_and_verify(encrypted_message)
- rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
- nil
+ deserialize(name) do |rotate|
+ @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate)
+ end
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature
+ parse_legacy_signed_message(name, encrypted_message)
end
def commit(options)
@@ -625,39 +641,15 @@ module ActionDispatch
raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE
end
- end
- # UpgradeLegacyEncryptedCookieJar is used by ActionDispatch::Session::CookieStore
- # instead of EncryptedCookieJar if secrets.secret_token and secrets.secret_key_base
- # are both set. It reads legacy cookies signed with the old dummy key generator and
- # encrypts and re-saves them using the new key generator to provide a smooth upgrade path.
- class UpgradeLegacyEncryptedCookieJar < EncryptedCookieJar #:nodoc:
- include VerifyAndUpgradeLegacySignedMessage
- end
+ def parse_legacy_signed_message(name, legacy_signed_message)
+ if defined?(@legacy_verifier)
+ deserialize(name) do |rotate|
+ rotate.call
- # UpgradeLegacyHmacAesCbcCookieJar is used by ActionDispatch::Session::CookieStore
- # to upgrade cookies encrypted with AES-256-CBC with HMAC to AES-256-GCM
- class UpgradeLegacyHmacAesCbcCookieJar < EncryptedCookieJar
- def initialize(parent_jar)
- super
-
- secret = key_generator.generate_key(request.encrypted_cookie_salt || "")[0, ActiveSupport::MessageEncryptor.key_len]
- sign_secret = key_generator.generate_key(request.encrypted_signed_cookie_salt || "")
-
- @legacy_encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", digest: digest, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
- end
-
- def decrypt_and_verify_legacy_encrypted_message(name, signed_message)
- deserialize(name, @legacy_encryptor.decrypt_and_verify(signed_message)).tap do |value|
- self[name] = { value: value }
- end
- rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
- nil
- end
-
- private
- def parse(name, signed_message)
- super || decrypt_and_verify_legacy_encrypted_message(name, signed_message)
+ @legacy_verifier.verified(legacy_signed_message)
+ end
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
index 3006cd97ce..511306eb0e 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "../http/request"
-require_relative "exception_wrapper"
-require_relative "../routing/inspector"
+require "action_dispatch/http/request"
+require "action_dispatch/middleware/exception_wrapper"
+require "action_dispatch/routing/inspector"
require "action_view"
require "action_view/base"
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index e054fefc9b..5b0be96223 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -3,8 +3,8 @@
require "rack/utils"
require "rack/request"
require "rack/session/abstract/id"
-require_relative "../cookies"
-require_relative "../../request/session"
+require "action_dispatch/middleware/cookies"
+require "action_dispatch/request/session"
module ActionDispatch
module Session
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
index c84bc8bfad..a6d965a644 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "abstract_store"
+require "action_dispatch/middleware/session/abstract_store"
module ActionDispatch
module Session
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index a12cb00d36..4ea96196d3 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support/core_ext/hash/keys"
-require_relative "abstract_store"
+require "action_dispatch/middleware/session/abstract_store"
require "rack/session/cookie"
module ActionDispatch
@@ -21,39 +21,25 @@ module ActionDispatch
# knowing your app's secret key, but can easily read their +user_id+. This
# was the default for Rails 3 apps.
#
- # If you have secret_key_base set, your cookies will be encrypted. This
+ # Your cookies will be encrypted using your apps secret_key_base. This
# goes a step further than signed cookies in that encrypted cookies cannot
# be altered or read by users. This is the default starting in Rails 4.
#
- # If you have both secret_token and secret_key_base set, your cookies will
- # be encrypted, and signed cookies generated by Rails 3 will be
- # transparently read and encrypted to provide a smooth upgrade path.
- #
# Configure your session store in <tt>config/initializers/session_store.rb</tt>:
#
# Rails.application.config.session_store :cookie_store, key: '_your_app_session'
#
- # Configure your secret key in <tt>config/secrets.yml</tt>:
- #
- # development:
- # secret_key_base: 'secret key'
- #
- # To generate a secret key for an existing application, run <tt>rails secret</tt>.
+ # By default, your secret key base is derived from your application name in
+ # the test and development environments. In all other environments, it is stored
+ # encrypted in the <tt>config/credentials.yml.enc</tt> file.
#
- # If you are upgrading an existing Rails 3 app, you should leave your
- # existing secret_token in place and simply add the new secret_key_base.
- # Note that you should wait to set secret_key_base until you have 100% of
- # your userbase on Rails 4 and are reasonably sure you will not need to
- # rollback to Rails 3. This is because cookies signed based on the new
- # secret_key_base in Rails 4 are not backwards compatible with Rails 3.
- # You are free to leave your existing secret_token in place, not set the
- # new secret_key_base, and ignore the deprecation warnings until you are
- # reasonably sure that your upgrade is otherwise complete. Additionally,
- # you should take care to make sure you are not relying on the ability to
- # decode signed cookies generated by your app in external applications or
- # JavaScript before upgrading.
+ # If your application was not updated to Rails 5.2 defaults, the secret_key_base
+ # will be found in the old <tt>config/secrets.yml</tt> file.
#
- # Note that changing the secret key will invalidate all existing sessions!
+ # Note that changing your secret_key_base will invalidate all existing session.
+ # Additionally, you should take care to make sure you are not relying on the
+ # ability to decode signed cookies generated by your app in external
+ # applications or JavaScript before changing it.
#
# Because CookieStore extends Rack::Session::Abstract::Persisted, many of the
# options described there can be used to customize the session cookie that
diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
index f0aec39c9c..914df3a2b1 100644
--- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "abstract_store"
+require "action_dispatch/middleware/session/abstract_store"
begin
require "rack/session/dalli"
rescue LoadError => e
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index d2e739d27f..3c88afd4d3 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../http/request"
-require_relative "exception_wrapper"
+require "action_dispatch/http/request"
+require "action_dispatch/middleware/exception_wrapper"
module ActionDispatch
# This middleware rescues any exception returned by the application
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
index 45290b6ac3..ef633aadc6 100644
--- a/actionpack/lib/action_dispatch/middleware/ssl.rb
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -1,47 +1,52 @@
# frozen_string_literal: true
module ActionDispatch
- # This middleware is added to the stack when `config.force_ssl = true`, and is passed
- # the options set in `config.ssl_options`. It does three jobs to enforce secure HTTP
+ # This middleware is added to the stack when <tt>config.force_ssl = true</tt>, and is passed
+ # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP
# requests:
#
- # 1. TLS redirect: Permanently redirects http:// requests to https://
- # with the same URL host, path, etc. Enabled by default. Set `config.ssl_options`
- # to modify the destination URL
- # (e.g. `redirect: { host: "secure.widgets.com", port: 8080 }`), or set
- # `redirect: false` to disable this feature.
+ # 1. <b>TLS redirect</b>: Permanently redirects +http://+ requests to +https://+
+ # with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+
+ # to modify the destination URL
+ # (e.g. <tt>redirect: { host: "secure.widgets.com", port: 8080 }</tt>), or set
+ # <tt>redirect: false</tt> to disable this feature.
#
- # 2. Secure cookies: Sets the `secure` flag on cookies to tell browsers they
- # mustn't be sent along with http:// requests. Enabled by default. Set
- # `config.ssl_options` with `secure_cookies: false` to disable this feature.
+ # Requests can opt-out of redirection with +exclude+:
#
- # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember
- # this site as TLS-only and automatically redirect non-TLS requests.
- # Enabled by default. Configure `config.ssl_options` with `hsts: false` to disable.
+ # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
#
- # Set `config.ssl_options` with `hsts: { … }` to configure HSTS:
- # * `expires`: How long, in seconds, these settings will stick. The minimum
- # required to qualify for browser preload lists is `18.weeks`. Defaults to
- # `180.days` (recommended).
- # * `subdomains`: Set to `true` to tell the browser to apply these settings
- # to all subdomains. This protects your cookies from interception by a
- # vulnerable site on a subdomain. Defaults to `true`.
- # * `preload`: Advertise that this site may be included in browsers'
- # preloaded HSTS lists. HSTS protects your site on every visit *except the
- # first visit* since it hasn't seen your HSTS header yet. To close this
- # gap, browser vendors include a baked-in list of HSTS-enabled sites.
- # Go to https://hstspreload.appspot.com to submit your site for inclusion.
- # Defaults to `false`.
+ # 2. <b>Secure cookies</b>: Sets the +secure+ flag on cookies to tell browsers they
+ # must not be sent along with +http://+ requests. Enabled by default. Set
+ # +config.ssl_options+ with <tt>secure_cookies: false</tt> to disable this feature.
#
- # To turn off HSTS, omitting the header is not enough. Browsers will remember the
- # original HSTS directive until it expires. Instead, use the header to tell browsers to
- # expire HSTS immediately. Setting `hsts: false` is a shortcut for
- # `hsts: { expires: 0 }`.
+ # 3. <b>HTTP Strict Transport Security (HSTS)</b>: Tells the browser to remember
+ # this site as TLS-only and automatically redirect non-TLS requests.
+ # Enabled by default. Configure +config.ssl_options+ with <tt>hsts: false</tt> to disable.
#
- # Requests can opt-out of redirection with `exclude`:
+ # Set +config.ssl_options+ with <tt>hsts: { ... }</tt> to configure HSTS:
#
- # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
+ # * +expires+: How long, in seconds, these settings will stick. The minimum
+ # required to qualify for browser preload lists is 18 weeks. Defaults to
+ # 180 days (recommended).
+ #
+ # * +subdomains+: Set to +true+ to tell the browser to apply these settings
+ # to all subdomains. This protects your cookies from interception by a
+ # vulnerable site on a subdomain. Defaults to +true+.
+ #
+ # * +preload+: Advertise that this site may be included in browsers'
+ # preloaded HSTS lists. HSTS protects your site on every visit <i>except the
+ # first visit</i> since it hasn't seen your HSTS header yet. To close this
+ # gap, browser vendors include a baked-in list of HSTS-enabled sites.
+ # Go to https://hstspreload.org to submit your site for inclusion.
+ # Defaults to +false+.
+ #
+ # To turn off HSTS, omitting the header is not enough. Browsers will remember the
+ # original HSTS directive until it expires. Instead, use the header to tell browsers to
+ # expire HSTS immediately. Setting <tt>hsts: false</tt> is a shortcut for
+ # <tt>hsts: { expires: 0 }</tt>.
class SSL
+ # :stopdoc:
+
# Default to 180 days, the low end for https://www.ssllabs.com/ssltest/
# and greater than the 18-week requirement for browser preload lists.
HSTS_EXPIRES_IN = 15552000
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 4743a7ce61..855f2ffa47 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "action_dispatch"
+require "active_support/messages/rotation_configuration"
module ActionDispatch
class Railtie < Rails::Railtie # :nodoc:
@@ -18,6 +19,7 @@ module ActionDispatch
config.action_dispatch.signed_cookie_salt = "signed cookie"
config.action_dispatch.encrypted_cookie_salt = "encrypted cookie"
config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie"
+ config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie"
config.action_dispatch.use_authenticated_cookie_encryption = false
config.action_dispatch.perform_deep_munge = true
@@ -27,6 +29,8 @@ module ActionDispatch
"X-Content-Type-Options" => "nosniff"
}
+ config.action_dispatch.cookies_rotations = ActiveSupport::Messages::RotationConfiguration.new
+
config.eager_load_namespaces << ActionDispatch
initializer "action_dispatch.configure" do |app|
@@ -39,8 +43,6 @@ module ActionDispatch
ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
- config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie" if config.action_dispatch.use_authenticated_cookie_encryption
-
config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 2b43ade081..dea8387c3d 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -4,8 +4,8 @@ require "active_support/core_ext/hash/slice"
require "active_support/core_ext/enumerable"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/regexp"
-require_relative "redirection"
-require_relative "endpoint"
+require "action_dispatch/routing/redirection"
+require "action_dispatch/routing/endpoint"
module ActionDispatch
module Routing
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 2e2bc87b57..143a4b3d62 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require_relative "../http/request"
+require "action_dispatch/http/request"
require "active_support/core_ext/uri"
require "active_support/core_ext/array/extract_options"
require "rack/utils"
require "action_controller/metal/exceptions"
-require_relative "endpoint"
+require "action_dispatch/routing/endpoint"
module ActionDispatch
module Routing
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 445e86b13c..71cb458112 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,14 +1,14 @@
# frozen_string_literal: true
-require_relative "../journey"
+require "action_dispatch/journey"
require "active_support/core_ext/object/to_query"
require "active_support/core_ext/hash/slice"
require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/array/extract_options"
require "action_controller/metal/exceptions"
-require_relative "../http/request"
-require_relative "endpoint"
+require "action_dispatch/http/request"
+require "action_dispatch/routing/endpoint"
module ActionDispatch
module Routing
@@ -584,14 +584,14 @@ module ActionDispatch
if route.segment_keys.include?(:controller)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Using a dynamic :controller segment in a route is deprecated and
- will be removed in Rails 5.2.
+ will be removed in Rails 6.0.
MSG
end
if route.segment_keys.include?(:action)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Using a dynamic :action segment in a route is deprecated and
- will be removed in Rails 5.2.
+ will be removed in Rails 6.0.
MSG
end
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index ae4aeac59d..58cea7b779 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -1,13 +1,15 @@
# frozen_string_literal: true
+gem "capybara", "~> 2.13"
+
require "capybara/dsl"
require "capybara/minitest"
require "action_controller"
-require_relative "system_testing/driver"
-require_relative "system_testing/server"
-require_relative "system_testing/test_helpers/screenshot_helper"
-require_relative "system_testing/test_helpers/setup_and_teardown"
-require_relative "system_testing/test_helpers/undef_methods"
+require "action_dispatch/system_testing/driver"
+require "action_dispatch/system_testing/server"
+require "action_dispatch/system_testing/test_helpers/screenshot_helper"
+require "action_dispatch/system_testing/test_helpers/setup_and_teardown"
+require "action_dispatch/system_testing/test_helpers/undef_methods"
module ActionDispatch
# = System Testing
@@ -121,6 +123,8 @@ module ActionDispatch
#
# driven_by :selenium, using: :firefox
#
+ # driven_by :selenium, using: :headless_chrome
+ #
# driven_by :selenium, screen_size: [800, 800]
def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {})
self.driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options)
diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb
index 4279336f2f..770fbde74e 100644
--- a/actionpack/lib/action_dispatch/system_testing/driver.rb
+++ b/actionpack/lib/action_dispatch/system_testing/driver.rb
@@ -31,8 +31,24 @@ module ActionDispatch
end
end
+ def browser_options
+ if @browser == :headless_chrome
+ browser_options = Selenium::WebDriver::Chrome::Options.new
+ browser_options.args << "--headless"
+ browser_options.args << "--disable-gpu"
+
+ @options.merge(options: browser_options)
+ else
+ @options
+ end
+ end
+
+ def browser
+ @browser == :headless_chrome ? :chrome : @browser
+ end
+
def register_selenium(app)
- Capybara::Selenium::Driver.new(app, { browser: @browser }.merge(@options)).tap do |driver|
+ Capybara::Selenium::Driver.new(app, { browser: browser }.merge(browser_options)).tap do |driver|
driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
end
end
diff --git a/actionpack/lib/action_dispatch/system_testing/server.rb b/actionpack/lib/action_dispatch/system_testing/server.rb
index 76bada8df1..32aa6a4dc4 100644
--- a/actionpack/lib/action_dispatch/system_testing/server.rb
+++ b/actionpack/lib/action_dispatch/system_testing/server.rb
@@ -12,29 +12,17 @@ module ActionDispatch
self.silence_puma = false
def run
- register
setup
end
private
- def register
- Capybara.register_server :rails_puma do |app, port, host|
- Rack::Handler::Puma.run(
- app,
- Port: port,
- Threads: "0:1",
- Silent: self.class.silence_puma
- )
- end
- end
-
def setup
set_server
set_port
end
def set_server
- Capybara.server = :rails_puma
+ Capybara.server = :puma, { Silent: self.class.silence_puma }
end
def set_port
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb
index ef680cafed..d64be3b3d9 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb
@@ -14,7 +14,7 @@ module ActionDispatch
def method_missing(method, *args, &block)
if METHODS.include?(method)
- raise NoMethodError
+ raise NoMethodError, "System tests cannot make direct requests via ##{method}; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information."
else
super
end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index ae1f368e8b..7171b6942c 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -7,43 +7,43 @@ require "active_support/core_ext/object/try"
require "rack/test"
require "minitest"
-require_relative "request_encoder"
+require "action_dispatch/testing/request_encoder"
module ActionDispatch
module Integration #:nodoc:
module RequestHelpers
- # Performs a GET request with the given parameters. See +#process+ for more
- # details.
+ # Performs a GET request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def get(path, **args)
process(:get, path, **args)
end
- # Performs a POST request with the given parameters. See +#process+ for more
- # details.
+ # Performs a POST request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def post(path, **args)
process(:post, path, **args)
end
- # Performs a PATCH request with the given parameters. See +#process+ for more
- # details.
+ # Performs a PATCH request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def patch(path, **args)
process(:patch, path, **args)
end
- # Performs a PUT request with the given parameters. See +#process+ for more
- # details.
+ # Performs a PUT request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def put(path, **args)
process(:put, path, **args)
end
- # Performs a DELETE request with the given parameters. See +#process+ for
- # more details.
+ # Performs a DELETE request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def delete(path, **args)
process(:delete, path, **args)
end
- # Performs a HEAD request with the given parameters. See +#process+ for more
- # details.
+ # Performs a HEAD request with the given parameters. See ActionDispatch::Integration::Session#process
+ # for more details.
def head(path, *args)
process(:head, path, *args)
end
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index 3b63706aaa..8ac50c730d 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../middleware/cookies"
-require_relative "../middleware/flash"
+require "action_dispatch/middleware/cookies"
+require "action_dispatch/middleware/flash"
module ActionDispatch
module TestProcess
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index b23ea7479c..1e6b21f235 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "request_encoder"
+require "action_dispatch/testing/request_encoder"
module ActionDispatch
# Integration test methods such as ActionDispatch::Integration::Session#get
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index fe2fc7c474..95fdd3affb 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -23,4 +23,4 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-require_relative "action_pack/version"
+require "action_pack/version"
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index caa56018f8..34dc02bebf 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -449,3 +449,7 @@ end
class DrivenBySeleniumWithChrome < ActionDispatch::SystemTestCase
driven_by :selenium, using: :chrome
end
+
+class DrivenBySeleniumWithHeadlessChrome < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :headless_chrome
+end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index d92ae0b817..34bc2c0caa 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "abstract_unit"
-require "active_support/key_generator"
+require "active_support/messages/rotation_configuration"
class FlashTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -243,6 +243,7 @@ end
class FlashIntegrationTest < ActionDispatch::IntegrationTest
SessionKey = "_myapp_session"
Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
+ Rotations = ActiveSupport::Messages::RotationConfiguration.new
class TestController < ActionController::Base
add_flash_types :bar
@@ -348,6 +349,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
args[0] ||= {}
args[0][:env] ||= {}
args[0][:env]["action_dispatch.key_generator"] ||= Generator
+ args[0][:env]["action_dispatch.cookies_rotations"] = Rotations
super(path, *args)
end
diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb
index d9f200b2a7..280134f8d2 100644
--- a/actionpack/test/controller/new_base/base_test.rb
+++ b/actionpack/test/controller/new_base/base_test.rb
@@ -47,7 +47,6 @@ module Dispatching
end
class BaseTest < Rack::TestCase
- # :api: plugin
test "simple dispatching" do
get "/dispatching/simple/index"
@@ -56,14 +55,12 @@ module Dispatching
assert_content_type "text/plain; charset=utf-8"
end
- # :api: plugin
test "directly modifying response body" do
get "/dispatching/simple/modify_response_body"
assert_body "success"
end
- # :api: plugin
test "directly modifying response body twice" do
get "/dispatching/simple/modify_response_body_twice"
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index e447b66486..2959dc3e4d 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -62,6 +62,10 @@ class RedirectController < ActionController::Base
redirect_back(fallback_location: "/things/stuff", status: 307)
end
+ def safe_redirect_back_with_status
+ redirect_back(fallback_location: "/things/stuff", status: 307, allow_other_host: false)
+ end
+
def host_redirect
redirect_to action: "other_host", only_path: false, host: "other.test.host"
end
@@ -259,6 +263,23 @@ class RedirectTest < ActionController::TestCase
assert_equal "http://test.host/things/stuff", redirect_to_url
end
+ def test_safe_redirect_back_from_other_host
+ @request.env["HTTP_REFERER"] = "http://another.host/coming/from"
+ get :safe_redirect_back_with_status
+
+ assert_response 307
+ assert_equal "http://test.host/things/stuff", redirect_to_url
+ end
+
+ def test_safe_redirect_back_from_the_same_host
+ referer = "http://test.host/coming/from"
+ @request.env["HTTP_REFERER"] = referer
+ get :safe_redirect_back_with_status
+
+ assert_response 307
+ assert_equal referer, redirect_to_url
+ end
+
def test_redirect_to_record
with_routing do |set|
set.draw do
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 3619afc513..37a62edc15 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -162,6 +162,17 @@ class TestController < ActionController::Base
render action: "hello_world"
end
+ def conditional_hello_with_expires_and_confliciting_cache_control_headers
+ response.headers["Cache-Control"] = "no-cache, must-revalidate"
+ expires_now
+ render action: "hello_world"
+ end
+
+ def conditional_hello_without_expires_and_confliciting_cache_control_headers
+ response.headers["Cache-Control"] = "no-cache, must-revalidate"
+ render action: "hello_world"
+ end
+
def conditional_hello_with_bangs
render action: "hello_world"
end
@@ -368,6 +379,16 @@ class ExpiresInRenderTest < ActionController::TestCase
assert_match(/no-transform/, @response.headers["Cache-Control"])
end
+ def test_expires_now_with_conflicting_cache_control_headers
+ get :conditional_hello_with_expires_and_confliciting_cache_control_headers
+ assert_equal "no-cache", @response.headers["Cache-Control"]
+ end
+
+ def test_no_expires_now_with_conflicting_cache_control_headers
+ get :conditional_hello_without_expires_and_confliciting_cache_control_headers
+ assert_equal "no-cache", @response.headers["Cache-Control"]
+ end
+
def test_date_header_when_expires_in
time = Time.mktime(2011, 10, 30)
Time.stub :now, time do
@@ -606,6 +627,18 @@ class MetalRenderTest < ActionController::TestCase
end
end
+class ActionControllerRenderTest < ActionController::TestCase
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+ end
+
+ def test_direct_render_to_string_with_body
+ mc = MinimalController.new
+ assert_equal "Hello world!", mc.render_to_string(body: ["Hello world!"])
+ end
+end
+
class ActionControllerBaseRenderTest < ActionController::TestCase
def test_direct_render_to_string
ac = ActionController::Base.new()
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 12ae95d602..eb3d2f34a8 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -2,6 +2,7 @@
require "abstract_unit"
require "active_support/log_subscriber/test_helper"
+require "active_support/messages/rotation_configuration"
# common controller actions
module RequestForgeryProtectionActions
@@ -630,13 +631,14 @@ end
class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase
class NullSessionDummyKeyGenerator
- def generate_key(secret)
+ def generate_key(secret, length = nil)
"03312270731a2ed0d11ed091c2338a06"
end
end
def setup
@request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new
+ @request.env[ActionDispatch::Cookies::COOKIES_ROTATIONS] = ActiveSupport::Messages::RotationConfiguration.new
end
test "should allow to set signed cookies" do
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 07f8c9dd8a..4ed79073e5 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -33,7 +33,7 @@ class RescueController < ActionController::Base
class ResourceUnavailableToRescueAsString < StandardError
end
- # We use a fully-qualified name in some strings, and a relative constant
+ # We use a fully qualified name in some strings, and a relative constant
# name in some other to test correct handling of both cases.
rescue_from NotAuthorized, with: :deny_access
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index cb225c0f62..40cbad3b0d 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -3,7 +3,7 @@
require "abstract_unit"
require "openssl"
require "active_support/key_generator"
-require "active_support/message_verifier"
+require "active_support/messages/rotation_configuration"
class CookieJarTest < ActiveSupport::TestCase
attr_reader :request
@@ -287,15 +287,25 @@ class CookiesTest < ActionController::TestCase
tests TestController
- SALT = "b3c631c314c0bbca50c1b2843150fe33"
+ SECRET_KEY_BASE = "b3c631c314c0bbca50c1b2843150fe33"
+ SIGNED_COOKIE_SALT = "signed cookie"
+ ENCRYPTED_COOKIE_SALT = "encrypted cookie"
+ ENCRYPTED_SIGNED_COOKIE_SALT = "sigend encrypted cookie"
+ AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "authenticated encrypted cookie"
def setup
super
- @request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new(SALT, iterations: 2)
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new(SECRET_KEY_BASE, iterations: 2)
+ @request.env["action_dispatch.cookies_rotations"] = ActiveSupport::Messages::RotationConfiguration.new
- @request.env["action_dispatch.signed_cookie_salt"] =
- @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] = SALT
+ @request.env["action_dispatch.secret_key_base"] = SECRET_KEY_BASE
+ @request.env["action_dispatch.use_authenticated_cookie_encryption"] = true
+
+ @request.env["action_dispatch.signed_cookie_salt"] = SIGNED_COOKIE_SALT
+ @request.env["action_dispatch.encrypted_cookie_salt"] = ENCRYPTED_COOKIE_SALT
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = ENCRYPTED_SIGNED_COOKIE_SALT
+ @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] = AUTHENTICATED_ENCRYPTED_COOKIE_SALT
@request.host = "www.nextangle.com"
end
@@ -430,28 +440,72 @@ class CookiesTest < ActionController::TestCase
assert_equal 45, cookies.signed[:user_id]
key_generator = @request.env["action_dispatch.key_generator"]
- signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
- secret = key_generator.generate_key(signed_cookie_salt)
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: "SHA1")
assert_equal verifier.generate(45), cookies[:user_id]
end
def test_signed_cookie_using_custom_digest
- @request.env["action_dispatch.cookies_digest"] = "SHA256"
+ @request.env["action_dispatch.signed_cookie_digest"] = "SHA256"
+
get :set_signed_cookie
cookies = @controller.send :cookies
assert_not_equal 45, cookies[:user_id]
assert_equal 45, cookies.signed[:user_id]
key_generator = @request.env["action_dispatch.key_generator"]
- signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
- secret = key_generator.generate_key(signed_cookie_salt)
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: "SHA256")
assert_equal verifier.generate(45), cookies[:user_id]
end
+ def test_signed_cookie_rotating_secret_and_digest
+ secret = "b3c631c314c0bbca50c1b2843150fe33"
+
+ @request.env["action_dispatch.signed_cookie_digest"] = "SHA256"
+ @request.env["action_dispatch.cookies_rotations"].rotate :signed, secret, digest: "SHA1"
+
+ old_message = ActiveSupport::MessageVerifier.new(secret, digest: "SHA1", serializer: Marshal).generate(45)
+ @request.headers["Cookie"] = "user_id=#{old_message}"
+
+ get :get_signed_cookie
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA256", serializer: Marshal)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_signed_cookie_with_legacy_secret_scheme
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+
+ old_message = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", digest: "SHA1", serializer: Marshal).generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{old_message}"
+ get :get_signed_cookie
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key("signed cookie")
+ verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA1", serializer: Marshal)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_tampered_with_signed_cookie
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal, digest: "SHA1")
+ message = verifier.generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{Marshal.dump 45}--#{message.split("--").last}"
+ get :get_signed_cookie
+ assert_nil @controller.send(:cookies).signed[:user_id]
+ end
+
def test_signed_cookie_using_default_serializer
get :set_signed_cookie
cookies = @controller.send :cookies
@@ -494,8 +548,7 @@ class CookiesTest < ActionController::TestCase
@request.env["action_dispatch.cookies_serializer"] = :hybrid
key_generator = @request.env["action_dispatch.key_generator"]
- signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
- secret = key_generator.generate_key(signed_cookie_salt)
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
marshal_value = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal).generate(45)
@request.headers["Cookie"] = "user_id=#{marshal_value}"
@@ -514,8 +567,8 @@ class CookiesTest < ActionController::TestCase
@request.env["action_dispatch.cookies_serializer"] = :hybrid
key_generator = @request.env["action_dispatch.key_generator"]
- signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
- secret = key_generator.generate_key(signed_cookie_salt)
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+
json_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45)
@request.headers["Cookie"] = "user_id=#{json_value}"
@@ -578,11 +631,10 @@ class CookiesTest < ActionController::TestCase
def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
@request.env["action_dispatch.cookies_serializer"] = :hybrid
- cipher = "aes-256-gcm"
- salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal)
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], 32)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal)
marshal_value = encryptor.encrypt_and_sign("bar")
@request.headers["Cookie"] = "foo=#{::Rack::Utils.escape marshal_value}"
@@ -592,7 +644,7 @@ class CookiesTest < ActionController::TestCase
assert_not_equal "bar", cookies[:foo]
assert_equal "bar", cookies.encrypted[:foo]
- json_encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
+ json_encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON)
assert_not_nil @response.cookies["foo"]
assert_equal "bar", json_encryptor.decrypt_and_verify(@response.cookies["foo"])
end
@@ -600,11 +652,10 @@ class CookiesTest < ActionController::TestCase
def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value
@request.env["action_dispatch.cookies_serializer"] = :hybrid
- cipher = "aes-256-gcm"
- salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], 32)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON)
json_value = encryptor.encrypt_and_sign("bar")
@request.headers["Cookie"] = "foo=#{::Rack::Utils.escape json_value}"
@@ -691,65 +742,8 @@ class CookiesTest < ActionController::TestCase
}
end
- def test_signed_uses_signed_cookie_jar_if_only_secret_token_is_set
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = nil
- get :set_signed_cookie
- assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed
- end
-
- def test_signed_uses_signed_cookie_jar_if_only_secret_key_base_is_set
- @request.env["action_dispatch.secret_token"] = nil
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- get :set_signed_cookie
- assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed
- end
-
- def test_signed_uses_upgrade_legacy_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- get :set_signed_cookie
- assert_kind_of ActionDispatch::Cookies::UpgradeLegacySignedCookieJar, cookies.signed
- end
-
- def test_signed_or_encrypted_uses_signed_cookie_jar_if_only_secret_token_is_set
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = nil
- get :get_encrypted_cookie
- assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed_or_encrypted
- end
-
- def test_signed_or_encrypted_uses_encrypted_cookie_jar_if_only_secret_key_base_is_set
- @request.env["action_dispatch.secret_token"] = nil
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- get :get_encrypted_cookie
- assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.signed_or_encrypted
- end
-
- def test_signed_or_encrypted_uses_upgrade_legacy_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- get :get_encrypted_cookie
- assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.signed_or_encrypted
- end
-
- def test_encrypted_uses_encrypted_cookie_jar_if_only_secret_key_base_is_set
- @request.env["action_dispatch.secret_token"] = nil
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- get :get_encrypted_cookie
- assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.encrypted
- end
-
- def test_encrypted_uses_upgrade_legacy_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
- @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- get :get_encrypted_cookie
- assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.encrypted
- end
-
def test_legacy_signed_cookie_is_read_and_transparently_upgraded_by_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
@@ -766,9 +760,6 @@ class CookiesTest < ActionController::TestCase
def test_legacy_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
- @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar")
@@ -777,17 +768,14 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- cipher = "aes-256-gcm"
- salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal)
+ secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], 32)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
@request.env["action_dispatch.cookies_serializer"] = :json
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
@@ -805,7 +793,6 @@ class CookiesTest < ActionController::TestCase
def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
@request.env["action_dispatch.cookies_serializer"] = :json
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar")
@@ -824,7 +811,6 @@ class CookiesTest < ActionController::TestCase
def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
@request.env["action_dispatch.cookies_serializer"] = :hybrid
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
@@ -842,7 +828,6 @@ class CookiesTest < ActionController::TestCase
def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
@request.env["action_dispatch.cookies_serializer"] = :hybrid
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar")
@@ -851,17 +836,15 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- cipher = "aes-256-gcm"
salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
def test_legacy_marshal_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
@request.env["action_dispatch.cookies_serializer"] = :hybrid
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
@@ -878,6 +861,8 @@ class CookiesTest < ActionController::TestCase
def test_legacy_marshal_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
@request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ @request.env["action_dispatch.use_authenticated_cookie_encryption"] = true
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
@@ -888,16 +873,14 @@ class CookiesTest < ActionController::TestCase
assert_equal "bar", @controller.send(:cookies).encrypted[:foo]
- cipher = "aes-256-gcm"
salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON)
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")]
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
@request.headers["Cookie"] = "user_id=45"
get :get_signed_cookie
@@ -908,7 +891,6 @@ class CookiesTest < ActionController::TestCase
def test_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered
@request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
@request.headers["Cookie"] = "foo=baz"
get :get_encrypted_cookie
@@ -917,18 +899,50 @@ class CookiesTest < ActionController::TestCase
assert_nil @response.cookies["foo"]
end
- def test_legacy_hmac_aes_cbc_encrypted_marshal_cookie_is_upgraded_to_authenticated_encrypted_cookie
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ def test_use_authenticated_cookie_encryption_uses_legacy_hmac_aes_cbc_encryption_when_not_enabled
+ @request.env["action_dispatch.use_authenticated_cookie_encryption"] = nil
- @request.env["action_dispatch.encrypted_cookie_salt"] =
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len("aes-256-cbc"))
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", digest: "SHA1", serializer: Marshal)
+
+ get :set_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+ assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_rotating_signed_cookies_digest
+ @request.env["action_dispatch.signed_cookie_digest"] = "SHA256"
+ @request.env["action_dispatch.cookies_rotations"].rotate :signed, digest: "SHA1"
key_generator = @request.env["action_dispatch.key_generator"]
+
+ old_secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ old_value = ActiveSupport::MessageVerifier.new(old_secret).generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{old_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA256")
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_hmac_aes_cbc_encrypted_marshal_cookie_is_upgraded_to_authenticated_encrypted_cookie
+ key_generator = @request.env["action_dispatch.key_generator"]
encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
- secret = key_generator.generate_key(encrypted_cookie_salt)
+ secret = key_generator.generate_key(encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len("aes-256-cbc"))
sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
- marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: Marshal).encrypt_and_sign("bar")
+ marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: Marshal).encrypt_and_sign("bar")
@request.headers["Cookie"] = "foo=#{marshal_value}"
@@ -938,27 +952,22 @@ class CookiesTest < ActionController::TestCase
assert_not_equal "bar", cookies[:foo]
assert_equal "bar", cookies.encrypted[:foo]
- aead_cipher = "aes-256-gcm"
aead_salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- aead_secret = key_generator.generate_key(aead_salt)[0, ActiveSupport::MessageEncryptor.key_len(aead_cipher)]
- aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: aead_cipher, serializer: Marshal)
+ aead_secret = key_generator.generate_key(aead_salt, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm"))
+ aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: "aes-256-gcm", serializer: Marshal)
assert_equal "bar", aead_encryptor.decrypt_and_verify(@response.cookies["foo"])
end
def test_legacy_hmac_aes_cbc_encrypted_json_cookie_is_upgraded_to_authenticated_encrypted_cookie
@request.env["action_dispatch.cookies_serializer"] = :json
- @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
-
- @request.env["action_dispatch.encrypted_cookie_salt"] =
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
key_generator = @request.env["action_dispatch.key_generator"]
encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
- secret = key_generator.generate_key(encrypted_cookie_salt)
+ secret = key_generator.generate_key(encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len("aes-256-cbc"))
sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
- marshal_value = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret, serializer: JSON).encrypt_and_sign("bar")
+ marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: JSON).encrypt_and_sign("bar")
@request.headers["Cookie"] = "foo=#{marshal_value}"
@@ -968,19 +977,17 @@ class CookiesTest < ActionController::TestCase
assert_not_equal "bar", cookies[:foo]
assert_equal "bar", cookies.encrypted[:foo]
- aead_cipher = "aes-256-gcm"
aead_salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- aead_secret = key_generator.generate_key(aead_salt)[0, ActiveSupport::MessageEncryptor.key_len(aead_cipher)]
- aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: aead_cipher, serializer: JSON)
+ aead_secret = key_generator.generate_key(aead_salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")]
+ aead_encryptor = ActiveSupport::MessageEncryptor.new(aead_secret, cipher: "aes-256-gcm", serializer: JSON)
assert_equal "bar", aead_encryptor.decrypt_and_verify(@response.cookies["foo"])
end
def test_legacy_hmac_aes_cbc_encrypted_cookie_using_64_byte_key_is_upgraded_to_authenticated_encrypted_cookie
@request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
-
- @request.env["action_dispatch.encrypted_cookie_salt"] =
- @request.env["action_dispatch.encrypted_signed_cookie_salt"] = SALT
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
# Cookie generated with 64 bytes secret
message = ["566d4e75536d686e633246564e6b493062557079626c566d51574d30515430394c53315665564a694e4563786555744f57537454576b396a5a31566a626e52525054303d2d2d34663234333330623130623261306163363562316266323335396164666364613564643134623131"].pack("H*")
@@ -991,15 +998,35 @@ class CookiesTest < ActionController::TestCase
cookies = @controller.send :cookies
assert_not_equal "bar", cookies[:foo]
assert_equal "bar", cookies.encrypted[:foo]
- cipher = "aes-256-gcm"
salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"]
- secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)]
- encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: Marshal)
+ secret = @request.env["action_dispatch.key_generator"].generate_key(salt, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm"))
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal)
assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
end
+ def test_encrypted_cookie_rotating_secret
+ secret = "b3c631c314c0bbca50c1b2843150fe33"
+
+ @request.env["action_dispatch.encrypted_cookie_cipher"] = "aes-256-gcm"
+ @request.env["action_dispatch.cookies_rotations"].rotate :encrypted, secret
+
+ key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")
+
+ old_message = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal).encrypt_and_sign(45)
+
+ @request.headers["Cookie"] = "foo=#{::Rack::Utils.escape old_message}"
+
+ get :get_encrypted_cookie
+ assert_equal 45, @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], key_len)
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal)
+ assert_equal 45, encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
def test_cookie_with_all_domain_option
get :set_cookie_with_domain
assert_response :success
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 68c6d26364..2a18395aac 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -1304,3 +1304,18 @@ class RequestFormData < BaseRequestTest
assert !request.form_data?
end
end
+
+class EarlyHintsRequestTest < BaseRequestTest
+ def setup
+ super
+ @env["rack.early_hints"] = lambda { |links| links }
+ @request = stub_request
+ end
+
+ test "when early hints is set in the env link headers are sent" do
+ early_hints = @request.send_early_hints("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
+ expected_hints = { "Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload" }
+
+ assert_equal expected_hints, early_hints
+ end
+end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 446b65a9b9..44f902c163 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3,6 +3,7 @@
require "erb"
require "abstract_unit"
require "controller/fake_controllers"
+require "active_support/messages/rotation_configuration"
class TestRoutingMapper < ActionDispatch::IntegrationTest
SprocketsApp = lambda { |env|
@@ -4947,6 +4948,7 @@ end
class FlashRedirectTest < ActionDispatch::IntegrationTest
SessionKey = "_myapp_session"
Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
+ Rotations = ActiveSupport::Messages::RotationConfiguration.new
class KeyGeneratorMiddleware
def initialize(app)
@@ -4955,6 +4957,8 @@ class FlashRedirectTest < ActionDispatch::IntegrationTest
def call(env)
env["action_dispatch.key_generator"] ||= Generator
+ env["action_dispatch.cookies_rotations"] ||= Rotations
+
@app.call(env)
end
end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 6517cf4c99..cf51c47068 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -3,11 +3,13 @@
require "abstract_unit"
require "stringio"
require "active_support/key_generator"
+require "active_support/messages/rotation_configuration"
class CookieStoreTest < ActionDispatch::IntegrationTest
SessionKey = "_myapp_session"
SessionSecret = "b3c631c314c0bbca50c1b2843150fe33"
Generator = ActiveSupport::LegacyKeyGenerator.new(SessionSecret)
+ Rotations = ActiveSupport::Messages::RotationConfiguration.new
Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, digest: "SHA1")
SignedBar = Verifier.generate(foo: "bar", session_id: SecureRandom.hex(16))
@@ -346,6 +348,8 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
args[0] ||= {}
args[0][:headers] ||= {}
args[0][:headers]["action_dispatch.key_generator"] ||= Generator
+ args[0][:headers]["action_dispatch.cookies_rotations"] ||= Rotations
+
super(path, *args)
end
diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb
index e6f9353b22..75feae6fe0 100644
--- a/actionpack/test/dispatch/system_testing/driver_test.rb
+++ b/actionpack/test/dispatch/system_testing/driver_test.rb
@@ -17,6 +17,14 @@ class DriverTest < ActiveSupport::TestCase
assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options)
end
+ test "initializing the driver with a headless chrome" do
+ driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :headless_chrome, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" })
+ assert_equal :selenium, driver.instance_variable_get(:@name)
+ assert_equal :headless_chrome, driver.instance_variable_get(:@browser)
+ assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size)
+ assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options)
+ end
+
test "initializing the driver with a poltergeist" do
driver = ActionDispatch::SystemTesting::Driver.new(:poltergeist, screen_size: [1400, 1400], options: { js_errors: false })
assert_equal :poltergeist, driver.instance_variable_get(:@name)
diff --git a/actionpack/test/dispatch/system_testing/server_test.rb b/actionpack/test/dispatch/system_testing/server_test.rb
index ed65d93e49..1866225fc1 100644
--- a/actionpack/test/dispatch/system_testing/server_test.rb
+++ b/actionpack/test/dispatch/system_testing/server_test.rb
@@ -9,10 +9,6 @@ class ServerTest < ActiveSupport::TestCase
ActionDispatch::SystemTesting::Server.new.run
end
- test "initializing the server port" do
- assert_includes Capybara.servers, :rails_puma
- end
-
test "port is always included" do
assert Capybara.always_include_port, "expected Capybara.always_include_port to be true"
end
diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
index b60ec559ae..c6a6aef92b 100644
--- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb
+++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
@@ -22,6 +22,12 @@ class SetDriverToSeleniumTest < DrivenBySeleniumWithChrome
end
end
+class SetDriverToSeleniumHeadlessChromeTest < DrivenBySeleniumWithHeadlessChrome
+ test "uses selenium headless chrome" do
+ assert_equal :selenium, Capybara.current_driver
+ end
+end
+
class SetHostTest < DrivenByRackTest
test "sets default host" do
assert_equal "http://127.0.0.1", Capybara.app_host
@@ -36,32 +42,37 @@ end
class UndefMethodsTest < DrivenBySeleniumWithChrome
test "get" do
- assert_raise NoMethodError do
+ exception = assert_raise NoMethodError do
get "http://example.com"
end
+ assert_equal "System tests cannot make direct requests via #get; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message
end
test "post" do
- assert_raise NoMethodError do
+ exception = assert_raise NoMethodError do
post "http://example.com"
end
+ assert_equal "System tests cannot make direct requests via #post; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message
end
test "put" do
- assert_raise NoMethodError do
+ exception = assert_raise NoMethodError do
put "http://example.com"
end
+ assert_equal "System tests cannot make direct requests via #put; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message
end
test "patch" do
- assert_raise NoMethodError do
+ exception = assert_raise NoMethodError do
patch "http://example.com"
end
+ assert_equal "System tests cannot make direct requests via #patch; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message
end
test "delete" do
- assert_raise NoMethodError do
+ exception = assert_raise NoMethodError do
delete "http://example.com"
end
+ assert_equal "System tests cannot make direct requests via #delete; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information.", exception.message
end
end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index b22686d1d8..e3227103d6 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Remove deprecated Erubis ERB handler.
+
+ *Rafael Mendonça França*
+
* Remove default `alt` text generation.
Fixes #30096
diff --git a/actionview/Rakefile b/actionview/Rakefile
index 20dfa4e114..9e5ef35334 100644
--- a/actionview/Rakefile
+++ b/actionview/Rakefile
@@ -45,8 +45,8 @@ namespace :test do
end
end
- system("npm run lint && phantomjs ../ci/phantomjs.js http://localhost:4567/")
- status = $?.to_i
+ system("npm run lint && bundle exec ruby ../ci/qunit-selenium-runner.rb http://localhost:4567/")
+ status = $?.exitstatus
ensure
Process.kill("KILL", pid) if pid
FileUtils.rm_rf("log")
diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md
index f321b9f720..8198011b02 100644
--- a/actionview/app/assets/javascripts/README.md
+++ b/actionview/app/assets/javascripts/README.md
@@ -1,5 +1,4 @@
-Ruby on Rails unobtrusive scripting adapter.
-========================================
+# Ruby on Rails unobtrusive scripting adapter
This unobtrusive scripting support file is developed for the Ruby on Rails framework, but is not strictly tied to any specific backend. You can drop this into any application to:
@@ -8,51 +7,47 @@ This unobtrusive scripting support file is developed for the Ruby on Rails frame
- make forms or hyperlinks submit data asynchronously with Ajax;
- have submit buttons become automatically disabled on form submit to prevent double-clicking.
-These features are achieved by adding certain ["data" attributes][data] to your HTML markup. In Rails, they are added by the framework's template helpers.
+These features are achieved by adding certain [`data` attributes][data] to your HTML markup. In Rails, they are added by the framework's template helpers.
-Requirements
-------------
+## Optional prerequisites
-- HTML5 doctype (optional).
+Note that the `data` attributes this library adds are a feature of HTML5. If you're not targeting HTML5, these attributes may make your HTML to fail [validation][validator]. However, this shouldn't create any issues for web browsers or other user agents.
-If you don't use HTML5, adding "data" attributes to your HTML4 or XHTML pages might make them fail [W3C markup validation][validator]. However, this shouldn't create any issues for web browsers or other user agents.
+## Installation
-Installation using npm
-------------
+### NPM
-Run `npm install rails-ujs --save` to install the rails-ujs package.
+ npm install rails-ujs --save
+
+### Yarn
+
+ yarn add rails-ujs
-Installation using Yarn
-------------
+## Usage
-Run `yarn add rails-ujs` to install the rails-ujs package.
+### Asset pipeline
-Usage
-------------
-
-Require `rails-ujs` in your application.js manifest.
+In a conventional Rails application that uses the asset pipeline, require `rails-ujs` in your `application.js` manifest:
```javascript
//= require rails-ujs
```
-Usage with yarn
-------------
+### ES2015+
-When using with the Webpacker gem or your preferred JavaScript bundler, just
-add the following to your main JS file and compile.
+If you're using the Webpacker gem or some other JavaScript bundler, add the following to your main JS file:
```javascript
import Rails from 'rails-ujs';
Rails.start()
```
-How to run tests
-------------
+## How to run tests
Run `bundle exec rake ujs:server` first, and then run the web tests by visiting http://localhost:4567 in your browser.
## License
+
rails-ujs is released under the [MIT License](MIT-LICENSE).
[data]: http://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes "Embedding custom non-visible data with the data-* attributes"
diff --git a/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
index 852587042c..b3448dabac 100644
--- a/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/features/remote.coffee
@@ -62,7 +62,7 @@ Rails.handleRemote = (e) ->
fire(element, 'ajax:send', [xhr])
else
fire(element, 'ajax:stopped')
- xhr.abort()
+ return false
success: (args...) -> fire(element, 'ajax:success', args)
error: (args...) -> fire(element, 'ajax:error', args)
complete: (args...) -> fire(element, 'ajax:complete', args)
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
index a653d3af3d..4d7848e162 100644
--- a/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
@@ -20,13 +20,12 @@ Rails.ajax = (options) ->
else
options.error?(response, xhr.statusText, xhr)
options.complete?(xhr, xhr.statusText)
- # Call beforeSend hook
- options.beforeSend?(xhr, options)
- # Send the request
+
+ unless options.beforeSend?(xhr, options)
+ return false
+
if xhr.readyState is XMLHttpRequest.OPENED
xhr.send(options.data)
- else
- fire(document, 'ajaxStop') # to be compatible with jQuery.ajax
prepareOptions = (options) ->
options.url = options.url or location.href
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee
index 6bef618147..3d3c5bb330 100644
--- a/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/dom.coffee
@@ -5,6 +5,13 @@ m = Element.prototype.matches or
Element.prototype.oMatchesSelector or
Element.prototype.webkitMatchesSelector
+# Checks if the given native dom element matches the selector
+# element::
+# native DOM element
+# selector::
+# css selector string or
+# a javascript object with `selector` and `exclude` properties
+# Examples: "form", { selector: "form", exclude: "form[data-remote='true']"}
Rails.matches = (element, selector) ->
if selector.exclude?
m.call(element, selector.selector) and not m.call(element, selector.exclude)
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
index 8d3ff007ea..a2135c9851 100644
--- a/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
@@ -14,6 +14,13 @@ if typeof CustomEvent isnt 'function'
CustomEvent.prototype = window.Event.prototype
# Triggers a custom event on an element and returns false if the event result is false
+# obj::
+# a native DOM element
+# name::
+# string that corrspends to the event you want to trigger
+# e.g. 'click', 'submit'
+# data::
+# data you want to pass when you dispatch an event
fire = Rails.fire = (obj, name, data) ->
event = new CustomEvent(
name,
@@ -31,6 +38,17 @@ Rails.stopEverything = (e) ->
e.stopPropagation()
e.stopImmediatePropagation()
+# Delegates events
+# to a specified parent `element`, which fires event `handler`
+# for the specified `selector` when an event of `eventType` is triggered
+# element::
+# parent element that will listen for events e.g. document
+# selector::
+# css selector; or an object that has `selector` and `exclude` properties (see: Rails.matches)
+# eventType::
+# string representing the event e.g. 'submit', 'click'
+# handler::
+# the event handler to be called
Rails.delegate = (element, selector, eventType, handler) ->
element.addEventListener eventType, (e) ->
target = e.target
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index 3c8a8488a5..2069ea042a 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -25,7 +25,7 @@
require "active_support"
require "active_support/rails"
-require_relative "action_view/version"
+require "action_view/version"
module ActionView
extend ActiveSupport::Autoload
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index 637c8e7708..d41fe2a608 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -3,11 +3,11 @@
require "active_support/core_ext/module/attr_internal"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/ordered_options"
-require_relative "log_subscriber"
-require_relative "helpers"
-require_relative "context"
-require_relative "template"
-require_relative "lookup_context"
+require "action_view/log_subscriber"
+require "action_view/helpers"
+require "action_view/context"
+require "action_view/template"
+require "action_view/lookup_context"
module ActionView #:nodoc:
# = Action View Base
diff --git a/actionview/lib/action_view/context.rb b/actionview/lib/action_view/context.rb
index e1b02fbde4..665a9e3171 100644
--- a/actionview/lib/action_view/context.rb
+++ b/actionview/lib/action_view/context.rb
@@ -19,7 +19,6 @@ module ActionView
attr_accessor :output_buffer, :view_flow
# Prepares the context by setting the appropriate instance variables.
- # :api: plugin
def _prepare_context
@view_flow = OutputFlow.new
@output_buffer = nil
@@ -29,7 +28,6 @@ module ActionView
# Encapsulates the interaction with the view flow so it
# returns the correct buffer on +yield+. This is usually
# overwritten by helpers to add more behavior.
- # :api: plugin
def _layout_for(name = nil)
name ||= :layout
view_flow.get(name).html_safe
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index 02bd0545c2..182f6e2eef 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "concurrent/map"
-require_relative "path_set"
+require "action_view/path_set"
module ActionView
class DependencyTracker # :nodoc:
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index e404ebb6b6..dfd62bdcfd 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "concurrent/map"
-require_relative "dependency_tracker"
+require "action_view/dependency_tracker"
require "monitor"
module ActionView
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index bc2713d13e..e362f13798 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -2,8 +2,8 @@
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/hash/keys"
-require_relative "asset_url_helper"
-require_relative "tag_helper"
+require "action_view/helpers/asset_url_helper"
+require "action_view/helpers/tag_helper"
module ActionView
# = Action View Asset Tag Helpers
@@ -37,6 +37,9 @@ module ActionView
# When the Asset Pipeline is enabled, you can pass the name of your manifest as
# source, and include other JavaScript or CoffeeScript files inside the manifest.
#
+ # If the server supports Early Hints header links for these assets will be
+ # automatically pushed.
+ #
# ==== Options
#
# When the last parameter is a hash you can add HTML attributes using that
@@ -77,12 +80,20 @@ module ActionView
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
- sources.uniq.map { |source|
+ early_hints_links = []
+
+ sources_tags = sources.uniq.map { |source|
+ href = path_to_javascript(source, path_options)
+ early_hints_links << "<#{href}>; rel=preload; as=script"
tag_options = {
- "src" => path_to_javascript(source, path_options)
+ "src" => href
}.merge!(options)
content_tag("script".freeze, "", tag_options)
}.join("\n").html_safe
+
+ request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request)
+
+ sources_tags
end
# Returns a stylesheet link tag for the sources specified as arguments. If
@@ -92,6 +103,9 @@ module ActionView
# to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
# apply to all media types.
#
+ # If the server supports Early Hints header links for these assets will be
+ # automatically pushed.
+ #
# stylesheet_link_tag "style"
# # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
#
@@ -113,14 +127,22 @@ module ActionView
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
- sources.uniq.map { |source|
+ early_hints_links = []
+
+ sources_tags = sources.uniq.map { |source|
+ href = path_to_stylesheet(source, path_options)
+ early_hints_links << "<#{href}>; rel=preload; as=stylesheet"
tag_options = {
"rel" => "stylesheet",
"media" => "screen",
- "href" => path_to_stylesheet(source, path_options)
+ "href" => href
}.merge!(options)
tag(:link, tag_options)
}.join("\n").html_safe
+
+ request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request)
+
+ sources_tags
end
# Returns a link tag that browsers and feed readers can use to auto-detect
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 642bd0fec6..09040ccbc4 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "date"
-require_relative "tag_helper"
+require "action_view/helpers/tag_helper"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/date/conversions"
require "active_support/core_ext/hash/slice"
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 6702c65ccb..6d2ace8cf8 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1,12 +1,12 @@
# frozen_string_literal: true
require "cgi"
-require_relative "date_helper"
-require_relative "tag_helper"
-require_relative "form_tag_helper"
-require_relative "active_model_helper"
-require_relative "../model_naming"
-require_relative "../record_identifier"
+require "action_view/helpers/date_helper"
+require "action_view/helpers/tag_helper"
+require "action_view/helpers/form_tag_helper"
+require "action_view/helpers/active_model_helper"
+require "action_view/model_naming"
+require "action_view/record_identifier"
require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/hash/slice"
require "active_support/core_ext/string/output_safety"
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 1517abfad0..02a44477c1 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -2,7 +2,7 @@
require "cgi"
require "erb"
-require_relative "form_helper"
+require "action_view/helpers/form_helper"
require "active_support/core_ext/string/output_safety"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/array/wrap"
@@ -279,17 +279,17 @@ module ActionView
# Finally, this method supports a <tt>:default</tt> option, which selects
# a default ActiveSupport::TimeZone if the object's time zone is +nil+.
#
- # time_zone_select( "user", "time_zone", nil, include_blank: true)
+ # time_zone_select("user", "time_zone", nil, include_blank: true)
#
- # time_zone_select( "user", "time_zone", nil, default: "Pacific Time (US & Canada)" )
+ # time_zone_select("user", "time_zone", nil, default: "Pacific Time (US & Canada)")
#
- # time_zone_select( "user", 'time_zone', ActiveSupport::TimeZone.us_zones, default: "Pacific Time (US & Canada)")
+ # time_zone_select("user", 'time_zone', ActiveSupport::TimeZone.us_zones, default: "Pacific Time (US & Canada)")
#
- # time_zone_select( "user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ])
+ # time_zone_select("user", 'time_zone', [ ActiveSupport::TimeZone['Alaska'], ActiveSupport::TimeZone['Hawaii'] ])
#
- # time_zone_select( "user", 'time_zone', /Australia/)
+ # time_zone_select("user", 'time_zone', /Australia/)
#
- # time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, model: ActiveSupport::TimeZone)
+ # time_zone_select("user", "time_zone", ActiveSupport::TimeZone.all.sort, model: ActiveSupport::TimeZone)
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render
end
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index 31a1f8be8c..80d53c2c6e 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "cgi"
-require_relative "tag_helper"
+require "action_view/helpers/tag_helper"
require "active_support/core_ext/string/output_safety"
require "active_support/core_ext/module/attribute_accessors"
@@ -394,7 +394,7 @@ module ActionView
# # => <input checked="checked" id="receive_updates_no" name="receive_updates" type="radio" value="no" />
#
# radio_button_tag 'time_slot', "3:00 p.m.", false, disabled: true
- # # => <input disabled="disabled" id="time_slot_300_pm" name="time_slot" type="radio" value="3:00 p.m." />
+ # # => <input disabled="disabled" id="time_slot_3:00_p.m." name="time_slot" type="radio" value="3:00 p.m." />
#
# radio_button_tag 'color', "green", true, class: "color_input"
# # => <input checked="checked" class="color_input" id="color_green" name="color" type="radio" value="green" />
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index 11eefe0ee0..dd2cd57ac3 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "tag_helper"
+require "action_view/helpers/tag_helper"
module ActionView
module Helpers #:nodoc:
diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb
index a64d7e396e..a6cec3f69c 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -166,7 +166,7 @@ module ActionView
# This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
# from 1.4.3.
#
- # tag.div data: { city_state: %w( Chigaco IL ) }
+ # tag.div data: { city_state: %w( Chicago IL ) }
# # => <div data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]"></div>
#
# The generated attributes are escaped by default. This can be disabled using
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index 922d4c5390..8934a9894c 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -35,7 +35,7 @@ module ActionView
private
- def value(object)
+ def value
if @allow_method_names_outside_object
object.public_send @method_name if object && object.respond_to?(@method_name)
else
@@ -43,19 +43,19 @@ module ActionView
end
end
- def value_before_type_cast(object)
+ def value_before_type_cast
unless object.nil?
method_before_type_cast = @method_name + "_before_type_cast"
- if value_came_from_user?(object) && object.respond_to?(method_before_type_cast)
+ if value_came_from_user? && object.respond_to?(method_before_type_cast)
object.public_send(method_before_type_cast)
else
- value(object)
+ value
end
end
end
- def value_came_from_user?(object)
+ def value_came_from_user?
method_name = "#{@method_name}_came_from_user?"
!object.respond_to?(method_name) || object.public_send(method_name)
end
@@ -150,7 +150,7 @@ module ActionView
options[:include_blank] ||= true unless options[:prompt]
end
- value = options.fetch(:selected) { value(object) }
+ value = options.fetch(:selected) { value() }
select = content_tag("select", add_options(option_tags, options, value), html_options)
if html_options["multiple"] && options.fetch(:include_hidden, true)
diff --git a/actionview/lib/action_view/helpers/tags/check_box.rb b/actionview/lib/action_view/helpers/tags/check_box.rb
index c7d8cd0e97..4327e07cae 100644
--- a/actionview/lib/action_view/helpers/tags/check_box.rb
+++ b/actionview/lib/action_view/helpers/tags/check_box.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "checkable"
+require "action_view/helpers/tags/checkable"
module ActionView
module Helpers
@@ -18,7 +18,7 @@ module ActionView
options = @options.stringify_keys
options["type"] = "checkbox"
options["value"] = @checked_value
- options["checked"] = "checked" if input_checked?(object, options)
+ options["checked"] = "checked" if input_checked?(options)
if options["multiple"]
add_default_name_and_id_for_value(@checked_value, options)
diff --git a/actionview/lib/action_view/helpers/tags/checkable.rb b/actionview/lib/action_view/helpers/tags/checkable.rb
index f2f4a655a3..776fefe778 100644
--- a/actionview/lib/action_view/helpers/tags/checkable.rb
+++ b/actionview/lib/action_view/helpers/tags/checkable.rb
@@ -4,12 +4,12 @@ module ActionView
module Helpers
module Tags # :nodoc:
module Checkable # :nodoc:
- def input_checked?(object, options)
+ def input_checked?(options)
if options.has_key?("checked")
checked = options.delete "checked"
checked == true || checked == "checked"
else
- checked?(value(object))
+ checked?(value)
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
index 91c1135d20..455442178e 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "collection_helpers"
+require "action_view/helpers/tags/collection_helpers"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
index 0b0482f74e..16d37134e5 100644
--- a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "collection_helpers"
+require "action_view/helpers/tags/collection_helpers"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/collection_select.rb b/actionview/lib/action_view/helpers/tags/collection_select.rb
index ef1d4c8d1e..6a3af1b256 100644
--- a/actionview/lib/action_view/helpers/tags/collection_select.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_select.rb
@@ -15,7 +15,7 @@ module ActionView
def render
option_tags_options = {
- selected: @options.fetch(:selected) { value(@object) },
+ selected: @options.fetch(:selected) { value },
disabled: @options[:disabled]
}
diff --git a/actionview/lib/action_view/helpers/tags/color_field.rb b/actionview/lib/action_view/helpers/tags/color_field.rb
index 8e698ef922..c5f0bb6bbb 100644
--- a/actionview/lib/action_view/helpers/tags/color_field.rb
+++ b/actionview/lib/action_view/helpers/tags/color_field.rb
@@ -6,7 +6,7 @@ module ActionView
class ColorField < TextField # :nodoc:
def render
options = @options.stringify_keys
- options["value"] ||= validate_color_string(value(object))
+ options["value"] ||= validate_color_string(value)
@options = options
super
end
diff --git a/actionview/lib/action_view/helpers/tags/date_select.rb b/actionview/lib/action_view/helpers/tags/date_select.rb
index 13c0515974..fe4e3914d7 100644
--- a/actionview/lib/action_view/helpers/tags/date_select.rb
+++ b/actionview/lib/action_view/helpers/tags/date_select.rb
@@ -29,7 +29,7 @@ module ActionView
end
def datetime_selector(options, html_options)
- datetime = options.fetch(:selected) { value(object) || default_datetime(options) }
+ datetime = options.fetch(:selected) { value || default_datetime(options) }
@auto_index ||= nil
options = options.dup
diff --git a/actionview/lib/action_view/helpers/tags/datetime_field.rb b/actionview/lib/action_view/helpers/tags/datetime_field.rb
index 0556566130..5d9b639b1b 100644
--- a/actionview/lib/action_view/helpers/tags/datetime_field.rb
+++ b/actionview/lib/action_view/helpers/tags/datetime_field.rb
@@ -6,7 +6,7 @@ module ActionView
class DatetimeField < TextField # :nodoc:
def render
options = @options.stringify_keys
- options["value"] ||= format_date(value(object))
+ options["value"] ||= format_date(value)
options["min"] = format_date(datetime_value(options["min"]))
options["max"] = format_date(datetime_value(options["max"]))
@options = options
diff --git a/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb
index 971db8e85d..f24cb4beea 100644
--- a/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb
+++ b/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb
@@ -17,7 +17,7 @@ module ActionView
def render
option_tags_options = {
- selected: @options.fetch(:selected) { value(@object) },
+ selected: @options.fetch(:selected) { value },
disabled: @options[:disabled]
}
diff --git a/actionview/lib/action_view/helpers/tags/radio_button.rb b/actionview/lib/action_view/helpers/tags/radio_button.rb
index 9e4f1c9e4b..621db2b1b5 100644
--- a/actionview/lib/action_view/helpers/tags/radio_button.rb
+++ b/actionview/lib/action_view/helpers/tags/radio_button.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "checkable"
+require "action_view/helpers/tags/checkable"
module ActionView
module Helpers
@@ -17,7 +17,7 @@ module ActionView
options = @options.stringify_keys
options["type"] = "radio"
options["value"] = @tag_value
- options["checked"] = "checked" if input_checked?(object, options)
+ options["checked"] = "checked" if input_checked?(options)
add_default_name_and_id_for_value(@tag_value, options)
tag("input", options)
end
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index 0de4139101..345484ba92 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -15,7 +15,7 @@ module ActionView
def render
option_tags_options = {
- selected: @options.fetch(:selected) { value(@object) },
+ selected: @options.fetch(:selected) { value },
disabled: @options[:disabled]
}
diff --git a/actionview/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb
index d8460a4be4..4519082ff6 100644
--- a/actionview/lib/action_view/helpers/tags/text_area.rb
+++ b/actionview/lib/action_view/helpers/tags/text_area.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "placeholderable"
+require "action_view/helpers/tags/placeholderable"
module ActionView
module Helpers
@@ -16,7 +16,7 @@ module ActionView
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
end
- content_tag("textarea", options.delete("value") { value_before_type_cast(object) }, options)
+ content_tag("textarea", options.delete("value") { value_before_type_cast }, options)
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb
index e4c5a49069..d92967e212 100644
--- a/actionview/lib/action_view/helpers/tags/text_field.rb
+++ b/actionview/lib/action_view/helpers/tags/text_field.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "placeholderable"
+require "action_view/helpers/tags/placeholderable"
module ActionView
module Helpers
@@ -12,7 +12,7 @@ module ActionView
options = @options.stringify_keys
options["size"] = options["maxlength"] unless options.key?("size")
options["type"] ||= field_type
- options["value"] = options.fetch("value") { value_before_type_cast(object) } unless field_type == "file"
+ options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
add_default_name_and_id(options)
tag("input", options)
end
diff --git a/actionview/lib/action_view/helpers/tags/time_zone_select.rb b/actionview/lib/action_view/helpers/tags/time_zone_select.rb
index 3b6bcacce0..1d06096096 100644
--- a/actionview/lib/action_view/helpers/tags/time_zone_select.rb
+++ b/actionview/lib/action_view/helpers/tags/time_zone_select.rb
@@ -13,7 +13,7 @@ module ActionView
def render
select_content_tag(
- time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
+ time_zone_options_for_select(value || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
)
end
end
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index e663892592..1860bc4732 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "tag_helper"
+require "action_view/helpers/tag_helper"
require "active_support/core_ext/string/access"
require "i18n/exceptions"
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 2d5aac6dc7..efda549c0d 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "javascript_helper"
+require "action_view/helpers/javascript_helper"
require "active_support/core_ext/array/access"
require "active_support/core_ext/hash/keys"
require "active_support/core_ext/string/output_safety"
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index b11ef6e133..3e6d352c15 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "rendering"
+require "action_view/rendering"
require "active_support/core_ext/module/redefine_method"
module ActionView
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index acc0f57f1d..0e56eca35c 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -3,7 +3,7 @@
require "concurrent/map"
require "active_support/core_ext/module/remove_method"
require "active_support/core_ext/module/attribute_accessors"
-require_relative "template/resolver"
+require "action_view/template/resolver"
module ActionView
# = Action View Lookup Context
diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb
index b34a793c89..1310a1ce0a 100644
--- a/actionview/lib/action_view/record_identifier.rb
+++ b/actionview/lib/action_view/record_identifier.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support/core_ext/module"
-require_relative "model_naming"
+require "action_view/model_naming"
module ActionView
# RecordIdentifier encapsulates methods used by various ActionView helpers
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index beb0a18b65..5b40af4f2f 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "concurrent/map"
-require_relative "partial_renderer/collection_caching"
+require "action_view/renderer/partial_renderer/collection_caching"
module ActionView
class PartialIteration
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 2648f9153f..4e5fdfbb2d 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "view_paths"
+require "action_view/view_paths"
module ActionView
# This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
@@ -75,8 +75,7 @@ module ActionView
end
# Returns an object that is able to render templates.
- # :api: private
- def view_renderer
+ def view_renderer # :nodoc:
@_view_renderer ||= ActionView::Renderer.new(lookup_context)
end
@@ -92,7 +91,6 @@ module ActionView
private
# Find and render a template based on the options given.
- # :api: private
def _render_template(options)
variant = options.delete(:variant)
assigns = options.delete(:assigns)
@@ -114,7 +112,6 @@ module ActionView
# Normalize args by converting render "foo" to render :action => "foo" and
# render "foo/bar" to render :template => "foo/bar".
- # :api: private
def _normalize_args(action = nil, options = {})
options = super(action, options)
case action
@@ -137,7 +134,6 @@ module ActionView
end
# Normalize options.
- # :api: private
def _normalize_options(options)
options = super(options)
if options[:partial] == true
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 1d0797276f..0c4bb73acb 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -330,7 +330,7 @@ module ActionView
locals = @locals - Module::RUBY_RESERVED_KEYWORDS
locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
- # Double assign to suppress the dreaded 'assigned but unused variable' warning
+ # Assign for the same variable is to suppress unused variable warning
locals.each_with_object("".dup) { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" }
end
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index c41de62e52..b7b749f9da 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -3,11 +3,8 @@
module ActionView
class Template
module Handlers
- autoload :Erubis, "action_view/template/handlers/erb/deprecated_erubis"
-
class ERB
autoload :Erubi, "action_view/template/handlers/erb/erubi"
- autoload :Erubis, "action_view/template/handlers/erb/erubis"
# Specify trim mode for the ERB compiler. Defaults to '-'.
# See ERB documentation for suitable values.
diff --git a/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb b/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb
deleted file mode 100644
index 00c7d7cc7d..0000000000
--- a/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-::ActiveSupport::Deprecation.warn("ActionView::Template::Handlers::Erubis is deprecated and will be removed from Rails 5.2. Switch to ActionView::Template::Handlers::ERB::Erubi instead.")
-
-module ActionView
- class Template
- module Handlers
- Erubis = ERB::Erubis
- end
- end
-end
diff --git a/actionview/lib/action_view/template/handlers/erb/erubis.rb b/actionview/lib/action_view/template/handlers/erb/erubis.rb
deleted file mode 100644
index dc3bf1ff27..0000000000
--- a/actionview/lib/action_view/template/handlers/erb/erubis.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-# frozen_string_literal: true
-
-gem "erubis"
-require "erubis"
-
-module ActionView
- class Template
- module Handlers
- class ERB
- class Erubis < ::Erubis::Eruby
- # :nodoc: all
- def add_preamble(src)
- @newline_pending = 0
- src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
- end
-
- def add_text(src, text)
- return if text.empty?
-
- if text == "\n"
- @newline_pending += 1
- else
- src << "@output_buffer.safe_append='"
- src << "\n" * @newline_pending if @newline_pending > 0
- src << escape_text(text)
- src << "'.freeze;"
-
- @newline_pending = 0
- end
- end
-
- # Erubis toggles <%= and <%== behavior when escaping is enabled.
- # We override to always treat <%== as escaped.
- def add_expr(src, code, indicator)
- case indicator
- when "=="
- add_expr_escaped(src, code)
- else
- super
- end
- end
-
- BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
-
- def add_expr_literal(src, code)
- flush_newline_if_pending(src)
- if BLOCK_EXPR.match?(code)
- src << "@output_buffer.append= " << code
- else
- src << "@output_buffer.append=(" << code << ");"
- end
- end
-
- def add_expr_escaped(src, code)
- flush_newline_if_pending(src)
- if BLOCK_EXPR.match?(code)
- src << "@output_buffer.safe_expr_append= " << code
- else
- src << "@output_buffer.safe_expr_append=(" << code << ");"
- end
- end
-
- def add_stmt(src, code)
- flush_newline_if_pending(src)
- super
- end
-
- def add_postamble(src)
- flush_newline_if_pending(src)
- src << "@output_buffer.to_s"
- end
-
- def flush_newline_if_pending(src)
- if @newline_pending > 0
- src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
- @newline_pending = 0
- end
- end
- end
- end
- end
- end
-end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index a58d375293..5a86f10973 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -3,7 +3,7 @@
require "pathname"
require "active_support/core_ext/class"
require "active_support/core_ext/module/attribute_accessors"
-require_relative "../template"
+require "action_view/template"
require "thread"
require "concurrent/map"
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 93be2be2d1..e1cbae5845 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -270,7 +270,7 @@ module ActionView
begin
routes = @controller.respond_to?(:_routes) && @controller._routes
rescue
- # Dont call routes, if there is an error on _routes call
+ # Don't call routes, if there is an error on _routes call
end
if routes &&
@@ -286,7 +286,7 @@ module ActionView
begin
routes = @controller.respond_to?(:_routes) && @controller._routes
rescue
- # Dont call routes, if there is an error on _routes call
+ # Don't call routes, if there is an error on _routes call
end
routes &&
diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb
index 92fdb24a5e..68186c3bf8 100644
--- a/actionview/lib/action_view/testing/resolvers.rb
+++ b/actionview/lib/action_view/testing/resolvers.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../template/resolver"
+require "action_view/template/resolver"
module ActionView #:nodoc:
# Use FixtureResolver in your tests to simulate the presence of files on the
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index c98270bd12..f20a66c2d2 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -26,14 +26,6 @@ require "active_record"
require "pp" # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
-module Rails
- class << self
- def env
- @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test")
- end
- end
-end
-
ActiveSupport::Dependencies.hook!
Thread.abort_on_exception = true
@@ -110,12 +102,6 @@ module ActionDispatch
end
end
-module ActiveSupport
- class TestCase
- include ActionDispatch::DrawOnce
- end
-end
-
class RoutedRackApp
attr_reader :routes
@@ -162,29 +148,6 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
self.app = build_app
- # Stub Rails dispatcher so it does not get controller references and
- # simply return the controller#action as Rack::Body.
- class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
- private
- def controller_reference(controller_param)
- controller_param
- end
-
- def dispatch(controller, action, env)
- [200, { "Content-Type" => "text/html" }, ["#{controller}##{action}"]]
- end
- end
-
- def self.stub_controllers
- old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher
- ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
- ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher }
- yield ActionDispatch::Routing::RouteSet.new
- ensure
- ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
- ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
- end
-
def with_routing(&block)
temporary_routes = ActionDispatch::Routing::RouteSet.new
old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes)
@@ -196,21 +159,6 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
self.class.app = old_app
silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) }
end
-
- def with_autoload_path(path)
- path = File.join(File.expand_path("fixtures", __dir__), path)
- if ActiveSupport::Dependencies.autoload_paths.include?(path)
- yield
- else
- begin
- ActiveSupport::Dependencies.autoload_paths << path
- yield
- ensure
- ActiveSupport::Dependencies.autoload_paths.reject! { |p| p == path }
- ActiveSupport::Dependencies.clear
- end
- end
- end
end
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)
@@ -274,6 +222,7 @@ module ActionDispatch
end
class ActiveSupport::TestCase
+ include ActionDispatch::DrawOnce
include ActiveSupport::Testing::MethodCallAssertions
# Skips the current run on Rubinius using Minitest::Assertions#skip
diff --git a/actionview/test/activerecord/render_partial_with_record_identification_test.rb b/actionview/test/activerecord/render_partial_with_record_identification_test.rb
index 367d2c3174..3a698fa42e 100644
--- a/actionview/test/activerecord/render_partial_with_record_identification_test.rb
+++ b/actionview/test/activerecord/render_partial_with_record_identification_test.rb
@@ -17,21 +17,11 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
render partial: Reply.base
end
- def render_with_has_many_through_association
- @developer = Developer.first
- render partial: @developer.topics
- end
-
def render_with_has_one_association
@company = Company.find(1)
render partial: @company.mascot
end
- def render_with_belongs_to_association
- @reply = Reply.find(1)
- render partial: @reply.topic
- end
-
def render_with_record
@developer = Developer.first
render partial: @developer
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 6645839f0e..7475f5cc3c 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -19,6 +19,7 @@ class AssetTagHelperTest < ActionView::TestCase
def ssl?() false end
def host_with_port() "localhost" end
def base_url() "http://www.example.com" end
+ def send_early_hints(links) end
end.new
@controller.request = @request
@@ -305,6 +306,24 @@ class AssetTagHelperTest < ActionView::TestCase
%(font_path("font.ttf?123")) => %(/fonts/font.ttf?123)
}
+ FontUrlToTag = {
+ %(font_url("font.eot")) => %(http://www.example.com/fonts/font.eot),
+ %(font_url("font.eot#iefix")) => %(http://www.example.com/fonts/font.eot#iefix),
+ %(font_url("font.woff")) => %(http://www.example.com/fonts/font.woff),
+ %(font_url("font.ttf")) => %(http://www.example.com/fonts/font.ttf),
+ %(font_url("font.ttf?123")) => %(http://www.example.com/fonts/font.ttf?123),
+ %(font_url("font.ttf", host: "http://assets.example.com")) => %(http://assets.example.com/fonts/font.ttf)
+ }
+
+ UrlToFontToTag = {
+ %(url_to_font("font.eot")) => %(http://www.example.com/fonts/font.eot),
+ %(url_to_font("font.eot#iefix")) => %(http://www.example.com/fonts/font.eot#iefix),
+ %(url_to_font("font.woff")) => %(http://www.example.com/fonts/font.woff),
+ %(url_to_font("font.ttf")) => %(http://www.example.com/fonts/font.ttf),
+ %(url_to_font("font.ttf?123")) => %(http://www.example.com/fonts/font.ttf?123),
+ %(url_to_font("font.ttf", host: "http://assets.example.com")) => %(http://assets.example.com/fonts/font.ttf)
+ }
+
def test_autodiscovery_link_tag_with_unknown_type_but_not_pass_type_option_key
assert_raise(ArgumentError) do
auto_discovery_link_tag(:xml)
@@ -547,6 +566,14 @@ class AssetTagHelperTest < ActionView::TestCase
FontPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_font_url
+ FontUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_font_alias_for_font_url
+ UrlToFontToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_video_audio_tag_does_not_modify_options
options = { autoplay: true }
video_tag("video", options)
@@ -627,7 +654,9 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
@controller = BasicController.new
@controller.config.relative_url_root = "/collaboration/hieraki"
- @request = Struct.new(:protocol, :base_url).new("gopher://", "gopher://www.example.com")
+ @request = Struct.new(:protocol, :base_url) do
+ def send_early_hints(links); end
+ end.new("gopher://", "gopher://www.example.com")
@controller.request = @request
end
diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb
index 928b1ac7dd..1bfa39a319 100644
--- a/actionview/test/template/digestor_test.rb
+++ b/actionview/test/template/digestor_test.rb
@@ -4,17 +4,6 @@ require "abstract_unit"
require "fileutils"
require "action_view/dependency_tracker"
-class FixtureTemplate
- attr_reader :source, :handler
-
- def initialize(template_path)
- @source = File.read(template_path)
- @handler = ActionView::Template.handler_for_extension(:erb)
- rescue Errno::ENOENT
- raise ActionView::MissingTemplate.new([], "", [], true, [])
- end
-end
-
class FixtureFinder < ActionView::LookupContext
FIXTURES_DIR = File.expand_path("../fixtures/digestor", __dir__)
diff --git a/actionview/test/template/erb/deprecated_erubis_implementation_test.rb b/actionview/test/template/erb/deprecated_erubis_implementation_test.rb
deleted file mode 100644
index ea088e7cfc..0000000000
--- a/actionview/test/template/erb/deprecated_erubis_implementation_test.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-require "abstract_unit"
-
-module ERBTest
- class DeprecatedErubisImplementationTest < ActionView::TestCase
- test "Erubis implementation is deprecated" do
- assert_deprecated "ActionView::Template::Handlers::Erubis is deprecated and will be removed from Rails 5.2. Switch to ActionView::Template::Handlers::ERB::Erubi instead." do
- assert_equal "ActionView::Template::Handlers::ERB::Erubis", ActionView::Template::Handlers::Erubis.to_s
-
- assert_nothing_raised { Class.new(ActionView::Template::Handlers::Erubis) }
- end
- end
- end
-end
diff --git a/actionview/test/template/javascript_helper_test.rb b/actionview/test/template/javascript_helper_test.rb
index 4478c9f4ab..a72bc6c2fe 100644
--- a/actionview/test/template/javascript_helper_test.rb
+++ b/actionview/test/template/javascript_helper_test.rb
@@ -6,11 +6,15 @@ class JavaScriptHelperTest < ActionView::TestCase
tests ActionView::Helpers::JavaScriptHelper
attr_accessor :output_buffer
+ attr_reader :request
setup do
@old_escape_html_entities_in_json = ActiveSupport.escape_html_entities_in_json
ActiveSupport.escape_html_entities_in_json = true
@template = self
+ @request = Class.new do
+ def send_early_hints(links) end
+ end.new
end
def teardown
diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb
index c7714cf205..0e690c82cb 100644
--- a/actionview/test/template/sanitize_helper_test.rb
+++ b/actionview/test/template/sanitize_helper_test.rb
@@ -21,8 +21,8 @@ class SanitizeHelperTest < ActionView::TestCase
def test_should_sanitize_illegal_style_properties
raw = %(display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;)
- expected = %(display: block; width: 100%; height: 100%; background-color: black; background-x: center; background-y: center;)
- assert_equal expected, sanitize_css(raw)
+ expected = %r(\Adisplay:\s?block;\s?width:\s?100%;\s?height:\s?100%;\s?background-color:\s?black;\s?background-x:\s?center;\s?background-y:\s?center;\z)
+ assert_match expected, sanitize_css(raw)
end
def test_strip_tags
diff --git a/actionview/test/ujs/public/test/call-remote-callbacks.js b/actionview/test/ujs/public/test/call-remote-callbacks.js
index 707e21541d..48763f6301 100644
--- a/actionview/test/ujs/public/test/call-remote-callbacks.js
+++ b/actionview/test/ujs/public/test/call-remote-callbacks.js
@@ -12,34 +12,20 @@ module('call-remote-callbacks', {
$(document).undelegate('form[data-remote]', 'ajax:send')
$(document).undelegate('form[data-remote]', 'ajax:complete')
$(document).undelegate('form[data-remote]', 'ajax:success')
- $(document).unbind('ajaxStop')
$(document).unbind('iframe:loading')
}
})
-function start_after_submit(form) {
- form.bindNative('ajax:complete', function() {
- ok(true, 'ajax:complete')
- start()
- })
-}
-
function submit(fn) {
var form = $('form')
- start_after_submit(form)
if (fn) fn(form)
form.triggerNative('submit')
-}
-function submit_with_button(submit_button) {
- var form = $('form')
- start_after_submit(form)
-
- submit_button.triggerNative('click')
+ setTimeout(function() { start() }, 13)
}
-asyncTest('modifying form fields with "ajax:before" sends modified data in request', 4, function() {
+asyncTest('modifying form fields with "ajax:before" sends modified data in request', 3, function() {
$('form[data-remote]')
.append($('<input type="text" name="user_name" value="john">'))
.append($('<input type="text" name="removed_user_name" value="john">'))
@@ -61,7 +47,7 @@ asyncTest('modifying form fields with "ajax:before" sends modified data in reque
})
})
-asyncTest('modifying data("type") with "ajax:before" requests new dataType in request', 2, function() {
+asyncTest('modifying data("type") with "ajax:before" requests new dataType in request', 1, function() {
$('form[data-remote]').data('type', 'html')
.bindNative('ajax:before', function() {
this.setAttribute('data-type', 'xml')
@@ -74,7 +60,7 @@ asyncTest('modifying data("type") with "ajax:before" requests new dataType in re
})
})
-asyncTest('setting data("with-credentials",true) with "ajax:before" uses new setting in request', 2, function() {
+asyncTest('setting data("with-credentials",true) with "ajax:before" uses new setting in request', 1, function() {
$('form[data-remote]').data('with-credentials', false)
.bindNative('ajax:before', function() {
this.setAttribute('data-with-credentials', true)
@@ -96,14 +82,11 @@ asyncTest('stopping the "ajax:beforeSend" event aborts the request', 1, function
form.unbind('ajax:send').bindNative('ajax:send', function() {
ok(false, 'ajax:send should not run')
})
- form.unbind('ajax:complete').bindNative('ajax:complete', function() {
- ok(false, 'ajax:complete should not run')
- })
- form.bindNative('ajax:error', function(e, xhr, status, error) {
+ form.bindNative('ajax:error', function(e, response, status, xhr) {
ok(false, 'ajax:error should not run')
})
- $(document).bindNative('ajaxStop', function() {
- start()
+ form.bindNative('ajax:complete', function() {
+ ok(false, 'ajax:complete should not run')
})
})
})
@@ -188,16 +171,13 @@ asyncTest('"ajax:beforeSend" can be observed and stopped with event delegation',
form.unbind('ajax:send').bindNative('ajax:send', function() {
ok(false, 'ajax:send should not run')
})
- form.unbind('ajax:complete').bindNative('ajax:complete', function() {
+ form.bindNative('ajax:complete', function() {
ok(false, 'ajax:complete should not run')
})
- $(document).bindNative('ajaxStop', function() {
- start()
- })
})
})
-asyncTest('"ajax:beforeSend", "ajax:send", "ajax:success" and "ajax:complete" are triggered', 9, function() {
+asyncTest('"ajax:beforeSend", "ajax:send", "ajax:success" and "ajax:complete" are triggered', 8, function() {
submit(function(form) {
form.bindNative('ajax:beforeSend', function(e, xhr, settings) {
ok(xhr.setRequestHeader, 'first argument to "ajax:beforeSend" should be an XHR object')
@@ -218,25 +198,25 @@ asyncTest('"ajax:beforeSend", "ajax:send", "ajax:success" and "ajax:complete" ar
})
})
-if(window.phantom !== undefined) {
- asyncTest('"ajax:beforeSend", "ajax:send", "ajax:error" and "ajax:complete" are triggered on error', 7, function() {
- submit(function(form) {
- form.attr('action', '/error')
- form.bindNative('ajax:beforeSend', function(arg) { ok(true, 'ajax:beforeSend') })
- form.bindNative('ajax:send', function(arg) { ok(true, 'ajax:send') })
- form.bindNative('ajax:error', function(e, xhr, status, error) {
- ok(xhr.getResponseHeader, 'first argument to "ajax:error" should be an XHR object')
- equal(status, 'error', 'second argument to ajax:error should be a status string')
- // Firefox 8 returns "Forbidden " with trailing space
- equal($.trim(error), 'Forbidden', 'third argument to ajax:error should be an HTTP status response')
- // Opera returns "0" for HTTP code
- equal(xhr.status, window.opera ? 0 : 403, 'status code should be 403')
- })
+asyncTest('"ajax:beforeSend", "ajax:send", "ajax:error" and "ajax:complete" are triggered on error', 8, function() {
+ submit(function(form) {
+ form.attr('action', '/error')
+ form.bindNative('ajax:beforeSend', function(arg) { ok(true, 'ajax:beforeSend') })
+ form.bindNative('ajax:send', function(arg) { ok(true, 'ajax:send') })
+ form.bindNative('ajax:error', function(e, response, status, xhr) {
+ equal(response, '', 'first argument to ajax:error should be an HTTP status response')
+ equal(status, 'Forbidden', 'second argument to ajax:error should be a status string')
+ ok(xhr.getResponseHeader, 'third argument to "ajax:error" should be an XHR object')
+ // Opera returns "0" for HTTP code
+ equal(xhr.status, window.opera ? 0 : 403, 'status code should be 403')
+ })
+ form.bindNative('ajax:complete', function(e, xhr, status) {
+ ok(xhr.getResponseHeader, 'first argument to "ajax:complete" should be an XHR object')
+ equal(status, 'Forbidden', 'second argument to ajax:complete should be a status string')
})
})
-}
+})
-// IF THIS TEST IS FAILING, TRY INCREASING THE TIMEOUT AT THE BOTTOM TO > 100
asyncTest('binding to ajax callbacks via .delegate() triggers handlers properly', 4, function() {
$(document)
.delegate('form[data-remote]', 'ajax:beforeSend', function() {
@@ -245,29 +225,15 @@ asyncTest('binding to ajax callbacks via .delegate() triggers handlers properly'
.delegate('form[data-remote]', 'ajax:send', function() {
ok(true, 'ajax:send handler is triggered')
})
- .delegate('form[data-remote]', 'ajax:complete', function() {
- ok(true, 'ajax:complete handler is triggered')
- })
.delegate('form[data-remote]', 'ajax:success', function() {
ok(true, 'ajax:success handler is triggered')
})
- $('form[data-remote]').triggerNative('submit')
-
- setTimeout(function() {
- start()
- }, 63)
-})
-
-asyncTest('binding to ajax:send event to call jquery methods on ajax object', 2, function() {
- $('form[data-remote]')
- .bindNative('ajax:send', function(e, xhr) {
- ok(true, 'event should fire')
- equal(typeof(xhr.abort), 'function', 'event should pass jqXHR object')
- xhr.abort()
+ .delegate('form[data-remote]', 'ajax:complete', function() {
+ ok(true, 'ajax:complete handler is triggered')
})
- .triggerNative('submit')
+ $('form[data-remote]').triggerNative('submit')
- setTimeout(function() { start() }, 35)
+ setTimeout(function() { start() }, 13)
})
})()
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 77dfdefc05..0c69a5c663 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,8 +1,5 @@
-* Change logging instrumentation to log errors when a job raises an exception.
-
- Fixes #26848.
-
- *Steven Bull*
+* Support redis-rb 4.0.
+ *Jeremy Daer*
Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activejob/CHANGELOG.md) for previous changes.
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 56dab66544..1f5bd7b3e9 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -25,7 +25,7 @@
require "active_support"
require "active_support/rails"
-require_relative "active_job/version"
+require "active_job/version"
require "global_id"
module ActiveJob
diff --git a/activejob/lib/active_job/base.rb b/activejob/lib/active_job/base.rb
index 6af41260db..ae112abb2c 100644
--- a/activejob/lib/active_job/base.rb
+++ b/activejob/lib/active_job/base.rb
@@ -1,15 +1,15 @@
# frozen_string_literal: true
-require_relative "core"
-require_relative "queue_adapter"
-require_relative "queue_name"
-require_relative "queue_priority"
-require_relative "enqueuing"
-require_relative "execution"
-require_relative "callbacks"
-require_relative "exceptions"
-require_relative "logging"
-require_relative "translation"
+require "active_job/core"
+require "active_job/queue_adapter"
+require "active_job/queue_name"
+require "active_job/queue_priority"
+require "active_job/enqueuing"
+require "active_job/execution"
+require "active_job/callbacks"
+require "active_job/exceptions"
+require "active_job/logging"
+require "active_job/translation"
module ActiveJob #:nodoc:
# = Active Job
diff --git a/activejob/lib/active_job/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb
index c0d016853c..53cb98fc71 100644
--- a/activejob/lib/active_job/enqueuing.rb
+++ b/activejob/lib/active_job/enqueuing.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "arguments"
+require "active_job/arguments"
module ActiveJob
# Provides behavior for enqueuing jobs.
diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb
index dfc74deb1a..8b4a88ba6a 100644
--- a/activejob/lib/active_job/exceptions.rb
+++ b/activejob/lib/active_job/exceptions.rb
@@ -49,7 +49,7 @@ module ActiveJob
retry_job wait: determine_delay(wait), queue: queue, priority: priority
else
if block_given?
- yield self, exception
+ yield self, error
else
logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}."
raise error
diff --git a/activejob/lib/active_job/execution.rb b/activejob/lib/active_job/execution.rb
index 85e050b489..d75be376ec 100644
--- a/activejob/lib/active_job/execution.rb
+++ b/activejob/lib/active_job/execution.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support/rescuable"
-require_relative "arguments"
+require "active_job/arguments"
module ActiveJob
module Execution
diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb
index 7a3c372143..22fed0a808 100644
--- a/activejob/test/cases/exceptions_test.rb
+++ b/activejob/test/cases/exceptions_test.rb
@@ -61,7 +61,7 @@ class ExceptionsTest < ActiveJob::TestCase
test "custom handling of job that exceeds retry attempts" do
perform_enqueued_jobs do
RetryJob.perform_later "CustomCatchError", 6
- assert_equal "Dealt with a job that failed to retry in a custom way after 6 attempts", JobBuffer.last_value
+ assert_equal "Dealt with a job that failed to retry in a custom way after 6 attempts. Message: CustomCatchError", JobBuffer.last_value
end
end
diff --git a/activejob/test/jobs/retry_job.rb b/activejob/test/jobs/retry_job.rb
index a12d09779b..9aa99d9a21 100644
--- a/activejob/test/jobs/retry_job.rb
+++ b/activejob/test/jobs/retry_job.rb
@@ -17,7 +17,7 @@ class RetryJob < ActiveJob::Base
retry_on ShortWaitTenAttemptsError, wait: 1.second, attempts: 10
retry_on ExponentialWaitTenAttemptsError, wait: :exponentially_longer, attempts: 10
retry_on CustomWaitTenAttemptsError, wait: ->(executions) { executions * 2 }, attempts: 10
- retry_on(CustomCatchError) { |job, exception| JobBuffer.add("Dealt with a job that failed to retry in a custom way after #{job.arguments.second} attempts") }
+ retry_on(CustomCatchError) { |job, exception| JobBuffer.add("Dealt with a job that failed to retry in a custom way after #{job.arguments.second} attempts. Message: #{exception.message}") }
discard_on DiscardableError
def perform(raising, attempts)
diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb
index 484b476567..7d5174b957 100644
--- a/activejob/test/support/integration/adapters/resque.rb
+++ b/activejob/test/support/integration/adapters/resque.rb
@@ -3,7 +3,7 @@
module ResqueJobsManager
def setup
ActiveJob::Base.queue_adapter = :resque
- Resque.redis = Redis::Namespace.new "active_jobs_int_test", redis: Redis.connect(url: "redis://:password@127.0.0.1:6379/12", thread_safe: true)
+ Resque.redis = Redis::Namespace.new "active_jobs_int_test", redis: Redis.new(url: "redis://:password@127.0.0.1:6379/12", thread_safe: true)
Resque.logger = Rails.logger
unless can_run?
puts "Cannot run integration tests for resque. To be able to run integration tests for resque you need to install and start redis.\n"
@@ -41,11 +41,8 @@ module ResqueJobsManager
end
def can_run?
- begin
- Resque.redis.client.connect
- rescue
- return false
- end
- true
+ Resque.redis.ping == "PONG"
+ rescue
+ false
end
end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index dfd9be34a9..a97d7989be 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -25,7 +25,7 @@
require "active_support"
require "active_support/rails"
-require_relative "active_model/version"
+require "active_model/version"
module ActiveModel
extend ActiveSupport::Autoload
diff --git a/activemodel/lib/active_model/attributes.rb b/activemodel/lib/active_model/attributes.rb
new file mode 100644
index 0000000000..3e34d3b83a
--- /dev/null
+++ b/activemodel/lib/active_model/attributes.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require "active_model/type"
+
+module ActiveModel
+ module Attributes #:nodoc:
+ extend ActiveSupport::Concern
+ include ActiveModel::AttributeMethods
+ include ActiveModel::Dirty
+
+ included do
+ attribute_method_suffix "="
+ class_attribute :attribute_types, :_default_attributes, instance_accessor: false
+ self.attribute_types = {}
+ self._default_attributes = {}
+ end
+
+ module ClassMethods
+ def attribute(name, cast_type = Type::Value.new, **options)
+ self.attribute_types = attribute_types.merge(name.to_s => cast_type)
+ self._default_attributes = _default_attributes.merge(name.to_s => options[:default])
+ define_attribute_methods(name)
+ end
+
+ private
+
+ def define_method_attribute=(name)
+ safe_name = name.unpack("h*".freeze).first
+ ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
+
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def __temp__#{safe_name}=(value)
+ name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
+ write_attribute(name, value)
+ end
+ alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
+ undef_method :__temp__#{safe_name}=
+ STR
+ end
+ end
+
+ def initialize(*)
+ super
+ clear_changes_information
+ end
+
+ private
+
+ def write_attribute(attr_name, value)
+ name = if self.class.attribute_alias?(attr_name)
+ self.class.attribute_alias(attr_name).to_s
+ else
+ attr_name.to_s
+ end
+
+ cast_type = self.class.attribute_types[name]
+
+ deserialized_value = ActiveModel::Type.lookup(cast_type).cast(value)
+ attribute_will_change!(name) unless deserialized_value == attribute(name)
+ instance_variable_set("@#{name}", deserialized_value)
+ deserialized_value
+ end
+
+ def attribute(name)
+ if instance_variable_defined?("@#{name}")
+ instance_variable_get("@#{name}")
+ else
+ default = self.class._default_attributes[name]
+ default.respond_to?(:call) ? default.call : default
+ end
+ end
+
+ # Handle *= for method_missing.
+ def attribute=(attribute_name, value)
+ write_attribute(attribute_name, value)
+ end
+ end
+
+ module AttributeMethods #:nodoc:
+ AttrNames = Module.new {
+ def self.set_name_cache(name, value)
+ const_name = "ATTR_#{name}"
+ unless const_defined? const_name
+ const_set const_name, value.dup.freeze
+ end
+ end
+ }
+ end
+end
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb
index cb603b3d25..b0ed67f28e 100644
--- a/activemodel/lib/active_model/type.rb
+++ b/activemodel/lib/active_model/type.rb
@@ -1,21 +1,21 @@
# frozen_string_literal: true
-require_relative "type/helpers"
-require_relative "type/value"
-
-require_relative "type/big_integer"
-require_relative "type/binary"
-require_relative "type/boolean"
-require_relative "type/date"
-require_relative "type/date_time"
-require_relative "type/decimal"
-require_relative "type/float"
-require_relative "type/immutable_string"
-require_relative "type/integer"
-require_relative "type/string"
-require_relative "type/time"
-
-require_relative "type/registry"
+require "active_model/type/helpers"
+require "active_model/type/value"
+
+require "active_model/type/big_integer"
+require "active_model/type/binary"
+require "active_model/type/boolean"
+require "active_model/type/date"
+require "active_model/type/date_time"
+require "active_model/type/decimal"
+require "active_model/type/float"
+require "active_model/type/immutable_string"
+require "active_model/type/integer"
+require "active_model/type/string"
+require "active_model/type/time"
+
+require "active_model/type/registry"
module ActiveModel
module Type
diff --git a/activemodel/lib/active_model/type/big_integer.rb b/activemodel/lib/active_model/type/big_integer.rb
index d080fcc0f2..89e43bcc5f 100644
--- a/activemodel/lib/active_model/type/big_integer.rb
+++ b/activemodel/lib/active_model/type/big_integer.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "integer"
+require "active_model/type/integer"
module ActiveModel
module Type
diff --git a/activemodel/lib/active_model/type/helpers.rb b/activemodel/lib/active_model/type/helpers.rb
index a4e1427b64..403f0a9e6b 100644
--- a/activemodel/lib/active_model/type/helpers.rb
+++ b/activemodel/lib/active_model/type/helpers.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "helpers/accepts_multiparameter_time"
-require_relative "helpers/numeric"
-require_relative "helpers/mutable"
-require_relative "helpers/time_value"
+require "active_model/type/helpers/accepts_multiparameter_time"
+require "active_model/type/helpers/numeric"
+require "active_model/type/helpers/mutable"
+require "active_model/type/helpers/time_value"
diff --git a/activemodel/lib/active_model/type/string.rb b/activemodel/lib/active_model/type/string.rb
index 6ba2c2a3d2..36f13945b1 100644
--- a/activemodel/lib/active_model/type/string.rb
+++ b/activemodel/lib/active_model/type/string.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "immutable_string"
+require "active_model/type/immutable_string"
module ActiveModel
module Type
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index a6cbfcc813..3be7ab6ba8 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "clusivity"
+require "active_model/validations/clusivity"
module ActiveModel
module Validations
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 00e27b528a..3104e7e329 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "clusivity"
+require "active_model/validations/clusivity"
module ActiveModel
module Validations
diff --git a/activemodel/test/cases/attributes_dirty_test.rb b/activemodel/test/cases/attributes_dirty_test.rb
new file mode 100644
index 0000000000..26b0e85db3
--- /dev/null
+++ b/activemodel/test/cases/attributes_dirty_test.rb
@@ -0,0 +1,193 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "active_model/attributes"
+
+class AttributesDirtyTest < ActiveModel::TestCase
+ class DirtyModel
+ include ActiveModel::Model
+ include ActiveModel::Attributes
+ attribute :name, :string
+ attribute :color, :string
+ attribute :size, :integer
+
+ def save
+ changes_applied
+ end
+
+ def reload
+ clear_changes_information
+ end
+ end
+
+ setup do
+ @model = DirtyModel.new
+ end
+
+ test "setting attribute will result in change" do
+ assert !@model.changed?
+ assert !@model.name_changed?
+ @model.name = "Ringo"
+ assert @model.changed?
+ assert @model.name_changed?
+ end
+
+ test "list of changed attribute keys" do
+ assert_equal [], @model.changed
+ @model.name = "Paul"
+ assert_equal ["name"], @model.changed
+ end
+
+ test "changes to attribute values" do
+ assert !@model.changes["name"]
+ @model.name = "John"
+ assert_equal [nil, "John"], @model.changes["name"]
+ end
+
+ test "checking if an attribute has changed to a particular value" do
+ @model.name = "Ringo"
+ assert @model.name_changed?(from: nil, to: "Ringo")
+ assert_not @model.name_changed?(from: "Pete", to: "Ringo")
+ assert @model.name_changed?(to: "Ringo")
+ assert_not @model.name_changed?(to: "Pete")
+ assert @model.name_changed?(from: nil)
+ assert_not @model.name_changed?(from: "Pete")
+ end
+
+ test "changes accessible through both strings and symbols" do
+ @model.name = "David"
+ assert_not_nil @model.changes[:name]
+ assert_not_nil @model.changes["name"]
+ end
+
+ test "be consistent with symbols arguments after the changes are applied" do
+ @model.name = "David"
+ assert @model.attribute_changed?(:name)
+ @model.save
+ @model.name = "Rafael"
+ assert @model.attribute_changed?(:name)
+ end
+
+ test "attribute mutation" do
+ @model.instance_variable_set("@name", "Yam".dup)
+ assert !@model.name_changed?
+ @model.name.replace("Hadad")
+ assert !@model.name_changed?
+ @model.name_will_change!
+ @model.name.replace("Baal")
+ assert @model.name_changed?
+ end
+
+ test "resetting attribute" do
+ @model.name = "Bob"
+ @model.restore_name!
+ assert_nil @model.name
+ assert !@model.name_changed?
+ end
+
+ test "setting color to same value should not result in change being recorded" do
+ @model.color = "red"
+ assert @model.color_changed?
+ @model.save
+ assert !@model.color_changed?
+ assert !@model.changed?
+ @model.color = "red"
+ assert !@model.color_changed?
+ assert !@model.changed?
+ end
+
+ test "saving should reset model's changed status" do
+ @model.name = "Alf"
+ assert @model.changed?
+ @model.save
+ assert !@model.changed?
+ assert !@model.name_changed?
+ end
+
+ test "saving should preserve previous changes" do
+ @model.name = "Jericho Cane"
+ @model.save
+ assert_equal [nil, "Jericho Cane"], @model.previous_changes["name"]
+ end
+
+ test "setting new attributes should not affect previous changes" do
+ @model.name = "Jericho Cane"
+ @model.save
+ @model.name = "DudeFella ManGuy"
+ assert_equal [nil, "Jericho Cane"], @model.name_previous_change
+ end
+
+ test "saving should preserve model's previous changed status" do
+ @model.name = "Jericho Cane"
+ @model.save
+ assert @model.name_previously_changed?
+ end
+
+ test "previous value is preserved when changed after save" do
+ assert_equal({}, @model.changed_attributes)
+ @model.name = "Paul"
+ assert_equal({ "name" => nil }, @model.changed_attributes)
+
+ @model.save
+
+ @model.name = "John"
+ assert_equal({ "name" => "Paul" }, @model.changed_attributes)
+ end
+
+ test "changing the same attribute multiple times retains the correct original value" do
+ @model.name = "Otto"
+ @model.save
+ @model.name = "DudeFella ManGuy"
+ @model.name = "Mr. Manfredgensonton"
+ assert_equal ["Otto", "Mr. Manfredgensonton"], @model.name_change
+ assert_equal @model.name_was, "Otto"
+ end
+
+ test "using attribute_will_change! with a symbol" do
+ @model.size = 1
+ assert @model.size_changed?
+ end
+
+ test "reload should reset all changes" do
+ @model.name = "Dmitry"
+ @model.name_changed?
+ @model.save
+ @model.name = "Bob"
+
+ assert_equal [nil, "Dmitry"], @model.previous_changes["name"]
+ assert_equal "Dmitry", @model.changed_attributes["name"]
+
+ @model.reload
+
+ assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes
+ assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes
+ end
+
+ test "restore_attributes should restore all previous data" do
+ @model.name = "Dmitry"
+ @model.color = "Red"
+ @model.save
+ @model.name = "Bob"
+ @model.color = "White"
+
+ @model.restore_attributes
+
+ assert_not @model.changed?
+ assert_equal "Dmitry", @model.name
+ assert_equal "Red", @model.color
+ end
+
+ test "restore_attributes can restore only some attributes" do
+ @model.name = "Dmitry"
+ @model.color = "Red"
+ @model.save
+ @model.name = "Bob"
+ @model.color = "White"
+
+ @model.restore_attributes(["name"])
+
+ assert @model.changed?
+ assert_equal "Dmitry", @model.name
+ assert_equal "White", @model.color
+ end
+end
diff --git a/activemodel/test/cases/attributes_test.rb b/activemodel/test/cases/attributes_test.rb
new file mode 100644
index 0000000000..064cba40e3
--- /dev/null
+++ b/activemodel/test/cases/attributes_test.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "active_model/attributes"
+
+module ActiveModel
+ class AttributesTest < ActiveModel::TestCase
+ class ModelForAttributesTest
+ include ActiveModel::Model
+ include ActiveModel::Attributes
+
+ attribute :integer_field, :integer
+ attribute :string_field, :string
+ attribute :decimal_field, :decimal
+ attribute :string_with_default, :string, default: "default string"
+ attribute :date_field, :string, default: -> { Date.new(2016, 1, 1) }
+ attribute :boolean_field, :boolean
+ end
+
+ class ChildModelForAttributesTest < ModelForAttributesTest
+ end
+
+ class GrandchildModelForAttributesTest < ChildModelForAttributesTest
+ attribute :integer_field, :string
+ end
+
+ test "properties assignment" do
+ data = ModelForAttributesTest.new(
+ integer_field: "2.3",
+ string_field: "Rails FTW",
+ decimal_field: "12.3",
+ boolean_field: "0"
+ )
+
+ assert_equal 2, data.integer_field
+ assert_equal "Rails FTW", data.string_field
+ assert_equal BigDecimal.new("12.3"), data.decimal_field
+ assert_equal "default string", data.string_with_default
+ assert_equal Date.new(2016, 1, 1), data.date_field
+ assert_equal false, data.boolean_field
+
+ data.integer_field = 10
+ data.string_with_default = nil
+ data.boolean_field = "1"
+
+ assert_equal 10, data.integer_field
+ assert_nil data.string_with_default
+ assert_equal true, data.boolean_field
+ end
+
+ test "dirty" do
+ data = ModelForAttributesTest.new(
+ integer_field: "2.3",
+ string_field: "Rails FTW",
+ decimal_field: "12.3",
+ boolean_field: "0"
+ )
+
+ assert_equal false, data.changed?
+
+ data.integer_field = "2.1"
+
+ assert_equal false, data.changed?
+
+ data.string_with_default = "default string"
+
+ assert_equal false, data.changed?
+
+ data.integer_field = "5.1"
+
+ assert_equal true, data.changed?
+ assert_equal true, data.integer_field_changed?
+ assert_equal({ "integer_field" => [2, 5] }, data.changes)
+ end
+
+ test "nonexistent attribute" do
+ assert_raise ActiveModel::UnknownAttributeError do
+ ModelForAttributesTest.new(nonexistent: "nonexistent")
+ end
+ end
+
+ test "children inherit attributes" do
+ data = ChildModelForAttributesTest.new(integer_field: "4.4")
+
+ assert_equal 4, data.integer_field
+ end
+
+ test "children can override parents" do
+ data = GrandchildModelForAttributesTest.new(integer_field: "4.4")
+
+ assert_equal "4.4", data.integer_field
+ end
+ end
+end
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index 68dade556c..aa027c4128 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -43,48 +43,6 @@ class ConditionalValidationTest < ActiveModel::TestCase
assert_equal ["hoo 5"], t.errors["title"]
end
- def test_if_validation_using_string_true
- # When the evaluated string returns true
- ActiveSupport::Deprecation.silence do
- Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "a = 1; a == 1")
- end
- t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
- assert_equal ["hoo 5"], t.errors["title"]
- end
-
- def test_unless_validation_using_string_true
- # When the evaluated string returns true
- ActiveSupport::Deprecation.silence do
- Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "a = 1; a == 1")
- end
- t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
- assert_empty t.errors[:title]
- end
-
- def test_if_validation_using_string_false
- # When the evaluated string returns false
- ActiveSupport::Deprecation.silence do
- Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "false")
- end
- t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
- assert_empty t.errors[:title]
- end
-
- def test_unless_validation_using_string_false
- # When the evaluated string returns false
- ActiveSupport::Deprecation.silence do
- Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "false")
- end
- t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
- assert_equal ["hoo 5"], t.errors["title"]
- end
-
def test_if_validation_using_block_true
# When the block returns true
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
@@ -122,28 +80,4 @@ class ConditionalValidationTest < ActiveModel::TestCase
assert t.errors[:title].any?
assert_equal ["hoo 5"], t.errors["title"]
end
-
- # previous implementation of validates_presence_of eval'd the
- # string with the wrong binding, this regression test is to
- # ensure that it works correctly
- def test_validation_with_if_as_string
- Topic.validates_presence_of(:title)
- ActiveSupport::Deprecation.silence do
- Topic.validates_presence_of(:author_name, if: "title.to_s.match('important')")
- end
-
- t = Topic.new
- assert t.invalid?, "A topic without a title should not be valid"
- assert_empty t.errors[:author_name], "A topic without an 'important' title should not require an author"
-
- t.title = "Just a title"
- assert t.valid?, "A topic with a basic title should be valid"
-
- t.title = "A very important title"
- assert t.invalid?, "A topic with an important title, but without an author, should not be valid"
- assert t.errors[:author_name].any?, "A topic with an 'important' title should require an author"
-
- t.author_name = "Hubert J. Farnsworth"
- assert t.valid?, "A topic with an important title and author should be valid"
- end
end
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index fbe20dc000..13ef5e6a31 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -70,51 +70,15 @@ class ValidatesWithTest < ActiveModel::TestCase
assert_includes topic.errors[:base], OTHER_ERROR_MESSAGE
end
- test "with if statements that return false" do
- ActiveSupport::Deprecation.silence do
- Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 2")
- end
- topic = Topic.new
- assert topic.valid?
- end
-
- test "with if statements that return true" do
- ActiveSupport::Deprecation.silence do
- Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1")
- end
- topic = Topic.new
- assert topic.invalid?
- assert_includes topic.errors[:base], ERROR_MESSAGE
- end
-
- test "with unless statements that return true" do
- ActiveSupport::Deprecation.silence do
- Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 1")
- end
- topic = Topic.new
- assert topic.valid?
- end
-
- test "with unless statements that returns false" do
- ActiveSupport::Deprecation.silence do
- Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2")
- end
- topic = Topic.new
- assert topic.invalid?
- assert_includes topic.errors[:base], ERROR_MESSAGE
- end
-
test "passes all configuration options to the validator class" do
topic = Topic.new
validator = Minitest::Mock.new
- validator.expect(:new, validator, [{ foo: :bar, if: "1 == 1", class: Topic }])
+ validator.expect(:new, validator, [{ foo: :bar, if: :condition_is_true, class: Topic }])
validator.expect(:validate, nil, [topic])
validator.expect(:is_a?, false, [Symbol])
validator.expect(:is_a?, false, [String])
- ActiveSupport::Deprecation.silence do
- Topic.validates_with(validator, if: "1 == 1", foo: :bar)
- end
+ Topic.validates_with(validator, if: :condition_is_true, foo: :bar)
assert topic.valid?
validator.verify
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index b421fedc96..ee73004810 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,134 @@
+* Remove deprecated method `#sanitize_conditions`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated method `#scope_chain`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated configuration `.error_on_ignored_order_or_limit`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated arguments from `#verify!`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated argument `name` from `#indexes`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated method `ActiveRecord::Migrator.schema_migrations_table_name`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated method `supports_primary_key?`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated method `supports_migrations?`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated methods `initialize_schema_migrations_table` and `initialize_internal_metadata_table`.
+
+ *Rafael Mendonça França*
+
+* Raises when calling `lock!` in a dirty record.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to passing a class to `:class_name` on associations.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated argument `default` from `index_name_exists?`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated support to `quoted_id` when typecasting an Active Record object.
+
+ *Rafael Mendonça França*
+
+* Fix `bin/rails db:setup` and `bin/rails db:test:prepare` create wrong
+ ar_internal_metadata's data for a test database.
+
+ Before:
+ ```
+ $ RAILS_ENV=test rails dbconsole
+ > SELECT * FROM ar_internal_metadata;
+ key|value|created_at|updated_at
+ environment|development|2017-09-11 23:14:10.815679|2017-09-11 23:14:10.815679
+ ```
+
+ After:
+ ```
+ $ RAILS_ENV=test rails dbconsole
+ > SELECT * FROM ar_internal_metadata;
+ key|value|created_at|updated_at
+ environment|test|2017-09-11 23:14:10.815679|2017-09-11 23:14:10.815679
+ ```
+
+ Fixes #26731.
+
+ *bogdanvlviv*
+
+* Fix longer sequence name detection for serial columns.
+
+ Fixes #28332.
+
+ *Ryuta Kamizono*
+
+* MySQL: Don't lose `auto_increment: true` in the `db/schema.rb`.
+
+ Fixes #30894.
+
+ *Ryuta Kamizono*
+
+* Fix `COUNT(DISTINCT ...)` for `GROUP BY` with `ORDER BY` and `LIMIT`.
+
+ Fixes #30886.
+
+ *Ryuta Kamizono*
+
+* PostgreSQL `tsrange` now preserves subsecond precision.
+
+ PostgreSQL 9.1+ introduced range types, and Rails added support for using
+ this datatype in Active Record. However, the serialization of
+ `PostgreSQL::OID::Range` was incomplete, because it did not properly
+ cast the bounds that make up the range. This led to subseconds being
+ dropped in SQL commands:
+
+ Before:
+
+ connection.type_cast(tsrange.serialize(range_value))
+ # => "[2010-01-01 13:30:00 UTC,2011-02-02 19:30:00 UTC)"
+
+ Now:
+
+ connection.type_cast(tsrange.serialize(range_value))
+ # => "[2010-01-01 13:30:00.670277,2011-02-02 19:30:00.745125)"
+
+ *Thomas Cannon*
+
+* Passing a `Set` to `Relation#where` now behaves the same as passing an
+ array.
+
+ *Sean Griffin*
+
+* Use given algorithm while removing index from database.
+
+ Fixes #24190.
+
+ *Mehmet Emin İNAÇ*
+
+* Update payload names for `sql.active_record` instrumentation to be
+ more descriptive.
+
+ Fixes #30586.
+
+ *Jeremy Green*
+
* Add new error class `TransactionTimeout` for MySQL adapter which will be raised
when lock wait time expires.
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 0c19fed9e1..bf6dfd46e1 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -28,8 +28,8 @@ require "active_support/rails"
require "active_model"
require "arel"
-require_relative "active_record/version"
-require_relative "active_record/attribute_set"
+require "active_record/version"
+require "active_record/attribute_set"
module ActiveRecord
extend ActiveSupport::Autoload
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index a61c0336db..573f41d7ad 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -3,7 +3,7 @@
require "active_support/core_ext/enumerable"
require "active_support/core_ext/string/conversions"
require "active_support/core_ext/module/remove_method"
-require_relative "errors"
+require "active_record/errors"
module ActiveRecord
class AssociationNotFoundError < ConfigurationError #:nodoc:
@@ -557,9 +557,8 @@ module ActiveRecord
# has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event'
# end
#
- # Note: Joining, eager loading and preloading of these associations is not fully possible.
+ # Note: Joining, eager loading and preloading of these associations is not possible.
# These operations happen before instance creation and the scope will be called with a +nil+ argument.
- # This can lead to unexpected behavior and is deprecated.
#
# == Association callbacks
#
@@ -850,7 +849,7 @@ module ActiveRecord
# project.milestones # fetches milestones from the database
# project.milestones.size # uses the milestone cache
# project.milestones.empty? # uses the milestone cache
- # project.milestones(true).size # fetches milestones from the database
+ # project.milestones.reload.size # fetches milestones from the database
# project.milestones # uses the milestone cache
#
# == Eager loading of associations
@@ -1400,7 +1399,7 @@ module ActiveRecord
# has_many :tags, as: :taggable
# has_many :reports, -> { readonly }
# has_many :subscribers, through: :subscriptions, source: :user
- def has_many(name, scope = nil, options = {}, &extension)
+ def has_many(name, scope = nil, **options, &extension)
reflection = Builder::HasMany.build(self, name, scope, options, &extension)
Reflection.add_reflection self, name, reflection
end
@@ -1534,7 +1533,7 @@ module ActiveRecord
# has_one :club, through: :membership
# has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
# has_one :credit_card, required: true
- def has_one(name, scope = nil, options = {})
+ def has_one(name, scope = nil, **options)
reflection = Builder::HasOne.build(self, name, scope, options)
Reflection.add_reflection self, name, reflection
end
@@ -1678,7 +1677,7 @@ module ActiveRecord
# belongs_to :company, touch: :employees_last_updated_at
# belongs_to :user, optional: true
# belongs_to :account, default: -> { company.account }
- def belongs_to(name, scope = nil, options = {})
+ def belongs_to(name, scope = nil, **options)
reflection = Builder::BelongsTo.build(self, name, scope, options)
Reflection.add_reflection self, name, reflection
end
@@ -1848,7 +1847,7 @@ module ActiveRecord
builder = Builder::HasAndBelongsToMany.new name, self, options
- join_model = ActiveSupport::Deprecation.silence { builder.through_model }
+ join_model = builder.through_model
const_set join_model.name, join_model
private_constant join_model.name
@@ -1877,7 +1876,7 @@ module ActiveRecord
hm_options[k] = options[k] if options.key? k
end
- ActiveSupport::Deprecation.silence { has_many name, scope, hm_options, &extension }
+ has_many name, scope, hm_options, &extension
_reflections[name.to_s].parent_reflection = habtm_reflection
end
end
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 096f016976..07bd0a273b 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -6,36 +6,34 @@ module ActiveRecord
module Associations
# Keeps track of table aliases for ActiveRecord::Associations::JoinDependency
class AliasTracker # :nodoc:
- def self.create(connection, initial_table)
- aliases = Hash.new(0)
- aliases[initial_table] = 1
- new(connection, aliases)
- end
-
- def self.create_with_joins(connection, initial_table, joins)
+ def self.create(connection, initial_table, joins)
if joins.empty?
- create(connection, initial_table)
+ aliases = Hash.new(0)
else
aliases = Hash.new { |h, k|
h[k] = initial_count_for(connection, k, joins)
}
- aliases[initial_table] = 1
- new(connection, aliases)
end
+ aliases[initial_table] = 1
+ new(connection, aliases)
end
def self.initial_count_for(connection, name, table_joins)
- # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
- quoted_name = connection.quote_table_name(name).downcase
+ quoted_name = nil
counts = table_joins.map do |join|
if join.is_a?(Arel::Nodes::StringJoin)
+ # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase
+ quoted_name ||= connection.quote_table_name(name)
+
# Table names + table aliases
- join.left.downcase.scan(
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
+ join.left.scan(
+ /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i
).size
elsif join.respond_to? :left
- join.left.table_name == name ? 1 : 0
+ join.left.name == name ? 1 : 0
+ elsif join.is_a?(Hash)
+ join[name]
else
# this branch is reached by two tests:
#
@@ -79,10 +77,7 @@ module ActiveRecord
end
end
- # TODO Change this to private once we've dropped Ruby 2.2 support.
- # Workaround for Ruby 2.2 "private attribute?" warning.
- protected
- attr_reader :aliases
+ attr_reader :aliases
private
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 268b022ab8..ca1f9f1650 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -130,8 +130,8 @@ module ActiveRecord
def extensions
extensions = klass.default_extensions | reflection.extensions
- if scope = reflection.scope
- extensions |= klass.unscoped.instance_exec(owner, &scope).extensions
+ if reflection.scope
+ extensions |= reflection.scope_for(klass.unscoped, owner).extensions
end
extensions
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 3d79e540b8..11967e0571 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -23,15 +23,10 @@ module ActiveRecord
reflection = association.reflection
scope = klass.unscoped
owner = association.owner
- alias_tracker = AliasTracker.create(klass.connection, klass.table_name)
- chain_head, chain_tail = get_chain(reflection, association, alias_tracker)
+ chain = get_chain(reflection, association, scope.alias_tracker)
scope.extending! reflection.extensions
- add_constraints(scope, owner, reflection, chain_head, chain_tail)
- end
-
- def join_type
- Arel::Nodes::InnerJoin
+ add_constraints(scope, owner, chain)
end
def self.get_bind_values(owner, chain)
@@ -59,14 +54,15 @@ module ActiveRecord
private
def join(table, constraint)
- table.create_join(table, table.create_on(constraint), join_type)
+ table.create_join(table, table.create_on(constraint))
end
- def last_chain_scope(scope, table, reflection, owner)
+ def last_chain_scope(scope, reflection, owner)
join_keys = reflection.join_keys
key = join_keys.key
foreign_key = join_keys.foreign_key
+ table = reflection.aliased_table
value = transform_value(owner[foreign_key])
scope = apply_scope(scope, table, key, value)
@@ -82,11 +78,13 @@ module ActiveRecord
value_transformation.call(value)
end
- def next_chain_scope(scope, table, reflection, foreign_table, next_reflection)
+ def next_chain_scope(scope, reflection, next_reflection)
join_keys = reflection.join_keys
key = join_keys.key
foreign_key = join_keys.foreign_key
+ table = reflection.aliased_table
+ foreign_table = next_reflection.aliased_table
constraint = table[key].eq(foreign_table[foreign_key])
if reflection.type
@@ -98,12 +96,11 @@ module ActiveRecord
end
class ReflectionProxy < SimpleDelegator # :nodoc:
- attr_accessor :next
- attr_reader :alias_name
+ attr_reader :aliased_table
- def initialize(reflection, alias_name)
+ def initialize(reflection, aliased_table)
super(reflection)
- @alias_name = alias_name
+ @aliased_table = aliased_table
end
def all_includes; nil; end
@@ -111,42 +108,33 @@ module ActiveRecord
def get_chain(reflection, association, tracker)
name = reflection.name
- runtime_reflection = Reflection::RuntimeReflection.new(reflection, association)
- previous_reflection = runtime_reflection
+ chain = [Reflection::RuntimeReflection.new(reflection, association)]
reflection.chain.drop(1).each do |refl|
- alias_name = tracker.aliased_table_for(
+ aliased_table = tracker.aliased_table_for(
refl.table_name,
refl.alias_candidate(name),
refl.klass.type_caster
)
- proxy = ReflectionProxy.new(refl, alias_name)
- previous_reflection.next = proxy
- previous_reflection = proxy
+ chain << ReflectionProxy.new(refl, aliased_table)
end
- [runtime_reflection, previous_reflection]
+ chain
end
- def add_constraints(scope, owner, refl, chain_head, chain_tail)
- owner_reflection = chain_tail
- table = owner_reflection.alias_name
- scope = last_chain_scope(scope, table, owner_reflection, owner)
-
- reflection = chain_head
- while reflection
- table = reflection.alias_name
- next_reflection = reflection.next
+ def add_constraints(scope, owner, chain)
+ scope = last_chain_scope(scope, chain.last, owner)
- unless reflection == chain_tail
- foreign_table = next_reflection.alias_name
- scope = next_chain_scope(scope, table, reflection, foreign_table, next_reflection)
- end
+ chain.each_cons(2) do |reflection, next_reflection|
+ scope = next_chain_scope(scope, reflection, next_reflection)
+ end
+ chain_head = chain.first
+ chain.reverse_each do |reflection|
# Exclude the scope of the association itself, because that
# was already merged in the #scope method.
reflection.constraints.each do |scope_chain_item|
- item = eval_scope(reflection, table, scope_chain_item, owner)
+ item = eval_scope(reflection, scope_chain_item, owner)
- if scope_chain_item == refl.scope
+ if scope_chain_item == chain_head.scope
scope.merge! item.except(:where, :includes)
end
@@ -158,8 +146,6 @@ module ActiveRecord
scope.where_clause += item.where_clause
scope.order_values |= item.order_values
end
-
- reflection = next_reflection
end
scope
@@ -173,8 +159,9 @@ module ActiveRecord
end
end
- def eval_scope(reflection, table, scope, owner)
- reflection.build_scope(table).instance_exec(owner, &scope)
+ def eval_scope(reflection, scope, owner)
+ relation = reflection.build_scope(reflection.aliased_table)
+ relation.instance_exec(owner, &scope) || relation
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 496b16b58f..ca3032d967 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -38,11 +38,6 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.create_reflection(model, name, scope, options, extension = nil)
raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
- if scope.is_a?(Hash)
- options = scope
- scope = nil
- end
-
validate_options(options)
scope = build_scope(scope, extension)
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 753fde5146..35a72c3850 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../associations"
+require "active_record/associations"
module ActiveRecord::Associations::Builder # :nodoc:
class CollectionAssociation < Association #:nodoc:
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index ceedf150e3..ed215fb22c 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -181,8 +181,6 @@ module ActiveRecord
# are actually removed from the database, that depends precisely on
# +delete_records+. They are in any case removed from the collection.
def delete(*records)
- return if records.empty?
- records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
delete_or_destroy(records, options[:dependent])
end
@@ -192,8 +190,6 @@ module ActiveRecord
# Note that this method removes records from the database ignoring the
# +:dependent+ option.
def destroy(*records)
- return if records.empty?
- records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
delete_or_destroy(records, :destroy)
end
@@ -376,6 +372,8 @@ module ActiveRecord
end
def delete_or_destroy(records, method)
+ return if records.empty?
+ records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) }
records = records.flatten
records.each { |record| raise_on_type_mismatch!(record) }
existing_records = records.reject(&:new_record?)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 412e89255d..07f7303f8d 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -1072,7 +1072,6 @@ module ActiveRecord
end
# Reloads the collection from the database. Returns +self+.
- # Equivalent to <tt>collection(true)</tt>.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -1086,9 +1085,6 @@ module ActiveRecord
#
# person.pets.reload # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
- #
- # person.pets(true) # fetches pets from the database
- # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reload
proxy_association.reload
reset_scope
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index ac37c3c015..df4bf07999 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -43,8 +43,6 @@ module ActiveRecord
Column = Struct.new(:name, :alias)
end
- attr_reader :alias_tracker, :base_klass, :join_root
-
def self.make_tree(associations)
hash = {}
walk_tree associations, hash
@@ -90,8 +88,8 @@ module ActiveRecord
# associations # => [:appointments]
# joins # => []
#
- def initialize(base, table, associations, joins, eager_loading: true)
- @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins)
+ def initialize(base, table, associations, alias_tracker, eager_loading: true)
+ @alias_tracker = alias_tracker
@eager_loading = eager_loading
tree = self.class.make_tree associations
@join_root = JoinBase.new(base, table, build(tree, base))
@@ -158,6 +156,9 @@ module ActiveRecord
parents.values
end
+ protected
+ attr_reader :alias_tracker, :base_klass, :join_root
+
private
def make_constraints(parent, child, tables, join_type)
@@ -224,7 +225,7 @@ module ActiveRecord
raise EagerLoadPolymorphicError.new(reflection)
end
- JoinAssociation.new reflection, build(right, reflection.klass)
+ JoinAssociation.new(reflection, build(right, reflection.klass), alias_tracker)
end.compact
end
@@ -256,7 +257,8 @@ module ActiveRecord
else
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
- if node.reflection.scope_for(node.base_klass).readonly_value
+ if node.reflection.scope &&
+ node.reflection.scope_for(node.base_klass.unscoped).readonly_value
model.readonly!
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index a526468bf6..221c791bf8 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "join_part"
+require "active_record/associations/join_dependency/join_part"
module ActiveRecord
module Associations
@@ -11,11 +11,12 @@ module ActiveRecord
attr_accessor :tables
- def initialize(reflection, children)
+ def initialize(reflection, children, alias_tracker)
super(reflection.klass, children)
- @reflection = reflection
- @tables = nil
+ @alias_tracker = alias_tracker
+ @reflection = reflection
+ @tables = nil
end
def match?(other)
@@ -38,11 +39,12 @@ module ActiveRecord
joins << table.create_join(table, table.create_on(constraint), join_type)
join_scope = reflection.join_scope(table, foreign_klass)
+ arel = join_scope.arel(alias_tracker.aliases)
- if join_scope.arel.constraints.any?
- joins.concat join_scope.arel.join_sources
+ if arel.constraints.any?
+ joins.concat arel.join_sources
right = joins.last.right
- right.expr = right.expr.and(join_scope.arel.constraints)
+ right.expr = right.expr.and(arel.constraints)
end
# The current table in this iteration becomes the foreign table in the next
@@ -55,6 +57,9 @@ module ActiveRecord
def table
tables.first
end
+
+ protected
+ attr_reader :alias_tracker
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
index 8a8fa8993b..988b4e8fa2 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "join_part"
+require "active_record/associations/join_dependency/join_part"
module ActiveRecord
module Associations
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 62caf02a2c..e1754d4a19 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -91,13 +91,13 @@ module ActiveRecord
# { author: :avatar }
# [ :books, { author: :avatar } ]
def preload(records, associations, preload_scope = nil)
- records = Array.wrap(records).compact.uniq
- associations = Array.wrap(associations)
+ records = records.compact
if records.empty?
[]
else
- associations.flat_map { |association|
+ records.uniq!
+ Array.wrap(associations).flat_map { |association|
preloaders_on association, records, preload_scope
}
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 4915a37f06..607d376a08 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -17,26 +17,20 @@ module ActiveRecord
end
def run(preloader)
- preload(preloader)
- end
-
- def preload(preloader)
- raise NotImplementedError
- end
-
- # The name of the key on the associated records
- def association_key_name
- raise NotImplementedError
- end
-
- # The name of the key on the model which declares the association
- def owner_key_name
- raise NotImplementedError
+ associated_records_by_owner(preloader).each do |owner, records|
+ associate_records_to_owner(owner, records)
+ end
end
private
- def options
- reflection.options
+ # The name of the key on the associated records
+ def association_key_name
+ reflection.join_primary_key(klass)
+ end
+
+ # The name of the key on the model which declares the association
+ def owner_key_name
+ reflection.join_foreign_key
end
def associated_records_by_owner(preloader)
@@ -51,28 +45,30 @@ module ActiveRecord
end
end
+ def associate_records_to_owner(owner, records)
+ raise NotImplementedError
+ end
+
def owner_keys
- unless defined?(@owner_keys)
- @owner_keys = owners.map do |owner|
- owner[owner_key_name]
- end
- @owner_keys.uniq!
- @owner_keys.compact!
- end
- @owner_keys
+ @owner_keys ||= owners_by_key.keys
end
def owners_by_key
unless defined?(@owners_by_key)
@owners_by_key = owners.each_with_object({}) do |owner, h|
- h[convert_key(owner[owner_key_name])] = owner
+ key = convert_key(owner[owner_key_name])
+ h[key] = owner if key
end
end
@owners_by_key
end
def key_conversion_required?
- @key_conversion_required ||= association_key_type != owner_key_type
+ unless defined?(@key_conversion_required)
+ @key_conversion_required = (association_key_type != owner_key_type)
+ end
+
+ @key_conversion_required
end
def convert_key(key)
@@ -113,7 +109,7 @@ module ActiveRecord
end
def reflection_scope
- @reflection_scope ||= reflection.scope_for(klass)
+ @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
end
def build_scope
@@ -123,7 +119,7 @@ module ActiveRecord
scope.where!(reflection.type => model.base_class.sti_name)
end
- scope.merge!(reflection_scope)
+ scope.merge!(reflection_scope) if reflection.scope
scope.merge!(preload_scope) if preload_scope
scope
end
diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb
index ae9695f26a..a8e3340b23 100644
--- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/preloader/belongs_to.rb
@@ -4,13 +4,6 @@ module ActiveRecord
module Associations
class Preloader
class BelongsTo < SingularAssociation #:nodoc:
- def association_key_name
- options[:primary_key] || klass && klass.primary_key
- end
-
- def owner_key_name
- reflection.foreign_key
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb
index fb920a642c..fc2029f54a 100644
--- a/activerecord/lib/active_record/associations/preloader/collection_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb
@@ -5,13 +5,10 @@ module ActiveRecord
class Preloader
class CollectionAssociation < Association #:nodoc:
private
-
- def preload(preloader)
- associated_records_by_owner(preloader).each do |owner, records|
- association = owner.association(reflection.name)
- association.loaded!
- association.target.concat(records)
- end
+ def associate_records_to_owner(owner, records)
+ association = owner.association(reflection.name)
+ association.loaded!
+ association.target.concat(records)
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_many.rb b/activerecord/lib/active_record/associations/preloader/has_many.rb
index 29a1ce099d..72f55bc43f 100644
--- a/activerecord/lib/active_record/associations/preloader/has_many.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_many.rb
@@ -4,13 +4,6 @@ module ActiveRecord
module Associations
class Preloader
class HasMany < CollectionAssociation #:nodoc:
- def association_key_name
- reflection.foreign_key
- end
-
- def owner_key_name
- reflection.active_record_primary_key
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb
index d87abf630f..e339b65fb5 100644
--- a/activerecord/lib/active_record/associations/preloader/has_one.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_one.rb
@@ -4,13 +4,6 @@ module ActiveRecord
module Associations
class Preloader
class HasOne < SingularAssociation #:nodoc:
- def association_key_name
- reflection.foreign_key
- end
-
- def owner_key_name
- reflection.active_record_primary_key
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb
index 266b5f6b1c..30a92411e3 100644
--- a/activerecord/lib/active_record/associations/preloader/singular_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/singular_association.rb
@@ -5,14 +5,9 @@ module ActiveRecord
class Preloader
class SingularAssociation < Association #:nodoc:
private
-
- def preload(preloader)
- associated_records_by_owner(preloader).each do |owner, associated_records|
- record = associated_records.first
-
- association = owner.association(reflection.name)
- association.target = record
- end
+ def associate_records_to_owner(owner, records)
+ association = owner.association(reflection.name)
+ association.target = records.first
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index de4b847a41..fa32cc5553 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -28,6 +28,8 @@ module ActiveRecord
middle_records = through_records.flat_map(&:last)
+ reflection_scope = reflection_scope() if reflection.scope
+
preloaders = preloader.preload(middle_records,
source_reflection.name,
reflection_scope)
@@ -49,7 +51,7 @@ module ActiveRecord
}.compact
# Respect the order on `reflection_scope` if it exists, else use the natural order.
- if reflection_scope.values[:order].present?
+ if reflection_scope && !reflection_scope.order_values.empty?
@id_map ||= id_to_index_map @preloaded_records
rhs_records.sort_by { |rhs| @id_map[rhs] }
else
@@ -67,10 +69,7 @@ module ActiveRecord
id_map
end
- def reset_association(owners, association_name, through_scope)
- should_reset = (through_scope != through_reflection.klass.unscoped) ||
- (options[:source_type] && through_reflection.collection?)
-
+ def reset_association(owners, association_name, should_reset)
# Don't cache the association - we would only be caching a subset
if should_reset
owners.each { |owner|
@@ -81,29 +80,40 @@ module ActiveRecord
def through_scope
scope = through_reflection.klass.unscoped
- values = reflection_scope.values
+ options = reflection.options
if options[:source_type]
scope.where! reflection.foreign_type => options[:source_type]
- else
- unless reflection_scope.where_clause.empty?
- scope.includes_values = Array(values[:includes] || options[:source])
- scope.where_clause = reflection_scope.where_clause
- if joins = values[:joins]
- scope.joins!(source_reflection.name => joins)
- end
- if left_outer_joins = values[:left_outer_joins]
- scope.left_outer_joins!(source_reflection.name => left_outer_joins)
- end
+ elsif !reflection_scope.where_clause.empty?
+ scope.where_clause = reflection_scope.where_clause
+ values = reflection_scope.values
+
+ if includes = values[:includes]
+ scope.includes!(source_reflection.name => includes)
+ else
+ scope.includes!(source_reflection.name)
+ end
+
+ if values[:references] && !values[:references].empty?
+ scope.references!(values[:references])
+ else
+ scope.references!(source_reflection.table_name)
+ end
+
+ if joins = values[:joins]
+ scope.joins!(source_reflection.name => joins)
+ end
+
+ if left_outer_joins = values[:left_outer_joins]
+ scope.left_outer_joins!(source_reflection.name => left_outer_joins)
end
- scope.references! values[:references]
if scope.eager_loading? && order_values = values[:order]
scope = scope.order(order_values)
end
end
- scope
+ scope unless scope.empty_scope?
end
end
end
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
index 690a931615..f746960fac 100644
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../attribute"
+require "active_record/attribute"
module ActiveRecord
class Attribute # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index e4ca6c8408..891e556dc4 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -33,7 +33,9 @@ module ActiveRecord
BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass)
- class GeneratedAttributeMethods < Module; end # :nodoc:
+ class GeneratedAttributeMethods < Module #:nodoc:
+ include Mutex_m
+ end
module ClassMethods
def inherited(child_class) #:nodoc:
@@ -42,7 +44,7 @@ module ActiveRecord
end
def initialize_generated_modules # :nodoc:
- @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m }
+ @generated_attribute_methods = GeneratedAttributeMethods.new
@attribute_methods_generated = false
include @generated_attribute_methods
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 4f957ac3ca..79110d04f4 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support/core_ext/module/attribute_accessors"
-require_relative "../attribute_mutation_tracker"
+require "active_record/attribute_mutation_tracker"
module ActiveRecord
module AttributeMethods
@@ -34,7 +34,7 @@ module ActiveRecord
def reload(*)
super.tap do
@mutations_before_last_save = nil
- clear_mutation_trackers
+ @mutations_from_database = nil
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
end
@@ -44,22 +44,21 @@ module ActiveRecord
@attributes = self.class._default_attributes.map do |attr|
attr.with_value_from_user(@attributes.fetch_value(attr.name))
end
- clear_mutation_trackers
+ @mutations_from_database = nil
end
def changes_applied # :nodoc:
- @mutations_before_last_save = mutation_tracker
- @mutations_from_database = AttributeMutationTracker.new(@attributes)
+ @mutations_before_last_save = mutations_from_database
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
- clear_mutation_trackers
+ @mutations_from_database = nil
end
def clear_changes_information # :nodoc:
@mutations_before_last_save = nil
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
- clear_mutation_trackers
+ @mutations_from_database = nil
end
def clear_attribute_changes(attr_names) # :nodoc:
@@ -75,7 +74,7 @@ module ActiveRecord
if defined?(@cached_changed_attributes)
@cached_changed_attributes
else
- super.reverse_merge(mutation_tracker.changed_values).freeze
+ super.reverse_merge(mutations_from_database.changed_values).freeze
end
end
@@ -90,7 +89,7 @@ module ActiveRecord
end
def attribute_changed_in_place?(attr_name) # :nodoc:
- mutation_tracker.changed_in_place?(attr_name)
+ mutations_from_database.changed_in_place?(attr_name)
end
# Did this attribute change when we last saved? This method can be invoked
@@ -183,26 +182,18 @@ module ActiveRecord
result
end
- def mutation_tracker
- unless defined?(@mutation_tracker)
- @mutation_tracker = nil
- end
- @mutation_tracker ||= AttributeMutationTracker.new(@attributes)
- end
-
def mutations_from_database
unless defined?(@mutations_from_database)
@mutations_from_database = nil
end
- @mutations_from_database ||= mutation_tracker
+ @mutations_from_database ||= AttributeMutationTracker.new(@attributes)
end
def changes_include?(attr_name)
- super || mutation_tracker.changed?(attr_name)
+ super || mutations_from_database.changed?(attr_name)
end
def clear_attribute_change(attr_name)
- mutation_tracker.forget_change(attr_name)
mutations_from_database.forget_change(attr_name)
end
@@ -227,11 +218,6 @@ module ActiveRecord
@attributes = @attributes.map(&:forgetting_assignment)
end
- def clear_mutation_trackers
- @mutation_tracker = nil
- @mutations_from_database = nil
- end
-
def mutations_before_last_save
@mutations_before_last_save ||= NullMutationTracker.instance
end
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 63c059e291..d8fc046e10 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -17,13 +17,15 @@ module ActiveRecord
# Returns the primary key value.
def id
sync_with_transaction_state
- _read_attribute(self.class.primary_key) if self.class.primary_key
+ primary_key = self.class.primary_key
+ _read_attribute(primary_key) if primary_key
end
# Sets the primary key value.
def id=(value)
sync_with_transaction_state
- _write_attribute(self.class.primary_key, value) if self.class.primary_key
+ primary_key = self.class.primary_key
+ _write_attribute(primary_key, value) if primary_key
end
# Queries the primary key value.
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index b070235684..4077250583 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -58,8 +58,9 @@ module ActiveRecord
attr_name.to_s
end
- name = self.class.primary_key if name == "id".freeze && self.class.primary_key
- sync_with_transaction_state if name == self.class.primary_key
+ primary_key = self.class.primary_key
+ name = primary_key if name == "id".freeze && primary_key
+ sync_with_transaction_state if name == primary_key
_read_attribute(name, &block)
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 37891ce2ef..bb0ec6a8c3 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -39,8 +39,9 @@ module ActiveRecord
attr_name.to_s
end
- name = self.class.primary_key if name == "id".freeze && self.class.primary_key
- sync_with_transaction_state if name == self.class.primary_key
+ primary_key = self.class.primary_key
+ name = primary_key if name == "id".freeze && primary_key
+ sync_with_transaction_state if name == primary_key
_write_attribute(name, value)
end
diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb
index 492067e2b3..9785666e77 100644
--- a/activerecord/lib/active_record/attribute_set.rb
+++ b/activerecord/lib/active_record/attribute_set.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "attribute_set/builder"
-require_relative "attribute_set/yaml_encoder"
+require "active_record/attribute_set/builder"
+require "active_record/attribute_set/yaml_encoder"
module ActiveRecord
class AttributeSet # :nodoc:
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb
index e3a9c7fdb3..349cc7e403 100644
--- a/activerecord/lib/active_record/attribute_set/builder.rb
+++ b/activerecord/lib/active_record/attribute_set/builder.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../attribute"
+require "active_record/attribute"
module ActiveRecord
class AttributeSet # :nodoc:
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index afb559db71..9224d58928 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "attribute/user_provided_default"
+require "active_record/attribute/user_provided_default"
module ActiveRecord
# See ActiveRecord::Attributes::ClassMethods for documentation
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 541ff51fbe..b7ad944cec 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -15,14 +15,14 @@ require "active_support/core_ext/kernel/singleton_class"
require "active_support/core_ext/module/introspection"
require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/class/subclasses"
-require_relative "attribute_decorators"
-require_relative "define_callbacks"
-require_relative "errors"
-require_relative "log_subscriber"
-require_relative "explain_subscriber"
-require_relative "relation/delegation"
-require_relative "attributes"
-require_relative "type_caster"
+require "active_record/attribute_decorators"
+require "active_record/define_callbacks"
+require "active_record/errors"
+require "active_record/log_subscriber"
+require "active_record/explain_subscriber"
+require "active_record/relation/delegation"
+require "active_record/attributes"
+require "active_record/type_caster"
module ActiveRecord #:nodoc:
# = Active Record
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index a2439e6ec7..9ab2780760 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -98,9 +98,9 @@ module ActiveRecord
# == Types of callbacks
#
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
- # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects
+ # inline methods (using a proc). Method references and callback objects
# are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for
- # creating mix-ins), and inline eval methods are deprecated.
+ # creating mix-ins).
#
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
#
@@ -240,7 +240,7 @@ module ActiveRecord
#
# private
#
- # def log_chidren
+ # def log_children
# # Child processing
# end
#
@@ -265,7 +265,7 @@ module ActiveRecord
#
# private
#
- # def log_chidren
+ # def log_children
# # Child processing
# end
#
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index b1937a3c68..f3e6516414 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -13,7 +13,7 @@ module ActiveRecord
end
else
column_type = type_for_attribute(timestamp_column.to_s)
- column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
+ column = connection.column_name_from_arel_node(collection.arel_attribute(timestamp_column))
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
if collection.has_limit_or_offset?
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 9ad04c3216..92e46ccf9f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -11,19 +11,6 @@ module ActiveRecord
def quote(value)
value = id_value_for_database(value) if value.is_a?(Base)
- if value.respond_to?(:quoted_id)
- at = value.method(:quoted_id).source_location
- at &&= " at %s:%d" % at
-
- owner = value.method(:quoted_id).owner.to_s
- klass = value.class.to_s
- klass += "(#{owner})" unless owner == klass
-
- ActiveSupport::Deprecation.warn \
- "Defining #quoted_id is deprecated and will be ignored in Rails 5.2. (defined on #{klass}#{at})"
- return value.quoted_id
- end
-
if value.respond_to?(:value_for_database)
value = value.value_for_database
end
@@ -37,10 +24,6 @@ module ActiveRecord
def type_cast(value, column = nil)
value = id_value_for_database(value) if value.is_a?(Base)
- if value.respond_to?(:quoted_id) && value.respond_to?(:id)
- return value.id
- end
-
if column
value = type_cast_from_column(column, value)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
index 8bf3879a4c..bd05fb8f6e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -62,7 +62,7 @@ module ActiveRecord
end
def visit_PrimaryKeyDefinition(o)
- "PRIMARY KEY (#{o.name.join(', ')})"
+ "PRIMARY KEY (#{o.name.map { |name| quote_column_name(name) }.join(', ')})"
end
def visit_ForeignKeyDefinition(o)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 788a455773..be2f625d74 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -396,6 +396,9 @@ module ActiveRecord
alias :belongs_to :references
def new_column_definition(name, type, **options) # :nodoc:
+ if integer_like_primary_key?(type, options)
+ type = integer_like_primary_key_type(type, options)
+ end
type = aliased_types(type.to_s, type)
options[:primary_key] ||= type == :primary_key
options[:null] = false if options[:primary_key]
@@ -410,6 +413,14 @@ module ActiveRecord
def aliased_types(name, fallback)
"timestamp" == name ? :datetime : fallback
end
+
+ def integer_like_primary_key?(type, options)
+ options[:primary_key] && [:integer, :bigint].include?(type) && !options.key?(:default)
+ end
+
+ def integer_like_primary_key_type(type, options)
+ type
+ end
end
class AlterTable # :nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index f57c7a5d4d..6698047ad6 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "../../migration/join_table"
+require "active_record/migration/join_table"
require "active_support/core_ext/string/access"
-require "digest"
+require "digest/sha2"
module ActiveRecord
module ConnectionAdapters # :nodoc:
@@ -79,7 +79,7 @@ module ActiveRecord
end
# Returns an array of indexes for the given table.
- def indexes(table_name, name = nil)
+ def indexes(table_name)
raise NotImplementedError, "#indexes is not implemented"
end
@@ -522,6 +522,8 @@ module ActiveRecord
# Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
# * <tt>:scale</tt> -
# Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
+ # * <tt>:comment</tt> -
+ # Specifies the comment for the column. This option is ignored by some backends.
#
# Note: The precision is the total number of significant digits,
# and the scale is the number of digits that can be stored following
@@ -804,12 +806,7 @@ module ActiveRecord
end
# Verifies the existence of an index with a given name.
- def index_name_exists?(table_name, index_name, default = nil)
- unless default.nil?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing default to #index_name_exists? is deprecated without replacement.
- MSG
- end
+ def index_name_exists?(table_name, index_name)
index_name = index_name.to_s
indexes(table_name).detect { |i| i.name == index_name }
end
@@ -1018,16 +1015,6 @@ module ActiveRecord
insert_versions_sql(versions) if versions.any?
end
- def initialize_schema_migrations_table # :nodoc:
- ActiveRecord::SchemaMigration.create_table
- end
- deprecate :initialize_schema_migrations_table
-
- def initialize_internal_metadata_table # :nodoc:
- ActiveRecord::InternalMetadata.create_table
- end
- deprecate :initialize_internal_metadata_table
-
def internal_string_options_for_primary_key # :nodoc:
{ primary_key: true }
end
@@ -1172,7 +1159,7 @@ module ActiveRecord
end
# Changes the comment for a column or removes it if +nil+.
- def change_column_comment(table_name, column_name, comment) #:nodoc:
+ def change_column_comment(table_name, column_name, comment)
raise NotImplementedError, "#{self.class} does not support changing column comments"
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 47881e3305..e3aab8dad8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require_relative "../type"
-require_relative "determine_if_preparable_visitor"
-require_relative "schema_cache"
-require_relative "sql_type_metadata"
-require_relative "abstract/schema_dumper"
-require_relative "abstract/schema_creation"
+require "active_record/type"
+require "active_record/connection_adapters/determine_if_preparable_visitor"
+require "active_record/connection_adapters/schema_cache"
+require "active_record/connection_adapters/sql_type_metadata"
+require "active_record/connection_adapters/abstract/schema_dumper"
+require "active_record/connection_adapters/abstract/schema_creation"
require "arel/collectors/bind"
require "arel/collectors/composite"
require "arel/collectors/sql_string"
@@ -196,16 +196,6 @@ module ActiveRecord
self.class::ADAPTER_NAME
end
- def supports_migrations? # :nodoc:
- true
- end
- deprecate :supports_migrations?
-
- def supports_primary_key? # :nodoc:
- true
- end
- deprecate :supports_primary_key?
-
# Does this adapter support DDL rollbacks in transactions? That is, would
# CREATE TABLE or ALTER TABLE get rolled back by a transaction?
def supports_ddl_transactions?
@@ -402,10 +392,7 @@ module ActiveRecord
# Checks whether the connection to the database is still active (i.e. not stale).
# This is done under the hood by calling #active?. If the connection
# is no longer active, then this method will reconnect to the database.
- def verify!(*ignored)
- if ignored.size > 0
- ActiveSupport::Deprecation.warn("Passing arguments to #verify method of the connection has no effect and has been deprecated. Please remove all arguments from the #verify method call.")
- end
+ def verify!
reconnect! unless active?
end
@@ -442,7 +429,11 @@ module ActiveRecord
end
def column_name_for_operation(operation, node) # :nodoc:
- visitor.accept(node, collector).value
+ column_name_from_arel_node(node)
+ end
+
+ def column_name_from_arel_node(node) # :nodoc:
+ visitor.accept(node, Arel::Collectors::SQLString.new).value
end
def default_index_type?(index) # :nodoc:
@@ -474,6 +465,8 @@ module ActiveRecord
m.alias_type %r(number)i, "decimal"
m.alias_type %r(double)i, "float"
+ m.register_type %r(^json)i, Type::Json.new
+
m.register_type(%r(decimal)i) do |sql_type|
scale = extract_scale(sql_type)
precision = extract_precision(sql_type)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 7cd086084a..bfec6fb784 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,15 +1,15 @@
# frozen_string_literal: true
-require_relative "abstract_adapter"
-require_relative "statement_pool"
-require_relative "mysql/column"
-require_relative "mysql/explain_pretty_printer"
-require_relative "mysql/quoting"
-require_relative "mysql/schema_creation"
-require_relative "mysql/schema_definitions"
-require_relative "mysql/schema_dumper"
-require_relative "mysql/schema_statements"
-require_relative "mysql/type_metadata"
+require "active_record/connection_adapters/abstract_adapter"
+require "active_record/connection_adapters/statement_pool"
+require "active_record/connection_adapters/mysql/column"
+require "active_record/connection_adapters/mysql/explain_pretty_printer"
+require "active_record/connection_adapters/mysql/quoting"
+require "active_record/connection_adapters/mysql/schema_creation"
+require "active_record/connection_adapters/mysql/schema_definitions"
+require "active_record/connection_adapters/mysql/schema_dumper"
+require "active_record/connection_adapters/mysql/schema_statements"
+require "active_record/connection_adapters/mysql/type_metadata"
require "active_support/core_ext/string/strip"
@@ -284,7 +284,7 @@ module ActiveRecord
def table_comment(table_name) # :nodoc:
scope = quoted_scope(table_name)
- query_value(<<-SQL.strip_heredoc, "SCHEMA")
+ query_value(<<-SQL.strip_heredoc, "SCHEMA").presence
SELECT table_comment
FROM information_schema.tables
WHERE table_schema = #{scope[:schema]}
@@ -311,6 +311,11 @@ module ActiveRecord
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
end
+ def change_table_comment(table_name, comment) #:nodoc:
+ comment = "" if comment.nil?
+ execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}")
+ end
+
# Renames a table.
#
# Example:
@@ -351,18 +356,19 @@ module ActiveRecord
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
default = extract_new_default_value(default_or_changes)
- column = column_for(table_name, column_name)
- change_column table_name, column_name, column.sql_type, default: default
+ change_column table_name, column_name, nil, default: default
end
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
- column = column_for(table_name, column_name)
-
unless null || default.nil?
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
end
- change_column table_name, column_name, column.sql_type, null: null
+ change_column table_name, column_name, nil, null: null
+ end
+
+ def change_column_comment(table_name, column_name, comment) #:nodoc:
+ change_column table_name, column_name, nil, comment: comment
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
@@ -557,7 +563,6 @@ module ActiveRecord
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
m.register_type %r(^float)i, Type::Float.new(limit: 24)
m.register_type %r(^double)i, Type::Float.new(limit: 53)
- m.register_type %r(^json)i, Type::Json.new
register_integer_type m, %r(^bigint)i, limit: 8
register_integer_type m, %r(^int)i, limit: 4
@@ -668,6 +673,7 @@ module ActiveRecord
def change_column_sql(table_name, column_name, type, options = {})
column = column_for(table_name, column_name)
+ type ||= column.sql_type
unless options.key?(:default)
options[:default] = column.default
@@ -716,7 +722,7 @@ module ActiveRecord
def remove_index_sql(table_name, options = {})
index_name = index_name_for_remove(table_name, options)
- "DROP INDEX #{index_name}"
+ "DROP INDEX #{quote_column_name(index_name)}"
end
def add_timestamps_sql(table_name, options = {})
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 16273fb5f1..28d949b503 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -15,7 +15,7 @@ module ActiveRecord
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil)
+ def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil, **)
@name = name.freeze
@table_name = table_name
@sql_type_metadata = sql_type_metadata
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index 29542f917e..508132accb 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -183,13 +183,25 @@ module ActiveRecord
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
+ # Require the adapter itself and give useful feedback about
+ # 1. Missing adapter gems and
+ # 2. Adapter gems' missing dependencies.
path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter"
begin
require path_to_adapter
- rescue Gem::LoadError => e
- raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord)."
rescue LoadError => e
- raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace
+ # We couldn't require the adapter itself. Raise an exception that
+ # points out config typos and missing gems.
+ if e.path == path_to_adapter
+ # We can assume that a non-builtin adapter was specified, so it's
+ # either misspelled or missing from Gemfile.
+ raise e.class, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
+
+ # Bubbled up from the adapter require. Prefix the exception message
+ # with some guidance about how to address it and reraise.
+ else
+ raise e.class, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
+ end
end
adapter_method = "#{spec[:adapter]}_connection"
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
index b22a2e4da7..da25e4863c 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -4,11 +4,6 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
module ColumnMethods
- def primary_key(name, type = :primary_key, **options)
- options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)
- super
- end
-
def blob(*args, **options)
args.each { |name| column(name, :blob, options) }
end
@@ -68,7 +63,6 @@ module ActiveRecord
when :primary_key
type = :integer
options[:limit] ||= 8
- options[:auto_increment] = true
options[:primary_key] = true
when /\Aunsigned_(?<type>.+)\z/
type = $~[:type].to_sym
@@ -82,6 +76,11 @@ module ActiveRecord
def aliased_types(name, fallback)
fallback
end
+
+ def integer_like_primary_key_type(type, options)
+ options[:auto_increment] = true
+ type
+ end
end
class Table < ActiveRecord::ConnectionAdapters::Table
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index 95eb77aea4..d23178e43c 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -8,6 +8,7 @@ module ActiveRecord
def prepare_column_options(column)
spec = super
spec[:unsigned] = "true" if column.unsigned?
+ spec[:auto_increment] = "true" if column.auto_increment?
if @connection.supports_virtual_columns? && column.virtual?
spec[:as] = extract_expression_for_virtual_column(column)
@@ -18,6 +19,12 @@ module ActiveRecord
spec
end
+ def column_spec_for_primary_key(column)
+ spec = super
+ spec.delete(:auto_increment) if column.type == :integer && column.auto_increment?
+ spec
+ end
+
def default_primary_key?(column)
super && column.auto_increment? && !column.unsigned?
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index 759493e3bd..a15c7d1787 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -5,13 +5,7 @@ module ActiveRecord
module MySQL
module SchemaStatements # :nodoc:
# Returns an array of indexes for the given table.
- def indexes(table_name, name = nil)
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing name to #indexes is deprecated without replacement.
- MSG
- end
-
+ def indexes(table_name)
indexes = []
current_index = nil
execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 2c2321872d..8de582fee1 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "abstract_mysql_adapter"
-require_relative "mysql/database_statements"
+require "active_record/connection_adapters/abstract_mysql_adapter"
+require "active_record/connection_adapters/mysql/database_statements"
gem "mysql2", ">= 0.3.18", "< 0.5"
require "mysql2"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index 1b67cee24b..469ef3f5a0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -7,11 +7,38 @@ module ActiveRecord
delegate :array, :oid, :fmod, to: :sql_type_metadata
alias :array? :array
+ def initialize(*, max_identifier_length: 63, **)
+ super
+ @max_identifier_length = max_identifier_length
+ end
+
def serial?
return unless default_function
- %r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function
+ if %r{\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z} =~ default_function
+ sequence_name_from_parts(table_name, name, suffix) == sequence_name
+ end
end
+
+ protected
+ attr_reader :max_identifier_length
+
+ private
+ def sequence_name_from_parts(table_name, column_name, suffix)
+ over_length = [table_name, column_name, suffix].map(&:length).sum + 2 - max_identifier_length
+
+ if over_length > 0
+ column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min
+ over_length -= column_name.length - column_name_length
+ column_name = column_name[0, column_name_length - [over_length, 0].min]
+ end
+
+ if over_length > 0
+ table_name = table_name[0, table_name.length - over_length]
+ end
+
+ "#{table_name}_#{column_name}_#{suffix}"
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index b28418d74f..542ca75d3e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -1,27 +1,27 @@
# frozen_string_literal: true
-require_relative "oid/array"
-require_relative "oid/bit"
-require_relative "oid/bit_varying"
-require_relative "oid/bytea"
-require_relative "oid/cidr"
-require_relative "oid/date_time"
-require_relative "oid/decimal"
-require_relative "oid/enum"
-require_relative "oid/hstore"
-require_relative "oid/inet"
-require_relative "oid/jsonb"
-require_relative "oid/money"
-require_relative "oid/oid"
-require_relative "oid/point"
-require_relative "oid/legacy_point"
-require_relative "oid/range"
-require_relative "oid/specialized_string"
-require_relative "oid/uuid"
-require_relative "oid/vector"
-require_relative "oid/xml"
+require "active_record/connection_adapters/postgresql/oid/array"
+require "active_record/connection_adapters/postgresql/oid/bit"
+require "active_record/connection_adapters/postgresql/oid/bit_varying"
+require "active_record/connection_adapters/postgresql/oid/bytea"
+require "active_record/connection_adapters/postgresql/oid/cidr"
+require "active_record/connection_adapters/postgresql/oid/date_time"
+require "active_record/connection_adapters/postgresql/oid/decimal"
+require "active_record/connection_adapters/postgresql/oid/enum"
+require "active_record/connection_adapters/postgresql/oid/hstore"
+require "active_record/connection_adapters/postgresql/oid/inet"
+require "active_record/connection_adapters/postgresql/oid/jsonb"
+require "active_record/connection_adapters/postgresql/oid/money"
+require "active_record/connection_adapters/postgresql/oid/oid"
+require "active_record/connection_adapters/postgresql/oid/point"
+require "active_record/connection_adapters/postgresql/oid/legacy_point"
+require "active_record/connection_adapters/postgresql/oid/range"
+require "active_record/connection_adapters/postgresql/oid/specialized_string"
+require "active_record/connection_adapters/postgresql/oid/uuid"
+require "active_record/connection_adapters/postgresql/oid/vector"
+require "active_record/connection_adapters/postgresql/oid/xml"
-require_relative "oid/type_map_initializer"
+require "active_record/connection_adapters/postgresql/oid/type_map_initializer"
module ActiveRecord
module ConnectionAdapters
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
index 7d5d7d91e6..a89aa5ea09 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -35,7 +35,7 @@ module ActiveRecord
if value.is_a?(::Range)
from = type_cast_single_for_database(value.begin)
to = type_cast_single_for_database(value.end)
- "[#{from},#{to}#{value.exclude_end? ? ')' : ']'}"
+ ::Range.new(from, to, value.exclude_end?)
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index fc458d0c73..9fdeab06c1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -64,7 +64,7 @@ module ActiveRecord
def quote_default_expression(value, column) # :nodoc:
if value.is_a?(Proc)
value.call
- elsif column.type == :uuid && /\(\)/.match?(value)
+ elsif column.type == :uuid && value.is_a?(String) && /\(\)/.match?(value)
value # Does not quote function default values for UUID columns
elsif column.respond_to?(:array?)
value = type_cast_from_column(column, value)
@@ -101,6 +101,8 @@ module ActiveRecord
end
when OID::Array::Data
_quote(encode_array(value))
+ when Range
+ _quote(encode_range(value))
else
super
end
@@ -117,6 +119,8 @@ module ActiveRecord
value.to_s
when OID::Array::Data
encode_array(value)
+ when Range
+ encode_range(value)
else
super
end
@@ -133,6 +137,10 @@ module ActiveRecord
result
end
+ def encode_range(range)
+ "[#{type_cast(range.first)},#{type_cast(range.last)}#{range.exclude_end? ? ')' : ']'}"
+ end
+
def determine_encoding_of_strings_in_array(value)
case value
when ::Array then determine_encoding_of_strings_in_array(value.first)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
index 386d22a9bd..8df91c988b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb
@@ -4,26 +4,21 @@ module ActiveRecord
module ConnectionAdapters
module PostgreSQL
module ReferentialIntegrity # :nodoc:
- def supports_disable_referential_integrity? # :nodoc:
- true
- end
-
def disable_referential_integrity # :nodoc:
- if supports_disable_referential_integrity?
- original_exception = nil
+ original_exception = nil
- begin
- transaction(requires_new: true) do
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
- end
- rescue ActiveRecord::ActiveRecordError => e
- original_exception = e
+ begin
+ transaction(requires_new: true) do
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
end
+ rescue ActiveRecord::ActiveRecordError => e
+ original_exception = e
+ end
- begin
- yield
- rescue ActiveRecord::InvalidForeignKey => e
- warn <<-WARNING
+ begin
+ yield
+ rescue ActiveRecord::InvalidForeignKey => e
+ warn <<-WARNING
WARNING: Rails was not able to disable referential integrity.
This is most likely caused due to missing permissions.
@@ -32,17 +27,14 @@ Rails needs superuser privileges to disable referential integrity.
cause: #{original_exception.try(:message)}
WARNING
- raise e
- end
+ raise e
+ end
- begin
- transaction(requires_new: true) do
- execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
- end
- rescue ActiveRecord::ActiveRecordError
+ begin
+ transaction(requires_new: true) do
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
end
- else
- yield
+ rescue ActiveRecord::ActiveRecordError
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index f1489e4d69..75622eb304 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -44,15 +44,8 @@ module ActiveRecord
# a record (as primary keys cannot be +nil+). This might be done via the
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
def primary_key(name, type = :primary_key, **options)
- options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)
if type == :uuid
options[:default] = options.fetch(:default, "gen_random_uuid()")
- elsif options.delete(:auto_increment) == true && %i(integer bigint).include?(type)
- type = if type == :bigint || options[:limit] == 8
- :bigserial
- else
- :serial
- end
end
super
@@ -185,6 +178,15 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
+
+ private
+ def integer_like_primary_key_type(type, options)
+ if type == :bigint || options[:limit] == 8
+ :bigserial
+ else
+ :serial
+ end
+ end
end
class Table < ActiveRecord::ConnectionAdapters::Table
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 9e2f61e6ce..846e721983 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/core_ext/string/strip"
-
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
@@ -66,12 +64,7 @@ module ActiveRecord
end
# Verifies existence of an index with a given name.
- def index_name_exists?(table_name, index_name, default = nil)
- unless default.nil?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing default to #index_name_exists? is deprecated without replacement.
- MSG
- end
+ def index_name_exists?(table_name, index_name)
table = quoted_scope(table_name)
index = quoted_scope(index_name)
@@ -89,13 +82,7 @@ module ActiveRecord
end
# Returns an array of indexes for the given table.
- def indexes(table_name, name = nil) # :nodoc:
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing name to #indexes is deprecated without replacement.
- MSG
- end
-
+ def indexes(table_name) # :nodoc:
scope = quoted_scope(table_name)
result = query(<<-SQL, "SCHEMA")
@@ -617,7 +604,8 @@ module ActiveRecord
table_name,
default_function,
collation,
- comment: comment.presence
+ comment: comment.presence,
+ max_identifier_length: max_identifier_length
)
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 4d37a292d6..2c3c1df2a9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -4,20 +4,20 @@
gem "pg", "~> 0.18"
require "pg"
-require_relative "abstract_adapter"
-require_relative "statement_pool"
-require_relative "postgresql/column"
-require_relative "postgresql/database_statements"
-require_relative "postgresql/explain_pretty_printer"
-require_relative "postgresql/oid"
-require_relative "postgresql/quoting"
-require_relative "postgresql/referential_integrity"
-require_relative "postgresql/schema_creation"
-require_relative "postgresql/schema_definitions"
-require_relative "postgresql/schema_dumper"
-require_relative "postgresql/schema_statements"
-require_relative "postgresql/type_metadata"
-require_relative "postgresql/utils"
+require "active_record/connection_adapters/abstract_adapter"
+require "active_record/connection_adapters/statement_pool"
+require "active_record/connection_adapters/postgresql/column"
+require "active_record/connection_adapters/postgresql/database_statements"
+require "active_record/connection_adapters/postgresql/explain_pretty_printer"
+require "active_record/connection_adapters/postgresql/oid"
+require "active_record/connection_adapters/postgresql/quoting"
+require "active_record/connection_adapters/postgresql/referential_integrity"
+require "active_record/connection_adapters/postgresql/schema_creation"
+require "active_record/connection_adapters/postgresql/schema_definitions"
+require "active_record/connection_adapters/postgresql/schema_dumper"
+require "active_record/connection_adapters/postgresql/schema_statements"
+require "active_record/connection_adapters/postgresql/type_metadata"
+require "active_record/connection_adapters/postgresql/utils"
module ActiveRecord
module ConnectionHandling # :nodoc:
@@ -312,14 +312,14 @@ module ActiveRecord
def get_advisory_lock(lock_id) # :nodoc:
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
- raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
end
query_value("SELECT pg_try_advisory_lock(#{lock_id})")
end
def release_advisory_lock(lock_id) # :nodoc:
unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63
- raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer")
+ raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer")
end
query_value("SELECT pg_advisory_unlock(#{lock_id})")
end
@@ -337,25 +337,20 @@ module ActiveRecord
end
def extension_enabled?(name)
- if supports_extensions?
- res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA")
- res.cast_values.first
- end
+ res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA")
+ res.cast_values.first
end
def extensions
- if supports_extensions?
- exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
- else
- super
- end
+ exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
end
# Returns the configured supported identifier length supported by PostgreSQL
- def table_alias_length
+ def max_identifier_length
@max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
end
- alias index_name_length table_alias_length
+ alias table_alias_length max_identifier_length
+ alias index_name_length max_identifier_length
# Set the authorized user for this session
def session_auth=(user)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
index 501f17dbad..c9855019c1 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
@@ -3,27 +3,16 @@
module ActiveRecord
module ConnectionAdapters
module SQLite3
- module ColumnMethods
- def primary_key(name, type = :primary_key, **options)
- if %i(integer bigint).include?(type) && (options.delete(:auto_increment) == true || !options.key?(:default))
- type = :primary_key
- end
-
- super
- end
- end
-
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
- include ColumnMethods
-
def references(*args, **options)
super(*args, type: :integer, **options)
end
alias :belongs_to :references
- end
- class Table < ActiveRecord::ConnectionAdapters::Table
- include ColumnMethods
+ private
+ def integer_like_primary_key_type(type, options)
+ :primary_key
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
index a512702b7b..f0d702136d 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb
@@ -5,13 +5,7 @@ module ActiveRecord
module SQLite3
module SchemaStatements # :nodoc:
# Returns an array of indexes for the given table.
- def indexes(table_name, name = nil)
- if name
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing name to #indexes is deprecated without replacement.
- MSG
- end
-
+ def indexes(table_name)
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
index_sql = query_value(<<-SQL, "SCHEMA")
SELECT sql
@@ -39,10 +33,6 @@ module ActiveRecord
end
end
- def update_table_definition(table_name, base)
- SQLite3::Table.new(table_name, base)
- end
-
def create_schema_dumper(options)
SQLite3::SchemaDumper.create(self, options)
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 6fdd666486..670afa3684 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require_relative "abstract_adapter"
-require_relative "statement_pool"
-require_relative "sqlite3/explain_pretty_printer"
-require_relative "sqlite3/quoting"
-require_relative "sqlite3/schema_creation"
-require_relative "sqlite3/schema_definitions"
-require_relative "sqlite3/schema_dumper"
-require_relative "sqlite3/schema_statements"
+require "active_record/connection_adapters/abstract_adapter"
+require "active_record/connection_adapters/statement_pool"
+require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
+require "active_record/connection_adapters/sqlite3/quoting"
+require "active_record/connection_adapters/sqlite3/schema_creation"
+require "active_record/connection_adapters/sqlite3/schema_definitions"
+require "active_record/connection_adapters/sqlite3/schema_dumper"
+require "active_record/connection_adapters/sqlite3/schema_statements"
gem "sqlite3", "~> 1.3.6"
require "sqlite3"
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 945c4eca78..0f7a503c90 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -76,26 +76,6 @@ module ActiveRecord
# scope being ignored is error-worthy, rather than a warning.
mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
- def self.error_on_ignored_order_or_limit
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The flag error_on_ignored_order_or_limit is deprecated. Limits are
- now supported. Please use error_on_ignored_order instead.
- MSG
- error_on_ignored_order
- end
-
- def error_on_ignored_order_or_limit
- self.class.error_on_ignored_order_or_limit
- end
-
- def self.error_on_ignored_order_or_limit=(value)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- The flag error_on_ignored_order_or_limit is deprecated. Limits are
- now supported. Please use error_on_ignored_order= instead.
- MSG
- self.error_on_ignored_order = value
- end
-
##
# :singleton-method:
# Specify whether or not to use timestamps for migration versions
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index 8bb54a24b7..7ccb938888 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "explain_registry"
+require "active_record/explain_registry"
module ActiveRecord
module Explain
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 9252fa3fed..a86217abc0 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support/notifications"
-require_relative "explain_registry"
+require "active_record/explain_registry"
module ActiveRecord
class ExplainSubscriber # :nodoc:
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 12169fffa9..86f13d75d5 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -6,8 +6,8 @@ require "zlib"
require "set"
require "active_support/dependencies"
require "active_support/core_ext/digest/uuid"
-require_relative "fixture_set/file"
-require_relative "errors"
+require "active_record/fixture_set/file"
+require "active_record/errors"
module ActiveRecord
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
index 14795cc815..5a65edf27e 100644
--- a/activerecord/lib/active_record/internal_metadata.rb
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "scoping/default"
-require_relative "scoping/named"
+require "active_record/scoping/default"
+require "active_record/scoping/named"
module ActiveRecord
# This class is used to create a table that keeps track of values and keys such
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 72bccd4906..bb85c47e06 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -63,12 +63,13 @@ module ActiveRecord
def lock!(lock = true)
if persisted?
if changed?
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Locking a record with unpersisted changes is deprecated and will raise an
- exception in Rails 5.2. Use `save` to persist the changes, or `reload` to
- discard them explicitly.
+ raise(<<-MSG.squish)
+ Locking a record with unpersisted changes is not supported. Use
+ `save` to persist the changes, or `reload` to discard them
+ explicitly.
MSG
end
+
reload(lock: lock)
end
self
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 52ca4671c2..1485687053 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -354,9 +354,9 @@ module ActiveRecord
# to match the structure of your database.
#
# To roll the database back to a previous migration version, use
- # <tt>rails db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
+ # <tt>rails db:rollback VERSION=X</tt> where <tt>X</tt> is the version to which
# you wish to downgrade. Alternatively, you can also use the STEP option if you
- # wish to rollback last few migrations. <tt>rails db:migrate STEP=2</tt> will rollback
+ # wish to rollback last few migrations. <tt>rails db:rollback STEP=2</tt> will rollback
# the latest two migrations.
#
# If any of the migrations throw an <tt>ActiveRecord::IrreversibleMigration</tt> exception,
@@ -1026,11 +1026,6 @@ module ActiveRecord
new(:up, migrations(migrations_paths), nil)
end
- def schema_migrations_table_name
- SchemaMigration.table_name
- end
- deprecate :schema_migrations_table_name
-
def get_all_versions(connection = Base.connection)
if SchemaMigration.table_exists?
SchemaMigration.all_versions.map(&:to_i)
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index a3a5e0fa16..ac7d506fd1 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -161,8 +161,8 @@ module ActiveRecord
table, columns, options = *args
options ||= {}
- index_name = options[:name]
- options_hash = index_name ? { name: index_name } : { column: columns }
+ options_hash = options.slice(:name, :algorithm)
+ options_hash[:column] = columns if !options_hash[:name]
[:remove_index, [table, options_hash]]
end
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 784292f3f9..c979aaf0a0 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -20,6 +20,11 @@ module ActiveRecord
class V5_0 < V5_1
module TableDefinition
+ def primary_key(name, type = :primary_key, **options)
+ type = :integer if type == :primary_key
+ super
+ end
+
def references(*args, **options)
super(*args, type: :integer, **options)
end
@@ -47,11 +52,8 @@ module ActiveRecord
end
if block_given?
- super(table_name, options) do |t|
- class << t
- prepend TableDefinition
- end
- yield t
+ super do |t|
+ yield compatible_table_definition(t)
end
else
super
@@ -60,21 +62,46 @@ module ActiveRecord
def change_table(table_name, options = {})
if block_given?
- super(table_name, options) do |t|
- class << t
- prepend TableDefinition
- end
- yield t
+ super do |t|
+ yield compatible_table_definition(t)
+ end
+ else
+ super
+ end
+ end
+
+ def create_join_table(table_1, table_2, column_options: {}, **options)
+ column_options.reverse_merge!(type: :integer)
+
+ if block_given?
+ super do |t|
+ yield compatible_table_definition(t)
end
else
super
end
end
+ def add_column(table_name, column_name, type, options = {})
+ if type == :primary_key
+ type = :integer
+ options[:primary_key] = true
+ end
+ super
+ end
+
def add_reference(table_name, ref_name, **options)
super(table_name, ref_name, type: :integer, **options)
end
alias :add_belongs_to :add_reference
+
+ private
+ def compatible_table_definition(t)
+ class << t
+ prepend TableDefinition
+ end
+ t
+ end
end
class V4_2 < V5_0
@@ -93,11 +120,8 @@ module ActiveRecord
def create_table(table_name, options = {})
if block_given?
- super(table_name, options) do |t|
- class << t
- prepend TableDefinition
- end
- yield t
+ super do |t|
+ yield compatible_table_definition(t)
end
else
super
@@ -106,11 +130,8 @@ module ActiveRecord
def change_table(table_name, options = {})
if block_given?
- super(table_name, options) do |t|
- class << t
- prepend TableDefinition
- end
- yield t
+ super do |t|
+ yield compatible_table_definition(t)
end
else
super
@@ -146,6 +167,12 @@ module ActiveRecord
end
private
+ def compatible_table_definition(t)
+ class << t
+ prepend TableDefinition
+ end
+ super
+ end
def index_name_for_remove(table_name, options = {})
index_name = index_name(table_name, options)
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 34c0ef4e75..bed9400f51 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -115,20 +115,6 @@ module ActiveRecord
# If true, the default table name for a Product class will be "products". If false, it would just be "product".
# See table_name for the full rules on table/class naming. This is true, by default.
- ##
- # :singleton-method: ignored_columns
- # :call-seq: ignored_columns
- #
- # The list of columns names the model should ignore. Ignored columns won't have attribute
- # accessors defined, and won't be referenced in SQL queries.
-
- ##
- # :singleton-method: ignored_columns=
- # :call-seq: ignored_columns=(columns)
- #
- # Sets the columns names the model should ignore. Ignored columns won't have attribute
- # accessors defined, and won't be referenced in SQL queries.
-
included do
mattr_accessor :primary_key_prefix_type, instance_writer: false
@@ -138,9 +124,9 @@ module ActiveRecord
class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata"
class_attribute :protected_environments, instance_accessor: false, default: [ "production" ]
class_attribute :pluralize_table_names, instance_writer: false, default: true
- class_attribute :ignored_columns, instance_accessor: false, default: [].freeze
self.inheritance_column = "type"
+ self.ignored_columns = [].freeze
delegate :type_for_attribute, to: :class
@@ -271,6 +257,22 @@ module ActiveRecord
@explicit_inheritance_column = true
end
+ # The list of columns names the model should ignore. Ignored columns won't have attribute
+ # accessors defined, and won't be referenced in SQL queries.
+ def ignored_columns
+ if defined?(@ignored_columns)
+ @ignored_columns
+ else
+ superclass.ignored_columns
+ end
+ end
+
+ # Sets the columns names the model should ignore. Ignored columns won't have attribute
+ # accessors defined, and won't be referenced in SQL queries.
+ def ignored_columns=(columns)
+ @ignored_columns = columns.map(&:to_s)
+ end
+
def sequence_name
if base_class == self
@sequence_name ||= reset_sequence_name
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index fbbf9082cc..a57c60ffac 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -71,6 +71,100 @@ module ActiveRecord
klass.allocate.init_with("attributes" => attributes, "new_record" => false, &block)
end
+ # Updates an object (or multiple objects) and saves it to the database, if validations pass.
+ # The resulting object is returned whether the object was saved successfully to the database or not.
+ #
+ # ==== Parameters
+ #
+ # * +id+ - This should be the id or an array of ids to be updated.
+ # * +attributes+ - This should be a hash of attributes or an array of hashes.
+ #
+ # ==== Examples
+ #
+ # # Updates one record
+ # Person.update(15, user_name: "Samuel", group: "expert")
+ #
+ # # Updates multiple records
+ # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
+ # Person.update(people.keys, people.values)
+ #
+ # # Updates multiple records from the result of a relation
+ # people = Person.where(group: "expert")
+ # people.update(group: "masters")
+ #
+ # Note: Updating a large number of records will run an UPDATE
+ # query for each record, which may cause a performance issue.
+ # When running callbacks is not needed for each record update,
+ # it is preferred to use {update_all}[rdoc-ref:Relation#update_all]
+ # for updating all records in a single query.
+ def update(id = :all, attributes)
+ if id.is_a?(Array)
+ id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }.compact
+ elsif id == :all
+ all.each { |record| record.update(attributes) }
+ else
+ if ActiveRecord::Base === id
+ raise ArgumentError,
+ "You are passing an instance of ActiveRecord::Base to `update`. " \
+ "Please pass the id of the object by calling `.id`."
+ end
+ object = find(id)
+ object.update(attributes)
+ object
+ end
+ rescue RecordNotFound
+ end
+
+ # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
+ # therefore all callbacks and filters are fired off before the object is deleted. This method is
+ # less efficient than #delete but allows cleanup methods and other actions to be run.
+ #
+ # This essentially finds the object (or multiple objects) with the given id, creates a new object
+ # from the attributes, and then calls destroy on it.
+ #
+ # ==== Parameters
+ #
+ # * +id+ - This should be the id or an array of ids to be destroyed.
+ #
+ # ==== Examples
+ #
+ # # Destroy a single object
+ # Todo.destroy(1)
+ #
+ # # Destroy multiple objects
+ # todos = [1,2,3]
+ # Todo.destroy(todos)
+ def destroy(id)
+ if id.is_a?(Array)
+ id.map { |one_id| destroy(one_id) }.compact
+ else
+ find(id).destroy
+ end
+ rescue RecordNotFound
+ end
+
+ # Deletes the row with a primary key matching the +id+ argument, using a
+ # SQL +DELETE+ statement, and returns the number of rows deleted. Active
+ # Record objects are not instantiated, so the object's callbacks are not
+ # executed, including any <tt>:dependent</tt> association options.
+ #
+ # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
+ #
+ # Note: Although it is often much faster than the alternative, #destroy,
+ # skipping callbacks might bypass business logic in your application
+ # that ensures referential integrity or performs other essential jobs.
+ #
+ # ==== Examples
+ #
+ # # Delete a single row
+ # Todo.delete(1)
+ #
+ # # Delete multiple rows
+ # Todo.delete([2,3,4])
+ def delete(id_or_array)
+ where(primary_key => id_or_array).delete_all
+ end
+
private
# Called by +instantiate+ to decide which class to use for a new
# record instance.
@@ -228,7 +322,7 @@ module ActiveRecord
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
- became.instance_variable_set("@mutation_tracker", @mutation_tracker) if defined?(@mutation_tracker)
+ became.instance_variable_set("@mutations_from_database", @mutations_from_database) if defined?(@mutations_from_database)
became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index f780538319..3996d5661f 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -7,7 +7,7 @@ module ActiveRecord
delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
delegate :find_by, :find_by!, to: :all
- delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all
+ delegate :destroy_all, :delete_all, :update_all, to: :all
delegate :find_each, :find_in_batches, :in_batches, to: :all
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or,
:where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending,
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index ead42d64ec..812e1d7a00 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -53,8 +53,8 @@ module ActiveRecord
# to avoid cross references when loading a constant for the
# first time. Also, make it output to STDERR.
console do |app|
- require_relative "railties/console_sandbox" if app.sandbox?
- require_relative "base"
+ require "active_record/railties/console_sandbox" if app.sandbox?
+ require "active_record/base"
unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
console = ActiveSupport::Logger.new(STDERR)
Rails.logger.extend ActiveSupport::Logger.broadcast console
@@ -62,7 +62,7 @@ module ActiveRecord
end
runner do
- require_relative "base"
+ require "active_record/base"
end
initializer "active_record.initialize_timezone" do
@@ -106,7 +106,7 @@ module ActiveRecord
initializer "active_record.warn_on_records_fetched_greater_than" do
if config.active_record.warn_on_records_fetched_greater_than
ActiveSupport.on_load(:active_record) do
- require_relative "relation/record_fetch_warning"
+ require "active_record/relation/record_fetch_warning"
end
end
end
@@ -146,7 +146,7 @@ end_warning
# Expose database runtime to controller for logging.
initializer "active_record.log_runtime" do
- require_relative "railties/controller_runtime"
+ require "active_record/railties/controller_runtime"
ActiveSupport.on_load(:action_controller) do
include ActiveRecord::Railties::ControllerRuntime
end
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index 3cf66980a5..2ae733f657 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support/core_ext/module/attr_internal"
-require_relative "../log_subscriber"
+require "active_record/log_subscriber"
module ActiveRecord
module Railties # :nodoc:
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 691b3612d8..723272b4b2 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -189,7 +189,7 @@ db_namespace = namespace :db do
namespace :fixtures do
desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task load: [:environment, :load_config] do
- require_relative "../fixtures"
+ require "active_record/fixtures"
base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path
@@ -211,7 +211,7 @@ db_namespace = namespace :db do
# desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
task identify: [:environment, :load_config] do
- require_relative "../fixtures"
+ require "active_record/fixtures"
label, id = ENV["LABEL"], ENV["ID"]
raise "LABEL or ID required" if label.blank? && id.blank?
@@ -237,7 +237,7 @@ db_namespace = namespace :db do
namespace :schema do
desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record"
task dump: [:environment, :load_config] do
- require_relative "../schema_dumper"
+ require "active_record/schema_dumper"
filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema.rb")
File.open(filename, "w:utf-8") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
@@ -313,7 +313,7 @@ db_namespace = namespace :db do
begin
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
ActiveRecord::Schema.verbose = false
- ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :ruby, ENV["SCHEMA"]
+ ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :ruby, ENV["SCHEMA"], "test"
ensure
if should_reconnect
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
@@ -323,7 +323,7 @@ db_namespace = namespace :db do
# desc "Recreate the test database from an existent structure.sql file"
task load_structure: %w(db:test:purge) do
- ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"]
+ ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"], "test"
end
# desc "Empty the test database"
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 00ff20f4d7..8877f762b2 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "active_support/core_ext/string/filters"
-require "active_support/deprecation"
require "concurrent/map"
module ActiveRecord
@@ -138,7 +137,7 @@ module ActiveRecord
# HasAndBelongsToManyReflection
# ThroughReflection
# PolymorphicReflection
- # RuntimeReflection
+ # RuntimeReflection
class AbstractReflection # :nodoc:
def through_reflection?
false
@@ -154,14 +153,6 @@ module ActiveRecord
klass.new(attributes, &block)
end
- def quoted_table_name
- klass.quoted_table_name
- end
-
- def primary_key_type
- klass.type_for_attribute(klass.primary_key)
- end
-
# Returns the class name for the macro.
#
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
@@ -182,11 +173,6 @@ module ActiveRecord
scope ? [scope] : []
end
- def scope_chain
- chain.map(&:scopes)
- end
- deprecate :scope_chain
-
def build_join_constraint(table, foreign_table)
key = join_keys.key
foreign_key = join_keys.foreign_key
@@ -214,7 +200,7 @@ module ActiveRecord
def join_scopes(table, predicate_builder) # :nodoc:
if scope
- [build_scope(table, predicate_builder).instance_exec(&scope)]
+ [scope_for(build_scope(table, predicate_builder))]
else
[]
end
@@ -300,13 +286,17 @@ module ActiveRecord
end
def get_join_keys(association_klass)
- JoinKeys.new(join_pk(association_klass), join_foreign_key)
+ JoinKeys.new(join_primary_key(association_klass), join_foreign_key)
end
def build_scope(table, predicate_builder = predicate_builder(table))
Relation.create(klass, table, predicate_builder)
end
+ def join_primary_key(_)
+ foreign_key
+ end
+
def join_foreign_key
active_record_primary_key
end
@@ -321,10 +311,6 @@ module ActiveRecord
PredicateBuilder.new(TableMetadata.new(klass, table))
end
- def join_pk(_)
- foreign_key
- end
-
def primary_key(klass)
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
end
@@ -373,6 +359,17 @@ module ActiveRecord
#
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
# <tt>has_many :clients</tt> returns the Client class
+ #
+ # class Company < ActiveRecord::Base
+ # has_many :clients
+ # end
+ #
+ # Company.reflect_on_association(:clients).klass
+ # # => Client
+ #
+ # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
+ # a new association object. Use +build_association+ or +create_association+
+ # instead. This allows plugins to hook into association object creation.
def klass
@klass ||= compute_class(class_name)
end
@@ -391,8 +388,8 @@ module ActiveRecord
active_record == other_aggregation.active_record
end
- def scope_for(klass)
- scope ? klass.unscoped.instance_exec(nil, &scope) : klass.unscoped
+ def scope_for(relation, owner = nil)
+ relation.instance_exec(owner, &scope) || relation
end
private
@@ -413,22 +410,6 @@ module ActiveRecord
# Holds all the metadata about an association as it was specified in the
# Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
- # Returns the target association's class.
- #
- # class Author < ActiveRecord::Base
- # has_many :books
- # end
- #
- # Author.reflect_on_association(:books).klass
- # # => Book
- #
- # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
- # a new association object. Use +build_association+ or +create_association+
- # instead. This allows plugins to hook into association object creation.
- def klass
- @klass ||= compute_class(class_name)
- end
-
def compute_class(name)
active_record.send(:compute_type, name)
end
@@ -438,21 +419,13 @@ module ActiveRecord
def initialize(name, scope, options, active_record)
super
- @automatic_inverse_of = nil
@type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
- @foreign_type = options[:foreign_type] || "#{name}_type"
+ @foreign_type = options[:polymorphic] && (options[:foreign_type] || "#{name}_type")
@constructable = calculate_constructable(macro, options)
@association_scope_cache = Concurrent::Map.new
if options[:class_name] && options[:class_name].class == Class
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing a class to the `class_name` is deprecated and will raise
- an ArgumentError in Rails 5.2. It eagerloads more classes than
- necessary and potentially creates circular dependencies.
-
- Please pass the class name as a string:
- `#{macro} :#{name}, class_name: '#{options[:class_name]}'`
- MSG
+ raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string."
end
end
@@ -622,12 +595,14 @@ module ActiveRecord
# If it cannot find a suitable inverse association name, it returns
# +nil+.
def inverse_name
- options.fetch(:inverse_of) do
- @automatic_inverse_of ||= automatic_inverse_of
+ unless defined?(@inverse_name)
+ @inverse_name = options.fetch(:inverse_of) { automatic_inverse_of }
end
+
+ @inverse_name
end
- # returns either false or the inverse association name that it finds.
+ # returns either +nil+ or the inverse association name that it finds.
def automatic_inverse_of
if can_find_inverse_of_automatically?(self)
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
@@ -644,8 +619,6 @@ module ActiveRecord
return inverse_name
end
end
-
- false
end
# Checks if the inverse reflection that is returned from the
@@ -749,6 +722,10 @@ module ActiveRecord
end
end
+ def join_primary_key(klass)
+ polymorphic? ? association_primary_key(klass) : association_primary_key
+ end
+
def join_foreign_key
foreign_key
end
@@ -758,10 +735,6 @@ module ActiveRecord
def calculate_constructable(macro, options)
!polymorphic?
end
-
- def join_pk(klass)
- polymorphic? ? association_primary_key(klass) : association_primary_key
- end
end
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
@@ -1029,6 +1002,8 @@ module ActiveRecord
end
class PolymorphicReflection < AbstractReflection # :nodoc:
+ delegate :klass, :scope, :plural_name, :type, :get_join_keys, to: :@reflection
+
def initialize(reflection, previous_reflection)
@reflection = reflection
@previous_reflection = previous_reflection
@@ -1044,30 +1019,10 @@ module ActiveRecord
scopes << @previous_reflection.source_type_scope
end
- def klass
- @reflection.klass
- end
-
- def scope
- @reflection.scope
- end
-
- def plural_name
- @reflection.plural_name
- end
-
- def type
- @reflection.type
- end
-
def constraints
@reflection.constraints + [source_type_info]
end
- def get_join_keys(association_klass)
- @reflection.get_join_keys(association_klass)
- end
-
private
def source_type_info
type = @previous_reflection.foreign_type
@@ -1076,8 +1031,8 @@ module ActiveRecord
end
end
- class RuntimeReflection < PolymorphicReflection # :nodoc:
- attr_accessor :next
+ class RuntimeReflection < AbstractReflection # :nodoc:
+ delegate :scope, :type, :constraints, :get_join_keys, to: :@reflection
def initialize(reflection, association)
@reflection = reflection
@@ -1088,12 +1043,8 @@ module ActiveRecord
@association.klass
end
- def constraints
- @reflection.constraints
- end
-
- def alias_name
- Arel::Table.new(table_name, type_caster: klass.type_caster)
+ def aliased_table
+ @aliased_table ||= Arel::Table.new(table_name, type_caster: klass.type_caster)
end
def all_includes; yield; end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 012bc838b1..997cfe4b5e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -63,7 +63,7 @@ module ActiveRecord
@klass.connection.insert(
im,
- "SQL",
+ "#{@klass} Create",
primary_key || false,
primary_key_value,
nil,
@@ -86,7 +86,7 @@ module ActiveRecord
@klass.connection.update(
um,
- "SQL",
+ "#{@klass} Update",
)
end
@@ -373,51 +373,7 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- @klass.connection.update stmt, "SQL"
- end
-
- # Updates an object (or multiple objects) and saves it to the database, if validations pass.
- # The resulting object is returned whether the object was saved successfully to the database or not.
- #
- # ==== Parameters
- #
- # * +id+ - This should be the id or an array of ids to be updated.
- # * +attributes+ - This should be a hash of attributes or an array of hashes.
- #
- # ==== Examples
- #
- # # Updates one record
- # Person.update(15, user_name: 'Samuel', group: 'expert')
- #
- # # Updates multiple records
- # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
- # Person.update(people.keys, people.values)
- #
- # # Updates multiple records from the result of a relation
- # people = Person.where(group: 'expert')
- # people.update(group: 'masters')
- #
- # Note: Updating a large number of records will run an
- # UPDATE query for each record, which may cause a performance
- # issue. When running callbacks is not needed for each record update,
- # it is preferred to use #update_all for updating all records
- # in a single query.
- def update(id = :all, attributes)
- if id.is_a?(Array)
- id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
- elsif id == :all
- records.each { |record| record.update(attributes) }
- else
- if ActiveRecord::Base === id
- raise ArgumentError, <<-MSG.squish
- You are passing an instance of ActiveRecord::Base to `update`.
- Please pass the id of the object by calling `.id`.
- MSG
- end
- object = find(id)
- object.update(attributes)
- object
- end
+ @klass.connection.update stmt, "#{@klass} Update All"
end
# Destroys the records by instantiating each
@@ -440,33 +396,6 @@ module ActiveRecord
records.each(&:destroy).tap { reset }
end
- # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
- # therefore all callbacks and filters are fired off before the object is deleted. This method is
- # less efficient than #delete but allows cleanup methods and other actions to be run.
- #
- # This essentially finds the object (or multiple objects) with the given id, creates a new object
- # from the attributes, and then calls destroy on it.
- #
- # ==== Parameters
- #
- # * +id+ - Can be either an Integer or an Array of Integers.
- #
- # ==== Examples
- #
- # # Destroy a single object
- # Todo.destroy(1)
- #
- # # Destroy multiple objects
- # todos = [1,2,3]
- # Todo.destroy(todos)
- def destroy(id)
- if id.is_a?(Array)
- id.map { |one_id| destroy(one_id) }
- else
- find(id).destroy
- end
- end
-
# Deletes the records without instantiating the records
# first, and hence not calling the {#destroy}[rdoc-ref:Persistence#destroy]
# method nor invoking callbacks.
@@ -503,35 +432,12 @@ module ActiveRecord
stmt.wheres = arel.constraints
end
- affected = @klass.connection.delete(stmt, "SQL")
+ affected = @klass.connection.delete(stmt, "#{@klass} Destroy")
reset
affected
end
- # Deletes the row with a primary key matching the +id+ argument, using a
- # SQL +DELETE+ statement, and returns the number of rows deleted. Active
- # Record objects are not instantiated, so the object's callbacks are not
- # executed, including any <tt>:dependent</tt> association options.
- #
- # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
- #
- # Note: Although it is often much faster than the alternative,
- # #destroy, skipping callbacks might bypass business logic in
- # your application that ensures referential integrity or performs other
- # essential jobs.
- #
- # ==== Examples
- #
- # # Delete a single row
- # Todo.delete(1)
- #
- # # Delete multiple rows
- # Todo.delete([2,3,4])
- def delete(id_or_array)
- where(primary_key => id_or_array).delete_all
- end
-
# Causes the records to be loaded from the database if they have not
# been loaded already. You can use this if for some reason you need
# to explicitly load some records before actually using them. The
@@ -580,7 +486,7 @@ module ActiveRecord
#
# User.where(name: 'Oscar').where_values_hash
# # => {name: "Oscar"}
- def where_values_hash(relation_table_name = table_name)
+ def where_values_hash(relation_table_name = klass.table_name)
where_clause.to_h(relation_table_name)
end
@@ -645,6 +551,11 @@ module ActiveRecord
limit_value || offset_value
end
+ def alias_tracker(joins = [], aliases = nil) # :nodoc:
+ joins += [aliases] if aliases
+ ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins)
+ end
+
protected
def load_records(records)
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index fa19c679cf..561869017a 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "batches/batch_enumerator"
+require "active_record/relation/batches/batch_enumerator"
module ActiveRecord
module Batches
@@ -251,20 +251,27 @@ module ActiveRecord
end
end
- batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
+ attr = Relation::QueryAttribute.new(primary_key, primary_key_offset, klass.type_for_attribute(primary_key))
+ batch_relation = relation.where(arel_attribute(primary_key).gt(Arel::Nodes::BindParam.new(attr)))
end
end
private
def apply_limits(relation, start, finish)
- relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
- relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
+ if start
+ attr = Relation::QueryAttribute.new(primary_key, start, klass.type_for_attribute(primary_key))
+ relation = relation.where(arel_attribute(primary_key).gteq(Arel::Nodes::BindParam.new(attr)))
+ end
+ if finish
+ attr = Relation::QueryAttribute.new(primary_key, finish, klass.type_for_attribute(primary_key))
+ relation = relation.where(arel_attribute(primary_key).lteq(Arel::Nodes::BindParam.new(attr)))
+ end
relation
end
def batch_order
- "#{quoted_table_name}.#{quoted_primary_key} ASC"
+ arel_attribute(primary_key).asc
end
def act_on_ignored_order(error_on_ignore)
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 42d43224fa..116bddce85 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -130,7 +130,7 @@ module ActiveRecord
# end
def calculate(operation, column_name)
if has_include?(column_name)
- relation = construct_relation_for_association_calculations
+ relation = apply_join_dependency(construct_join_dependency)
relation.distinct! if operation.to_s.downcase == "count"
relation.calculate(operation, column_name)
@@ -180,7 +180,8 @@ module ActiveRecord
end
if has_include?(column_names.first)
- construct_relation_for_association_calculations.pluck(*column_names)
+ relation = apply_join_dependency(construct_join_dependency)
+ relation.pluck(*column_names)
else
relation = spawn
relation.select_values = column_names.map { |cn|
@@ -215,7 +216,7 @@ module ActiveRecord
if operation == "count"
column_name ||= select_for_count
if column_name == :all
- if distinct && !(has_limit_or_offset? && order_values.any?)
+ if distinct && (group_values.any? || !(has_limit_or_offset? && order_values.any?))
column_name = primary_key
end
elsif column_name =~ /\s*DISTINCT[\s(]+/i
@@ -391,7 +392,7 @@ module ActiveRecord
def build_count_subquery(relation, column_name, distinct)
relation.select_values = [
if column_name == :all
- distinct ? table[Arel.star] : Arel.sql("1")
+ distinct ? table[Arel.star] : Arel.sql(FinderMethods::ONE_AS_ONE)
else
column_alias = Arel.sql("count_column")
aggregate_column(column_name).as(column_alias)
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index c5354bf4e9..48af777b69 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -43,8 +43,7 @@ module ActiveRecord
:to_sentence, :to_formatted_s, :as_json,
:shuffle, :split, :slice, :index, :rindex, to: :records
- delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
- :connection, :columns_hash, to: :klass
+ delegate :primary_key, :connection, to: :klass
module ClassSpecificRelation # :nodoc:
extend ActiveSupport::Concern
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 2aed941916..707245bab2 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -310,12 +310,12 @@ module ActiveRecord
return false if !conditions || limit_value == 0
- relation = self unless eager_loading?
- relation ||= apply_join_dependency(self, construct_join_dependency(eager_loading: false))
-
- return false if ActiveRecord::NullRelation === relation
+ if eager_loading?
+ relation = apply_join_dependency(construct_join_dependency(eager_loading: false))
+ return relation.exists?(conditions)
+ end
- relation = construct_relation_for_exists(relation, conditions)
+ relation = construct_relation_for_exists(conditions)
skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false
rescue ::RangeError
@@ -366,17 +366,16 @@ module ActiveRecord
# preexisting join in joins_values to categorizations (by way of
# the `has_many :through` for categories).
#
- join_dependency = construct_join_dependency(joins_values)
+ join_dependency = construct_join_dependency
- aliases = join_dependency.aliases
- relation = select aliases.columns
- relation = apply_join_dependency(relation, join_dependency)
+ relation = apply_join_dependency(join_dependency)
+ relation._select!(join_dependency.aliases.columns)
yield relation, join_dependency
end
- def construct_relation_for_exists(relation, conditions)
- relation = relation.except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
+ def construct_relation_for_exists(conditions)
+ relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
case conditions
when Array, Hash
@@ -388,17 +387,15 @@ module ActiveRecord
relation
end
- def construct_join_dependency(joins = [], eager_loading: true)
+ def construct_join_dependency(eager_loading: true)
including = eager_load_values + includes_values
- ActiveRecord::Associations::JoinDependency.new(klass, table, including, joins, eager_loading: eager_loading)
- end
-
- def construct_relation_for_association_calculations
- apply_join_dependency(self, construct_join_dependency(joins_values))
+ ActiveRecord::Associations::JoinDependency.new(
+ klass, table, including, alias_tracker(joins_values), eager_loading: eager_loading
+ )
end
- def apply_join_dependency(relation, join_dependency)
- relation = relation.except(:includes, :eager_load, :preload).joins!(join_dependency)
+ def apply_join_dependency(join_dependency)
+ relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
if using_limitable_reflections?(join_dependency.reflections)
relation
@@ -413,7 +410,9 @@ module ActiveRecord
def limited_ids_for(relation)
values = @klass.connection.columns_for_distinct(
- "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values)
+ connection.column_name_from_arel_node(arel_attribute(primary_key)),
+ relation.order_values
+ )
relation = relation.except(:select).select(values).distinct!
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 03824ffff9..ebc72d28fd 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -122,7 +122,7 @@ module ActiveRecord
end
join_dependency = ActiveRecord::Associations::JoinDependency.new(
- other.klass, other.table, joins_dependency, []
+ other.klass, other.table, joins_dependency, other.alias_tracker
)
relation.joins! rest
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 5c42414072..885c26d7aa 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -13,6 +13,7 @@ module ActiveRecord
register_handler(Range, RangeHandler.new(self))
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
+ register_handler(Set, ArrayHandler.new(self))
end
def build_from_hash(attributes)
@@ -123,11 +124,11 @@ module ActiveRecord
end
end
-require_relative "predicate_builder/array_handler"
-require_relative "predicate_builder/base_handler"
-require_relative "predicate_builder/basic_object_handler"
-require_relative "predicate_builder/range_handler"
-require_relative "predicate_builder/relation_handler"
+require "active_record/relation/predicate_builder/array_handler"
+require "active_record/relation/predicate_builder/base_handler"
+require "active_record/relation/predicate_builder/basic_object_handler"
+require "active_record/relation/predicate_builder/range_handler"
+require "active_record/relation/predicate_builder/relation_handler"
-require_relative "predicate_builder/association_query_value"
-require_relative "predicate_builder/polymorphic_array_value"
+require "active_record/relation/predicate_builder/association_query_value"
+require "active_record/relation/predicate_builder/polymorphic_array_value"
diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb
index 5a9a7fd432..fad08e2613 100644
--- a/activerecord/lib/active_record/relation/query_attribute.rb
+++ b/activerecord/lib/active_record/relation/query_attribute.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../attribute"
+require "active_record/attribute"
module ActiveRecord
class Relation
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index bdc5c27328..897ff5c8af 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require_relative "from_clause"
-require_relative "query_attribute"
-require_relative "where_clause"
-require_relative "where_clause_factory"
+require "active_record/relation/from_clause"
+require "active_record/relation/query_attribute"
+require "active_record/relation/where_clause"
+require "active_record/relation/where_clause_factory"
require "active_model/forbidden_attributes_protection"
module ActiveRecord
@@ -441,7 +441,7 @@ module ActiveRecord
# => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id"
#
def left_outer_joins(*args)
- check_if_method_has_arguments!(:left_outer_joins, args)
+ check_if_method_has_arguments!(__callee__, args)
args.compact!
args.flatten!
@@ -898,8 +898,8 @@ module ActiveRecord
end
# Returns the Arel object associated with the relation.
- def arel # :nodoc:
- @arel ||= build_arel
+ def arel(aliases = nil) # :nodoc:
+ @arel ||= build_arel(aliases)
end
protected
@@ -921,11 +921,11 @@ module ActiveRecord
raise ImmutableRelation if defined?(@arel) && @arel
end
- def build_arel
+ def build_arel(aliases)
arel = Arel::SelectManager.new(table)
- build_joins(arel, joins_values.flatten) unless joins_values.empty?
- build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty?
+ aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty?
+ build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
arel.where(where_clause.ast) unless where_clause.empty?
arel.having(having_clause.ast) unless having_clause.empty?
@@ -970,7 +970,7 @@ module ActiveRecord
end
end
- def build_left_outer_joins(manager, outer_joins)
+ def build_left_outer_joins(manager, outer_joins, aliases)
buckets = outer_joins.group_by do |join|
case join
when Hash, Symbol, Array
@@ -980,10 +980,10 @@ module ActiveRecord
end
end
- build_join_query(manager, buckets, Arel::Nodes::OuterJoin)
+ build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
end
- def build_joins(manager, joins)
+ def build_joins(manager, joins, aliases)
buckets = joins.group_by do |join|
case join
when String
@@ -999,10 +999,10 @@ module ActiveRecord
end
end
- build_join_query(manager, buckets, Arel::Nodes::InnerJoin)
+ build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases)
end
- def build_join_query(manager, buckets, join_type)
+ def build_join_query(manager, buckets, join_type, aliases)
buckets.default = []
association_joins = buckets[:association_join]
@@ -1011,9 +1011,10 @@ module ActiveRecord
string_joins = buckets[:string_join].map(&:strip).uniq
join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins)
+ alias_tracker = alias_tracker(join_list, aliases)
join_dependency = ActiveRecord::Associations::JoinDependency.new(
- klass, table, association_joins, join_list
+ klass, table, association_joins, alias_tracker
)
joins = join_dependency.join_constraints(stashed_association_joins, join_type)
@@ -1021,7 +1022,7 @@ module ActiveRecord
manager.join_sources.concat(join_list)
- manager
+ alias_tracker.aliases
end
def convert_join_strings_to_ast(table, joins)
@@ -1172,21 +1173,24 @@ module ActiveRecord
end
alias having_clause_factory where_clause_factory
+ DEFAULT_VALUES = {
+ create_with: FROZEN_EMPTY_HASH,
+ readonly: false,
+ where: Relation::WhereClause.empty,
+ having: Relation::WhereClause.empty,
+ from: Relation::FromClause.empty
+ }
+
+ Relation::MULTI_VALUE_METHODS.each do |value|
+ DEFAULT_VALUES[value] ||= FROZEN_EMPTY_ARRAY
+ end
+
+ Relation::SINGLE_VALUE_METHODS.each do |value|
+ DEFAULT_VALUES[value] = nil if DEFAULT_VALUES[value].nil?
+ end
+
def default_value_for(name)
- case name
- when :create_with
- FROZEN_EMPTY_HASH
- when :readonly
- false
- when :where, :having
- Relation::WhereClause.empty
- when :from
- Relation::FromClause.empty
- when *Relation::MULTI_VALUE_METHODS
- FROZEN_EMPTY_ARRAY
- when *Relation::SINGLE_VALUE_METHODS
- nil
- else
+ DEFAULT_VALUES.fetch(name) do
raise ArgumentError, "unknown relation value #{name.inspect}"
end
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 424894f835..617d8de8b2 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -2,7 +2,7 @@
require "active_support/core_ext/hash/except"
require "active_support/core_ext/hash/slice"
-require_relative "merger"
+require "active_record/relation/merger"
module ActiveRecord
module SpawnMethods
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 91a4f1fad6..1c3099f55c 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -30,8 +30,6 @@ module ActiveRecord
end
end
alias :sanitize_sql :sanitize_sql_for_conditions
- alias :sanitize_conditions :sanitize_sql
- deprecate sanitize_conditions: :sanitize_sql
# Accepts an array, hash, or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a SET clause.
@@ -207,10 +205,5 @@ module ActiveRecord
end
end
end
-
- def quoted_id # :nodoc:
- self.class.connection.quote(@attributes[self.class.primary_key].value_for_database)
- end
- deprecate :quoted_id
end
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 339a5334a8..f2d8b038fa 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "scoping/default"
-require_relative "scoping/named"
+require "active_record/scoping/default"
+require "active_record/scoping/named"
module ActiveRecord
# This class is used to create a table that keeps track of which migrations
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 0f3f84ca08..ff388ff1f6 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -225,22 +225,22 @@ module ActiveRecord
class_for_adapter(configuration["adapter"]).new(*arguments).structure_load(filename, structure_load_flags)
end
- def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
+ def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env) # :nodoc:
file ||= schema_file(format)
+ check_schema_file(file)
+ ActiveRecord::Base.establish_connection(configuration)
+
case format
when :ruby
- check_schema_file(file)
- ActiveRecord::Base.establish_connection(configuration)
load(file)
when :sql
- check_schema_file(file)
structure_load(configuration, file)
else
raise ArgumentError, "unknown format #{format.inspect}"
end
ActiveRecord::InternalMetadata.create_table
- ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ ActiveRecord::InternalMetadata[:environment] = environment
end
def schema_file(format = ActiveRecord::Base.schema_format)
@@ -253,8 +253,8 @@ module ActiveRecord
end
def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
- each_current_configuration(environment) { |configuration|
- load_schema configuration, format, file
+ each_current_configuration(environment) { |configuration, configuration_environment|
+ load_schema configuration, format, file, configuration_environment
}
ActiveRecord::Base.establish_connection(environment.to_sym)
end
@@ -301,9 +301,10 @@ module ActiveRecord
environments = [environment]
environments << "test" if environment == "development"
- configurations = ActiveRecord::Base.configurations.values_at(*environments)
- configurations.compact.each do |configuration|
- yield configuration unless configuration["database"].blank?
+ ActiveRecord::Base.configurations.slice(*environments).each do |configuration_environment, configuration|
+ next unless configuration["database"]
+
+ yield configuration, configuration_environment
end
end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 84265aa9e3..2f91884f59 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -101,7 +101,7 @@ module ActiveRecord
def error_class
if configuration["adapter"].include?("jdbc")
- require_relative "../railties/jdbcmysql_error"
+ require "active_record/railties/jdbcmysql_error"
ArJdbcMySQL::Error
elsif defined?(Mysql2)
Mysql2::Error
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 6761f2da25..97cba5d1c7 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -285,7 +285,7 @@ module ActiveRecord
fire_on = Array(options[:on])
assert_valid_transaction_action(fire_on)
options[:if] = Array(options[:if])
- options[:if].unshift("transaction_include_any_action?(#{fire_on})")
+ options[:if].unshift(-> { transaction_include_any_action?(fire_on) })
end
end
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index fa22df92b8..c303186ef2 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -2,21 +2,21 @@
require "active_model/type"
-require_relative "type/internal/timezone"
+require "active_record/type/internal/timezone"
-require_relative "type/date"
-require_relative "type/date_time"
-require_relative "type/decimal_without_scale"
-require_relative "type/json"
-require_relative "type/time"
-require_relative "type/text"
-require_relative "type/unsigned_integer"
+require "active_record/type/date"
+require "active_record/type/date_time"
+require "active_record/type/decimal_without_scale"
+require "active_record/type/json"
+require "active_record/type/time"
+require "active_record/type/text"
+require "active_record/type/unsigned_integer"
-require_relative "type/serialized"
-require_relative "type/adapter_specific_registry"
+require "active_record/type/serialized"
+require "active_record/type/adapter_specific_registry"
-require_relative "type/type_map"
-require_relative "type/hash_lookup_type_map"
+require "active_record/type/type_map"
+require "active_record/type/hash_lookup_type_map"
module ActiveRecord
module Type
diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb
index ed2e4fb79c..2e5f45fa3d 100644
--- a/activerecord/lib/active_record/type_caster.rb
+++ b/activerecord/lib/active_record/type_caster.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "type_caster/map"
-require_relative "type_caster/connection"
+require "active_record/type_caster/map"
+require "active_record/type_caster/connection"
module ActiveRecord
module TypeCaster # :nodoc:
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 3f5c879f2f..ca27a3f0ab 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -86,8 +86,8 @@ module ActiveRecord
end
end
-require_relative "validations/associated"
-require_relative "validations/uniqueness"
-require_relative "validations/presence"
-require_relative "validations/absence"
-require_relative "validations/length"
+require "active_record/validations/associated"
+require "active_record/validations/uniqueness"
+require "active_record/validations/presence"
+require "active_record/validations/absence"
+require "active_record/validations/length"
diff --git a/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb b/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb
new file mode 100644
index 0000000000..4c67633946
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/auto_increment_test.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "support/schema_dumping_helper"
+
+class Mysql2AutoIncrementTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table :auto_increments, if_exists: true
+ end
+
+ def test_auto_increment_without_primary_key
+ @connection.create_table :auto_increments, id: false, force: true do |t|
+ t.integer :id, null: false, auto_increment: true
+ t.index :id
+ end
+ output = dump_table_schema("auto_increments")
+ assert_match(/t\.integer\s+"id",\s+null: false,\s+auto_increment: true$/, output)
+ end
+
+ def test_auto_increment_with_composite_primary_key
+ @connection.create_table :auto_increments, primary_key: [:id, :created_at], force: true do |t|
+ t.integer :id, null: false, auto_increment: true
+ t.datetime :created_at, null: false
+ end
+ output = dump_table_schema("auto_increments")
+ assert_match(/t\.integer\s+"id",\s+null: false,\s+auto_increment: true$/, output)
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 9d81d506a0..e61c70848a 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -65,18 +65,6 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
assert @connection.active?
end
- def test_verify_with_args_is_deprecated
- assert_deprecated do
- @connection.verify!(option: true)
- end
- assert_deprecated do
- @connection.verify!([])
- end
- assert_deprecated do
- @connection.verify!({})
- end
- end
-
def test_execute_after_disconnect
@connection.disconnect!
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index 050614cade..a25f102bad 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -3,78 +3,76 @@
require "cases/helper"
require "support/schema_dumping_helper"
-if ActiveRecord::Base.connection.supports_extensions?
- class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase
- include SchemaDumpingHelper
- class Citext < ActiveRecord::Base
- self.table_name = "citexts"
- end
-
- def setup
- @connection = ActiveRecord::Base.connection
+class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+ class Citext < ActiveRecord::Base
+ self.table_name = "citexts"
+ end
- enable_extension!("citext", @connection)
+ def setup
+ @connection = ActiveRecord::Base.connection
- @connection.create_table("citexts") do |t|
- t.citext "cival"
- end
- end
+ enable_extension!("citext", @connection)
- teardown do
- @connection.drop_table "citexts", if_exists: true
- disable_extension!("citext", @connection)
+ @connection.create_table("citexts") do |t|
+ t.citext "cival"
end
+ end
- def test_citext_enabled
- assert @connection.extension_enabled?("citext")
- end
+ teardown do
+ @connection.drop_table "citexts", if_exists: true
+ disable_extension!("citext", @connection)
+ end
- def test_column
- column = Citext.columns_hash["cival"]
- assert_equal :citext, column.type
- assert_equal "citext", column.sql_type
- assert_not column.array?
+ def test_citext_enabled
+ assert @connection.extension_enabled?("citext")
+ end
- type = Citext.type_for_attribute("cival")
- assert_not type.binary?
- end
+ def test_column
+ column = Citext.columns_hash["cival"]
+ assert_equal :citext, column.type
+ assert_equal "citext", column.sql_type
+ assert_not column.array?
- def test_change_table_supports_json
- @connection.transaction do
- @connection.change_table("citexts") do |t|
- t.citext "username"
- end
- Citext.reset_column_information
- column = Citext.columns_hash["username"]
- assert_equal :citext, column.type
+ type = Citext.type_for_attribute("cival")
+ assert_not type.binary?
+ end
- raise ActiveRecord::Rollback # reset the schema change
+ def test_change_table_supports_json
+ @connection.transaction do
+ @connection.change_table("citexts") do |t|
+ t.citext "username"
end
- ensure
Citext.reset_column_information
+ column = Citext.columns_hash["username"]
+ assert_equal :citext, column.type
+
+ raise ActiveRecord::Rollback # reset the schema change
end
+ ensure
+ Citext.reset_column_information
+ end
- def test_write
- x = Citext.new(cival: "Some CI Text")
- x.save!
- citext = Citext.first
- assert_equal "Some CI Text", citext.cival
+ def test_write
+ x = Citext.new(cival: "Some CI Text")
+ x.save!
+ citext = Citext.first
+ assert_equal "Some CI Text", citext.cival
- citext.cival = "Some NEW CI Text"
- citext.save!
+ citext.cival = "Some NEW CI Text"
+ citext.save!
- assert_equal "Some NEW CI Text", citext.reload.cival
- end
+ assert_equal "Some NEW CI Text", citext.reload.cival
+ end
- def test_select_case_insensitive
- @connection.execute "insert into citexts (cival) values('Cased Text')"
- x = Citext.where(cival: "cased text").first
- assert_equal "Cased Text", x.cival
- end
+ def test_select_case_insensitive
+ @connection.execute "insert into citexts (cival) values('Cased Text')"
+ x = Citext.where(cival: "cased text").first
+ assert_equal "Cased Text", x.cival
+ end
- def test_schema_dump_with_shorthand
- output = dump_table_schema("citexts")
- assert_match %r[t\.citext "cival"], output
- end
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("citexts")
+ assert_match %r[t\.citext "cival"], output
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 2bb217a8b1..81358b8fc4 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -103,7 +103,7 @@ module ActiveRecord
end
def test_indexes_logs_name
- assert_deprecated { @connection.indexes("items", "hello") }
+ @connection.indexes("items")
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
index e589e3ab1b..df97ab11e7 100644
--- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb
@@ -22,10 +22,6 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase
@connection = ActiveRecord::Base.connection
- unless @connection.supports_extensions?
- return skip("no extension support")
- end
-
@old_schema_migration_table_name = ActiveRecord::SchemaMigration.table_name
@old_table_name_prefix = ActiveRecord::Base.table_name_prefix
@old_table_name_suffix = ActiveRecord::Base.table_name_suffix
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index 97a8a257c5..f09e34b5f2 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -3,378 +3,376 @@
require "cases/helper"
require "support/schema_dumping_helper"
-if ActiveRecord::Base.connection.supports_extensions?
- class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
- include SchemaDumpingHelper
- class Hstore < ActiveRecord::Base
- self.table_name = "hstores"
+class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+ class Hstore < ActiveRecord::Base
+ self.table_name = "hstores"
- store_accessor :settings, :language, :timezone
- end
+ store_accessor :settings, :language, :timezone
+ end
- class FakeParameters
- def to_unsafe_h
- { "hi" => "hi" }
- end
+ class FakeParameters
+ def to_unsafe_h
+ { "hi" => "hi" }
end
+ end
- def setup
- @connection = ActiveRecord::Base.connection
+ def setup
+ @connection = ActiveRecord::Base.connection
- enable_extension!("hstore", @connection)
+ enable_extension!("hstore", @connection)
- @connection.transaction do
- @connection.create_table("hstores") do |t|
- t.hstore "tags", default: ""
- t.hstore "payload", array: true
- t.hstore "settings"
- end
+ @connection.transaction do
+ @connection.create_table("hstores") do |t|
+ t.hstore "tags", default: ""
+ t.hstore "payload", array: true
+ t.hstore "settings"
end
- Hstore.reset_column_information
- @column = Hstore.columns_hash["tags"]
- @type = Hstore.type_for_attribute("tags")
- end
-
- teardown do
- @connection.drop_table "hstores", if_exists: true
- disable_extension!("hstore", @connection)
end
+ Hstore.reset_column_information
+ @column = Hstore.columns_hash["tags"]
+ @type = Hstore.type_for_attribute("tags")
+ end
- def test_hstore_included_in_extensions
- assert @connection.respond_to?(:extensions), "connection should have a list of extensions"
- assert_includes @connection.extensions, "hstore", "extension list should include hstore"
- end
+ teardown do
+ @connection.drop_table "hstores", if_exists: true
+ disable_extension!("hstore", @connection)
+ end
- def test_disable_enable_hstore
- assert @connection.extension_enabled?("hstore")
- @connection.disable_extension "hstore"
- assert_not @connection.extension_enabled?("hstore")
- @connection.enable_extension "hstore"
- assert @connection.extension_enabled?("hstore")
- ensure
- # Restore column(s) dropped by `drop extension hstore cascade;`
- load_schema
- end
+ def test_hstore_included_in_extensions
+ assert @connection.respond_to?(:extensions), "connection should have a list of extensions"
+ assert_includes @connection.extensions, "hstore", "extension list should include hstore"
+ end
- def test_column
- assert_equal :hstore, @column.type
- assert_equal "hstore", @column.sql_type
- assert_not @column.array?
+ def test_disable_enable_hstore
+ assert @connection.extension_enabled?("hstore")
+ @connection.disable_extension "hstore"
+ assert_not @connection.extension_enabled?("hstore")
+ @connection.enable_extension "hstore"
+ assert @connection.extension_enabled?("hstore")
+ ensure
+ # Restore column(s) dropped by `drop extension hstore cascade;`
+ load_schema
+ end
- assert_not @type.binary?
- end
+ def test_column
+ assert_equal :hstore, @column.type
+ assert_equal "hstore", @column.sql_type
+ assert_not @column.array?
- def test_default
- @connection.add_column "hstores", "permissions", :hstore, default: '"users"=>"read", "articles"=>"write"'
- Hstore.reset_column_information
+ assert_not @type.binary?
+ end
- assert_equal({ "users" => "read", "articles" => "write" }, Hstore.column_defaults["permissions"])
- assert_equal({ "users" => "read", "articles" => "write" }, Hstore.new.permissions)
- ensure
- Hstore.reset_column_information
- end
+ def test_default
+ @connection.add_column "hstores", "permissions", :hstore, default: '"users"=>"read", "articles"=>"write"'
+ Hstore.reset_column_information
- def test_change_table_supports_hstore
- @connection.transaction do
- @connection.change_table("hstores") do |t|
- t.hstore "users", default: ""
- end
- Hstore.reset_column_information
- column = Hstore.columns_hash["users"]
- assert_equal :hstore, column.type
+ assert_equal({ "users" => "read", "articles" => "write" }, Hstore.column_defaults["permissions"])
+ assert_equal({ "users" => "read", "articles" => "write" }, Hstore.new.permissions)
+ ensure
+ Hstore.reset_column_information
+ end
- raise ActiveRecord::Rollback # reset the schema change
+ def test_change_table_supports_hstore
+ @connection.transaction do
+ @connection.change_table("hstores") do |t|
+ t.hstore "users", default: ""
end
- ensure
Hstore.reset_column_information
+ column = Hstore.columns_hash["users"]
+ assert_equal :hstore, column.type
+
+ raise ActiveRecord::Rollback # reset the schema change
end
+ ensure
+ Hstore.reset_column_information
+ end
- def test_hstore_migration
- hstore_migration = Class.new(ActiveRecord::Migration::Current) do
- def change
- change_table("hstores") do |t|
- t.hstore :keys
- end
+ def test_hstore_migration
+ hstore_migration = Class.new(ActiveRecord::Migration::Current) do
+ def change
+ change_table("hstores") do |t|
+ t.hstore :keys
end
end
-
- hstore_migration.new.suppress_messages do
- hstore_migration.migrate(:up)
- assert_includes @connection.columns(:hstores).map(&:name), "keys"
- hstore_migration.migrate(:down)
- assert_not_includes @connection.columns(:hstores).map(&:name), "keys"
- end
end
- def test_cast_value_on_write
- x = Hstore.new tags: { "bool" => true, "number" => 5 }
- assert_equal({ "bool" => true, "number" => 5 }, x.tags_before_type_cast)
- assert_equal({ "bool" => "true", "number" => "5" }, x.tags)
- x.save
- assert_equal({ "bool" => "true", "number" => "5" }, x.reload.tags)
+ hstore_migration.new.suppress_messages do
+ hstore_migration.migrate(:up)
+ assert_includes @connection.columns(:hstores).map(&:name), "keys"
+ hstore_migration.migrate(:down)
+ assert_not_includes @connection.columns(:hstores).map(&:name), "keys"
end
+ end
- def test_type_cast_hstore
- assert_equal({ "1" => "2" }, @type.deserialize("\"1\"=>\"2\""))
- assert_equal({}, @type.deserialize(""))
- assert_equal({ "key" => nil }, @type.deserialize("key => NULL"))
- assert_equal({ "c" => "}", '"a"' => 'b "a b' }, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b")))
- end
+ def test_cast_value_on_write
+ x = Hstore.new tags: { "bool" => true, "number" => 5 }
+ assert_equal({ "bool" => true, "number" => 5 }, x.tags_before_type_cast)
+ assert_equal({ "bool" => "true", "number" => "5" }, x.tags)
+ x.save
+ assert_equal({ "bool" => "true", "number" => "5" }, x.reload.tags)
+ end
- def test_with_store_accessors
- x = Hstore.new(language: "fr", timezone: "GMT")
- assert_equal "fr", x.language
- assert_equal "GMT", x.timezone
+ def test_type_cast_hstore
+ assert_equal({ "1" => "2" }, @type.deserialize("\"1\"=>\"2\""))
+ assert_equal({}, @type.deserialize(""))
+ assert_equal({ "key" => nil }, @type.deserialize("key => NULL"))
+ assert_equal({ "c" => "}", '"a"' => 'b "a b' }, @type.deserialize(%q(c=>"}", "\"a\""=>"b \"a b")))
+ end
- x.save!
- x = Hstore.first
- assert_equal "fr", x.language
- assert_equal "GMT", x.timezone
+ def test_with_store_accessors
+ x = Hstore.new(language: "fr", timezone: "GMT")
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
- x.language = "de"
- x.save!
+ x.save!
+ x = Hstore.first
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
- x = Hstore.first
- assert_equal "de", x.language
- assert_equal "GMT", x.timezone
- end
+ x.language = "de"
+ x.save!
- def test_duplication_with_store_accessors
- x = Hstore.new(language: "fr", timezone: "GMT")
- assert_equal "fr", x.language
- assert_equal "GMT", x.timezone
+ x = Hstore.first
+ assert_equal "de", x.language
+ assert_equal "GMT", x.timezone
+ end
- y = x.dup
- assert_equal "fr", y.language
- assert_equal "GMT", y.timezone
- end
+ def test_duplication_with_store_accessors
+ x = Hstore.new(language: "fr", timezone: "GMT")
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
- def test_yaml_round_trip_with_store_accessors
- x = Hstore.new(language: "fr", timezone: "GMT")
- assert_equal "fr", x.language
- assert_equal "GMT", x.timezone
+ y = x.dup
+ assert_equal "fr", y.language
+ assert_equal "GMT", y.timezone
+ end
- y = YAML.load(YAML.dump(x))
- assert_equal "fr", y.language
- assert_equal "GMT", y.timezone
- end
+ def test_yaml_round_trip_with_store_accessors
+ x = Hstore.new(language: "fr", timezone: "GMT")
+ assert_equal "fr", x.language
+ assert_equal "GMT", x.timezone
- def test_changes_in_place
- hstore = Hstore.create!(settings: { "one" => "two" })
- hstore.settings["three"] = "four"
- hstore.save!
- hstore.reload
+ y = YAML.load(YAML.dump(x))
+ assert_equal "fr", y.language
+ assert_equal "GMT", y.timezone
+ end
- assert_equal "four", hstore.settings["three"]
- assert_not hstore.changed?
- end
+ def test_changes_in_place
+ hstore = Hstore.create!(settings: { "one" => "two" })
+ hstore.settings["three"] = "four"
+ hstore.save!
+ hstore.reload
- def test_dirty_from_user_equal
- settings = { "alongkey" => "anything", "key" => "value" }
- hstore = Hstore.create!(settings: settings)
+ assert_equal "four", hstore.settings["three"]
+ assert_not hstore.changed?
+ end
- hstore.settings = { "key" => "value", "alongkey" => "anything" }
- assert_equal settings, hstore.settings
- refute hstore.changed?
- end
+ def test_dirty_from_user_equal
+ settings = { "alongkey" => "anything", "key" => "value" }
+ hstore = Hstore.create!(settings: settings)
- def test_hstore_dirty_from_database_equal
- settings = { "alongkey" => "anything", "key" => "value" }
- hstore = Hstore.create!(settings: settings)
- hstore.reload
+ hstore.settings = { "key" => "value", "alongkey" => "anything" }
+ assert_equal settings, hstore.settings
+ refute hstore.changed?
+ end
- assert_equal settings, hstore.settings
- hstore.settings = settings
- refute hstore.changed?
- end
+ def test_hstore_dirty_from_database_equal
+ settings = { "alongkey" => "anything", "key" => "value" }
+ hstore = Hstore.create!(settings: settings)
+ hstore.reload
- def test_gen1
- assert_equal('" "=>""', @type.serialize(" " => ""))
- end
+ assert_equal settings, hstore.settings
+ hstore.settings = settings
+ refute hstore.changed?
+ end
- def test_gen2
- assert_equal('","=>""', @type.serialize("," => ""))
- end
+ def test_gen1
+ assert_equal('" "=>""', @type.serialize(" " => ""))
+ end
- def test_gen3
- assert_equal('"="=>""', @type.serialize("=" => ""))
- end
+ def test_gen2
+ assert_equal('","=>""', @type.serialize("," => ""))
+ end
- def test_gen4
- assert_equal('">"=>""', @type.serialize(">" => ""))
- end
+ def test_gen3
+ assert_equal('"="=>""', @type.serialize("=" => ""))
+ end
- def test_parse1
- assert_equal({ "a" => nil, "b" => nil, "c" => "NuLl", "null" => "c" }, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
- end
+ def test_gen4
+ assert_equal('">"=>""', @type.serialize(">" => ""))
+ end
- def test_parse2
- assert_equal({ " " => " " }, @type.deserialize("\\ =>\\ "))
- end
+ def test_parse1
+ assert_equal({ "a" => nil, "b" => nil, "c" => "NuLl", "null" => "c" }, @type.deserialize('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
+ end
- def test_parse3
- assert_equal({ "=" => ">" }, @type.deserialize("==>>"))
- end
+ def test_parse2
+ assert_equal({ " " => " " }, @type.deserialize("\\ =>\\ "))
+ end
- def test_parse4
- assert_equal({ "=a" => "q=w" }, @type.deserialize('\=a=>q=w'))
- end
+ def test_parse3
+ assert_equal({ "=" => ">" }, @type.deserialize("==>>"))
+ end
- def test_parse5
- assert_equal({ "=a" => "q=w" }, @type.deserialize('"=a"=>q\=w'))
- end
+ def test_parse4
+ assert_equal({ "=a" => "q=w" }, @type.deserialize('\=a=>q=w'))
+ end
- def test_parse6
- assert_equal({ "\"a" => "q>w" }, @type.deserialize('"\"a"=>q>w'))
- end
+ def test_parse5
+ assert_equal({ "=a" => "q=w" }, @type.deserialize('"=a"=>q\=w'))
+ end
- def test_parse7
- assert_equal({ "\"a" => "q\"w" }, @type.deserialize('\"a=>q"w'))
- end
+ def test_parse6
+ assert_equal({ "\"a" => "q>w" }, @type.deserialize('"\"a"=>q>w'))
+ end
- def test_rewrite
- @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
- x = Hstore.first
- x.tags = { '"a\'' => "b" }
- assert x.save!
- end
+ def test_parse7
+ assert_equal({ "\"a" => "q\"w" }, @type.deserialize('\"a=>q"w'))
+ end
- def test_select
- @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
- x = Hstore.first
- assert_equal({ "1" => "2" }, x.tags)
- end
+ def test_rewrite
+ @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
+ x = Hstore.first
+ x.tags = { '"a\'' => "b" }
+ assert x.save!
+ end
- def test_array_cycle
- assert_array_cycle([{ "AA" => "BB", "CC" => "DD" }, { "AA" => nil }])
- end
+ def test_select
+ @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
+ x = Hstore.first
+ assert_equal({ "1" => "2" }, x.tags)
+ end
- def test_array_strings_with_quotes
- assert_array_cycle([{ "this has" => 'some "s that need to be escaped"' }])
- end
+ def test_array_cycle
+ assert_array_cycle([{ "AA" => "BB", "CC" => "DD" }, { "AA" => nil }])
+ end
- def test_array_strings_with_commas
- assert_array_cycle([{ "this,has" => "many,values" }])
- end
+ def test_array_strings_with_quotes
+ assert_array_cycle([{ "this has" => 'some "s that need to be escaped"' }])
+ end
- def test_array_strings_with_array_delimiters
- assert_array_cycle(["{" => "}"])
- end
+ def test_array_strings_with_commas
+ assert_array_cycle([{ "this,has" => "many,values" }])
+ end
- def test_array_strings_with_null_strings
- assert_array_cycle([{ "NULL" => "NULL" }])
- end
+ def test_array_strings_with_array_delimiters
+ assert_array_cycle(["{" => "}"])
+ end
- def test_contains_nils
- assert_array_cycle([{ "NULL" => nil }])
- end
+ def test_array_strings_with_null_strings
+ assert_array_cycle([{ "NULL" => "NULL" }])
+ end
- def test_select_multikey
- @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')"
- x = Hstore.first
- assert_equal({ "1" => "2", "2" => "3" }, x.tags)
- end
+ def test_contains_nils
+ assert_array_cycle([{ "NULL" => nil }])
+ end
- def test_create
- assert_cycle("a" => "b", "1" => "2")
- end
+ def test_select_multikey
+ @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')"
+ x = Hstore.first
+ assert_equal({ "1" => "2", "2" => "3" }, x.tags)
+ end
- def test_nil
- assert_cycle("a" => nil)
- end
+ def test_create
+ assert_cycle("a" => "b", "1" => "2")
+ end
- def test_quotes
- assert_cycle("a" => 'b"ar', '1"foo' => "2")
- end
+ def test_nil
+ assert_cycle("a" => nil)
+ end
- def test_whitespace
- assert_cycle("a b" => "b ar", '1"foo' => "2")
- end
+ def test_quotes
+ assert_cycle("a" => 'b"ar', '1"foo' => "2")
+ end
- def test_backslash
- assert_cycle('a\\b' => 'b\\ar', '1"foo' => "2")
- end
+ def test_whitespace
+ assert_cycle("a b" => "b ar", '1"foo' => "2")
+ end
- def test_comma
- assert_cycle("a, b" => "bar", '1"foo' => "2")
- end
+ def test_backslash
+ assert_cycle('a\\b' => 'b\\ar', '1"foo' => "2")
+ end
- def test_arrow
- assert_cycle("a=>b" => "bar", '1"foo' => "2")
- end
+ def test_comma
+ assert_cycle("a, b" => "bar", '1"foo' => "2")
+ end
- def test_quoting_special_characters
- assert_cycle("ca" => "cà", "ac" => "àc")
- end
+ def test_arrow
+ assert_cycle("a=>b" => "bar", '1"foo' => "2")
+ end
- def test_multiline
- assert_cycle("a\nb" => "c\nd")
- end
+ def test_quoting_special_characters
+ assert_cycle("ca" => "cà", "ac" => "àc")
+ end
- class TagCollection
- def initialize(hash); @hash = hash end
- def to_hash; @hash end
- def self.load(hash); new(hash) end
- def self.dump(object); object.to_hash end
- end
+ def test_multiline
+ assert_cycle("a\nb" => "c\nd")
+ end
- class HstoreWithSerialize < Hstore
- serialize :tags, TagCollection
- end
+ class TagCollection
+ def initialize(hash); @hash = hash end
+ def to_hash; @hash end
+ def self.load(hash); new(hash) end
+ def self.dump(object); object.to_hash end
+ end
- def test_hstore_with_serialized_attributes
- HstoreWithSerialize.create! tags: TagCollection.new("one" => "two")
- record = HstoreWithSerialize.first
- assert_instance_of TagCollection, record.tags
- assert_equal({ "one" => "two" }, record.tags.to_hash)
- record.tags = TagCollection.new("three" => "four")
- record.save!
- assert_equal({ "three" => "four" }, HstoreWithSerialize.first.tags.to_hash)
- end
+ class HstoreWithSerialize < Hstore
+ serialize :tags, TagCollection
+ end
- def test_clone_hstore_with_serialized_attributes
- HstoreWithSerialize.create! tags: TagCollection.new("one" => "two")
- record = HstoreWithSerialize.first
- dupe = record.dup
- assert_equal({ "one" => "two" }, dupe.tags.to_hash)
- end
+ def test_hstore_with_serialized_attributes
+ HstoreWithSerialize.create! tags: TagCollection.new("one" => "two")
+ record = HstoreWithSerialize.first
+ assert_instance_of TagCollection, record.tags
+ assert_equal({ "one" => "two" }, record.tags.to_hash)
+ record.tags = TagCollection.new("three" => "four")
+ record.save!
+ assert_equal({ "three" => "four" }, HstoreWithSerialize.first.tags.to_hash)
+ end
- def test_schema_dump_with_shorthand
- output = dump_table_schema("hstores")
- assert_match %r[t\.hstore "tags",\s+default: {}], output
- end
+ def test_clone_hstore_with_serialized_attributes
+ HstoreWithSerialize.create! tags: TagCollection.new("one" => "two")
+ record = HstoreWithSerialize.first
+ dupe = record.dup
+ assert_equal({ "one" => "two" }, dupe.tags.to_hash)
+ end
+
+ def test_schema_dump_with_shorthand
+ output = dump_table_schema("hstores")
+ assert_match %r[t\.hstore "tags",\s+default: {}], output
+ end
+
+ def test_supports_to_unsafe_h_values
+ assert_equal("\"hi\"=>\"hi\"", @type.serialize(FakeParameters.new))
+ end
- def test_supports_to_unsafe_h_values
- assert_equal("\"hi\"=>\"hi\"", @type.serialize(FakeParameters.new))
+ private
+ def assert_array_cycle(array)
+ # test creation
+ x = Hstore.create!(payload: array)
+ x.reload
+ assert_equal(array, x.payload)
+
+ # test updating
+ x = Hstore.create!(payload: [])
+ x.payload = array
+ x.save!
+ x.reload
+ assert_equal(array, x.payload)
end
- private
- def assert_array_cycle(array)
- # test creation
- x = Hstore.create!(payload: array)
- x.reload
- assert_equal(array, x.payload)
-
- # test updating
- x = Hstore.create!(payload: [])
- x.payload = array
- x.save!
- x.reload
- assert_equal(array, x.payload)
- end
+ def assert_cycle(hash)
+ # test creation
+ x = Hstore.create!(tags: hash)
+ x.reload
+ assert_equal(hash, x.tags)
- def assert_cycle(hash)
- # test creation
- x = Hstore.create!(tags: hash)
- x.reload
- assert_equal(hash, x.tags)
-
- # test updating
- x = Hstore.create!(tags: {})
- x.tags = hash
- x.save!
- x.reload
- assert_equal(hash, x.tags)
- end
- end
+ # test updating
+ x = Hstore.create!(tags: {})
+ x.tags = hash
+ x.save!
+ x.reload
+ assert_equal(hash, x.tags)
+ end
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index b4a776d04d..a75fdef698 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -232,6 +232,57 @@ _SQL
end
end
+ def test_create_tstzrange_preserve_usec
+ tstzrange = Time.parse("2010-01-01 14:30:00.670277 +0100")...Time.parse("2011-02-02 14:30:00.745125 CDT")
+ round_trip(@new_range, :tstz_range, tstzrange)
+ assert_equal @new_range.tstz_range, tstzrange
+ assert_equal @new_range.tstz_range, Time.parse("2010-01-01 13:30:00.670277 UTC")...Time.parse("2011-02-02 19:30:00.745125 UTC")
+ end
+
+ def test_update_tstzrange_preserve_usec
+ assert_equal_round_trip(@first_range, :tstz_range,
+ Time.parse("2010-01-01 14:30:00.245124 CDT")...Time.parse("2011-02-02 14:30:00.451274 CET"))
+ assert_nil_round_trip(@first_range, :tstz_range,
+ Time.parse("2010-01-01 14:30:00.245124 +0100")...Time.parse("2010-01-01 13:30:00.245124 +0000"))
+ end
+
+ def test_create_tsrange_preseve_usec
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@new_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 125435)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 225435))
+ end
+
+ def test_update_tsrange_preserve_usec
+ tz = ::ActiveRecord::Base.default_timezone
+ assert_equal_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2011, 2, 2, 14, 30, 0, 224242))
+ assert_nil_round_trip(@first_range, :ts_range,
+ Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432)...Time.send(tz, 2010, 1, 1, 14, 30, 0, 142432))
+ end
+
+ def test_timezone_awareness_tsrange_preserve_usec
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ PostgresqlRange.reset_column_information
+ time_string = "2017-09-26 07:30:59.132451 -0700"
+ time = Time.zone.parse(time_string)
+ assert time.usec > 0
+
+ record = PostgresqlRange.new(ts_range: time_string..time_string)
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ assert_equal time.usec, record.ts_range.begin.usec
+
+ record.save!
+ record.reload
+
+ assert_equal time..time, record.ts_range
+ assert_equal ActiveSupport::TimeZone[tz], record.ts_range.begin.time_zone
+ assert_equal time.usec, record.ts_range.begin.usec
+ end
+ end
+
def test_create_numrange
assert_equal_round_trip(@new_range, :num_range,
BigDecimal.new("0.5")...BigDecimal.new("1"))
diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb
index 3c020a88d0..6a99323be5 100644
--- a/activerecord/test/cases/adapters/postgresql/serial_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb
@@ -86,3 +86,71 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase
assert_match %r{t\.bigint\s+"serials_id",\s+default: -> \{ "nextval\('postgresql_big_serials_id_seq'::regclass\)" \}$}, output
end
end
+
+module SequenceNameDetectionTestCases
+ class CollidedSequenceNameTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :foo_bar, force: true do |t|
+ t.serial :baz_id
+ end
+ @connection.create_table :foo, force: true do |t|
+ t.serial :bar_id
+ t.bigserial :bar_baz_id
+ end
+ end
+
+ def teardown
+ @connection.drop_table :foo_bar, if_exists: true
+ @connection.drop_table :foo, if_exists: true
+ end
+
+ def test_serial_columns
+ columns = @connection.columns(:foo)
+ columns.each do |column|
+ assert_equal :integer, column.type
+ assert column.serial?
+ end
+ end
+
+ def test_schema_dump_with_collided_sequence_name
+ output = dump_table_schema "foo"
+ assert_match %r{t\.serial\s+"bar_id",\s+null: false$}, output
+ assert_match %r{t\.bigserial\s+"bar_baz_id",\s+null: false$}, output
+ end
+ end
+
+ class LongerSequenceNameDetectionTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ def setup
+ @table_name = "long_table_name_to_test_sequence_name_detection_for_serial_cols"
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table @table_name, force: true do |t|
+ t.serial :seq
+ t.bigserial :bigseq
+ end
+ end
+
+ def teardown
+ @connection.drop_table @table_name, if_exists: true
+ end
+
+ def test_serial_columns
+ columns = @connection.columns(@table_name)
+ columns.each do |column|
+ assert_equal :integer, column.type
+ assert column.serial?
+ end
+ end
+
+ def test_schema_dump_with_long_table_name
+ output = dump_table_schema @table_name
+ assert_match %r{create_table "#{@table_name}", force: :cascade}, output
+ assert_match %r{t\.serial\s+"seq",\s+null: false$}, output
+ assert_match %r{t\.bigserial\s+"bigseq",\s+null: false$}, output
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
index 449023b6eb..8212ed4263 100644
--- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb
@@ -30,6 +30,6 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase
big_range = 0..123456789123456789
assert_raises(ActiveModel::RangeError) { int_range.serialize(big_range) }
- assert_equal "[0,123456789123456789]", bigint_range.serialize(big_range)
+ assert_equal "[0,123456789123456789]", @connection.type_cast(bigint_range.serialize(big_range))
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 76cb1bc354..c24e0cb330 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -76,6 +76,19 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
assert_nil column.default
end
+ def test_add_column_with_default_array
+ connection.add_column :uuid_data_type, :thingy, :uuid, array: true, default: []
+
+ UUIDType.reset_column_information
+ column = UUIDType.columns_hash["thingy"]
+
+ assert column.array?
+ assert_equal "{}", column.default
+
+ schema = dump_table_schema "uuid_data_type"
+ assert_match %r{t\.uuid "thingy", default: \[\], array: true$}, schema
+ end
+
def test_data_type_of_uuid_types
column = UUIDType.columns_hash["guid"]
assert_equal :uuid, column.type
@@ -209,68 +222,66 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
connection.execute "DROP FUNCTION IF EXISTS my_uuid_generator();"
end
- if ActiveRecord::Base.connection.supports_extensions?
- def test_id_is_uuid
- assert_equal :uuid, UUID.columns_hash["id"].type
- assert UUID.primary_key
- end
+ def test_id_is_uuid
+ assert_equal :uuid, UUID.columns_hash["id"].type
+ assert UUID.primary_key
+ end
- def test_id_has_a_default
- u = UUID.create
- assert_not_nil u.id
- end
+ def test_id_has_a_default
+ u = UUID.create
+ assert_not_nil u.id
+ end
- def test_auto_create_uuid
- u = UUID.create
- u.reload
- assert_not_nil u.other_uuid
- end
+ def test_auto_create_uuid
+ u = UUID.create
+ u.reload
+ assert_not_nil u.other_uuid
+ end
- def test_pk_and_sequence_for_uuid_primary_key
- pk, seq = connection.pk_and_sequence_for("pg_uuids")
- assert_equal "id", pk
- assert_nil seq
- end
+ def test_pk_and_sequence_for_uuid_primary_key
+ pk, seq = connection.pk_and_sequence_for("pg_uuids")
+ assert_equal "id", pk
+ assert_nil seq
+ end
- def test_schema_dumper_for_uuid_primary_key
- schema = dump_table_schema "pg_uuids"
- assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema)
- assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
- end
+ def test_schema_dumper_for_uuid_primary_key
+ schema = dump_table_schema "pg_uuids"
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
+ end
+
+ def test_schema_dumper_for_uuid_primary_key_with_custom_default
+ schema = dump_table_schema "pg_uuids_2"
+ assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
+ end
- def test_schema_dumper_for_uuid_primary_key_with_custom_default
- schema = dump_table_schema "pg_uuids_2"
- assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
- assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
+ def test_schema_dumper_for_uuid_primary_key_default
+ schema = dump_table_schema "pg_uuids_3"
+ if connection.supports_pgcrypto_uuid?
+ assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "gen_random_uuid\(\)" }/, schema)
+ else
+ assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
end
+ end
+
+ def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
- def test_schema_dumper_for_uuid_primary_key_default
- schema = dump_table_schema "pg_uuids_3"
- if connection.supports_pgcrypto_uuid?
- assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "gen_random_uuid\(\)" }/, schema)
- else
- assert_match(/\bcreate_table "pg_uuids_3", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
+ migration = Class.new(ActiveRecord::Migration[5.0]) do
+ def version; 101 end
+ def migrate(x)
+ create_table("pg_uuids_4", id: :uuid)
end
- end
+ end.new
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
- def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
- @verbose_was = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migration = Class.new(ActiveRecord::Migration[5.0]) do
- def version; 101 end
- def migrate(x)
- create_table("pg_uuids_4", id: :uuid)
- end
- end.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
-
- schema = dump_table_schema "pg_uuids_4"
- assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
- ensure
- drop_table "pg_uuids_4"
- ActiveRecord::Migration.verbose = @verbose_was
- end
+ schema = dump_table_schema "pg_uuids_4"
+ assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
+ ensure
+ drop_table "pg_uuids_4"
+ ActiveRecord::Migration.verbose = @verbose_was
end
end
@@ -289,38 +300,36 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
drop_table "pg_uuids"
end
- if ActiveRecord::Base.connection.supports_extensions?
- def test_id_allows_default_override_via_nil
- col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default
- FROM pg_attribute a
- LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
- WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first
- assert_nil col_desc["default"]
- end
+ def test_id_allows_default_override_via_nil
+ col_desc = connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default
+ FROM pg_attribute a
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
+ WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first
+ assert_nil col_desc["default"]
+ end
- def test_schema_dumper_for_uuid_primary_key_with_default_override_via_nil
- schema = dump_table_schema "pg_uuids"
- assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema)
- end
+ def test_schema_dumper_for_uuid_primary_key_with_default_override_via_nil
+ schema = dump_table_schema "pg_uuids"
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema)
+ end
- def test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration
- @verbose_was = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migration = Class.new(ActiveRecord::Migration[5.0]) do
- def version; 101 end
- def migrate(x)
- create_table("pg_uuids_4", id: :uuid, default: nil)
- end
- end.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
-
- schema = dump_table_schema "pg_uuids_4"
- assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: nil/, schema)
- ensure
- drop_table "pg_uuids_4"
- ActiveRecord::Migration.verbose = @verbose_was
- end
+ def test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ migration = Class.new(ActiveRecord::Migration[5.0]) do
+ def version; 101 end
+ def migrate(x)
+ create_table("pg_uuids_4", id: :uuid, default: nil)
+ end
+ end.new
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ schema = dump_table_schema "pg_uuids_4"
+ assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: nil/, schema)
+ ensure
+ drop_table "pg_uuids_4"
+ ActiveRecord::Migration.verbose = @verbose_was
end
end
@@ -354,23 +363,21 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
drop_table "pg_uuid_posts"
end
- if ActiveRecord::Base.connection.supports_extensions?
- def test_collection_association_with_uuid
- post = UuidPost.create!
- comment = post.uuid_comments.create!
- assert post.uuid_comments.find(comment.id)
- end
+ def test_collection_association_with_uuid
+ post = UuidPost.create!
+ comment = post.uuid_comments.create!
+ assert post.uuid_comments.find(comment.id)
+ end
- def test_find_with_uuid
- UuidPost.create!
- assert_raise ActiveRecord::RecordNotFound do
- UuidPost.find(123456)
- end
+ def test_find_with_uuid
+ UuidPost.create!
+ assert_raise ActiveRecord::RecordNotFound do
+ UuidPost.find(123456)
end
+ end
- def test_find_by_with_uuid
- UuidPost.create!
- assert_nil UuidPost.find_by(id: 789)
- end
+ def test_find_by_with_uuid
+ UuidPost.create!
+ assert_nil UuidPost.find_by(id: 789)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/json_test.rb b/activerecord/test/cases/adapters/sqlite3/json_test.rb
new file mode 100644
index 0000000000..568a524058
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/json_test.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "cases/json_shared_test_cases"
+
+class SQLite3JSONTest < ActiveRecord::SQLite3TestCase
+ include JSONSharedTestCases
+
+ def setup
+ super
+ @connection.create_table("json_data_type") do |t|
+ t.column "payload", :json, default: {}
+ t.column "settings", :json
+ end
+ end
+
+ def test_default
+ @connection.add_column "json_data_type", "permissions", column_type, default: { "users": "read", "posts": ["read", "write"] }
+ klass.reset_column_information
+
+ assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.column_defaults["permissions"])
+ assert_equal({ "users" => "read", "posts" => ["read", "write"] }, klass.new.permissions)
+ end
+
+ private
+ def column_type
+ :json
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 2b51a32db6..1f057fe5c6 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -269,14 +269,6 @@ module ActiveRecord
end
end
- def test_indexes_logs_name
- with_example_table do
- assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do
- assert_deprecated { @conn.indexes("ex", "hello") }
- end
- end
- end
-
def test_table_exists_logs_name
with_example_table do
sql = <<-SQL
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 44bf7f7d2f..9afe6a893c 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -530,6 +530,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
end
+ def test_preloading_has_many_through_with_implicit_source
+ authors = Author.includes(:very_special_comments).to_a
+ assert_no_queries do
+ special_comment_authors = authors.map { |author| [author.name, author.very_special_comments.size] }
+ assert_equal [["David", 1], ["Mary", 0], ["Bob", 0]], special_comment_authors
+ end
+ end
+
def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both
author = Author.all.merge!(includes: :special_nonexistent_post_comments, order: "authors.id").first
assert_equal [], author.special_nonexistent_post_comments
@@ -861,10 +869,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
- def find_all_ordered(className, include = nil)
- className.all.merge!(order: "#{className.table_name}.#{className.primary_key}", includes: include).to_a
- end
-
def test_limited_eager_with_order
assert_equal(
posts(:thinking, :sti_comments),
@@ -1299,6 +1303,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal projects.last.mentor.developers.first.contracts, projects.last.developers.last.contracts
end
+ def test_preloading_has_many_through_with_custom_scope
+ project = Project.includes(:developers_named_david_with_hash_conditions).find(projects(:active_record).id)
+ assert_equal [developers(:david)], project.developers_named_david_with_hash_conditions
+ end
+
test "scoping with a circular preload" do
assert_equal Comment.find(1), Comment.preload(post: :comments).scoping { Comment.find(1) }
end
@@ -1492,9 +1501,18 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal posts(:welcome), post
end
+ test "eager-loading with a polymorphic association and using the existential predicate" do
+ assert_equal true, authors(:david).essays.eager_load(:writer).exists?
+ end
+
# CollectionProxy#reader is expensive, so the preloader avoids calling it.
test "preloading has_many_through association avoids calling association.reader" do
ActiveRecord::Associations::HasManyAssociation.any_instance.expects(:reader).never
Author.preload(:readonly_comments).first!
end
+
+ private
+ def find_all_ordered(klass, include = nil)
+ klass.order("#{klass.table_name}.#{klass.primary_key}").includes(include).to_a
+ end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 979dd986de..c817d7267b 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -88,12 +88,6 @@ class DeveloperWithSymbolClassName < Developer
has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys
end
-ActiveSupport::Deprecation.silence do
- class DeveloperWithConstantClassName < Developer
- has_and_belongs_to_many :projects, class_name: ProjectWithSymbolsForKeys
- end
-end
-
class DeveloperWithExtendOption < Developer
module NamedExtension
def category
@@ -954,13 +948,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
end
- def test_with_constant_class_name
- assert_nothing_raised do
- developer = DeveloperWithConstantClassName.new
- developer.projects
- end
- end
-
def test_alternate_database
professor = Professor.create(name: "Plum")
course = Course.create(name: "Forensics")
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index f8ea51225a..c6a4ac356f 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1250,6 +1250,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
TenantMembership.current_member = nil
end
+ def test_has_many_trough_with_scope_that_has_joined_same_table_with_parent_relation
+ assert_equal authors(:david), Author.joins(:comments_for_first_author).take
+ end
+
+ def test_has_many_through_with_unscope_should_affect_to_through_scope
+ assert_equal [comments(:eager_other_comment1)], authors(:mary).unordered_comments
+ end
+
def test_has_many_through_with_scope_should_respect_table_alias
family = Family.create!
users = 3.times.map { User.create! }
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 2a9ebd19ed..ec5d95080b 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -30,7 +30,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
ActiveRecord::SQLCounter.clear_log
companies(:first_firm).account
ensure
- assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query"
+ log_all = ActiveRecord::SQLCounter.log_all
+ assert log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{log_all}"
end
def test_has_one_cache_nils
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 23be344419..7be875fec6 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -27,6 +27,24 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
end
end
+ def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associations_with_left_outer_joins
+ sql = Person.joins(agents: :agents).left_outer_joins(agents: :agents).to_sql
+ assert_match(/agents_people_4/i, sql)
+ end
+
+ def test_construct_finder_sql_does_not_table_name_collide_with_string_joins
+ sql = Person.joins(:agents).joins("JOIN people agents_people ON agents_people.primary_contact_id = people.id").to_sql
+ assert_match(/agents_people_2/i, sql)
+ end
+
+ def test_construct_finder_sql_does_not_table_name_collide_with_aliased_joins
+ people = Person.arel_table
+ agents = people.alias("agents_people")
+ constraint = agents[:primary_contact_id].eq(people[:id])
+ sql = Person.joins(:agents).joins(agents.create_join(agents, agents.create_on(constraint))).to_sql
+ assert_match(/agents_people_2/i, sql)
+ end
+
def test_construct_finder_sql_ignores_empty_joins_hash
sql = Author.joins({}).to_sql
assert_no_match(/JOIN/i, sql)
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 0ea8ef5cea..2f42684212 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -999,6 +999,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_equal ["title"], model.accessed_fields
end
+ test "generated attribute methods ancestors have correct class" do
+ mod = Topic.send(:generated_attribute_methods)
+ assert_match %r(GeneratedAttributeMethods), mod.inspect
+ end
+
private
def new_topic_like_ar_class(&block)
@@ -1019,14 +1024,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase
ActiveRecord::Base.time_zone_aware_types = old_types
end
- def cached_columns
- Topic.columns.map(&:name)
- end
-
- def time_related_columns_on_topic
- Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) }
- end
-
def privatize(method_signature)
@target.class_eval(<<-private_method, __FILE__, __LINE__ + 1)
private
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 29a25b4461..2caf2a63d4 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -108,12 +108,14 @@ module ActiveRecord
assert_equal 6, klass.attribute_types.length
assert_equal 6, klass.column_defaults.length
+ assert_equal 6, klass.attribute_names.length
assert_not klass.attribute_types.include?("wibble")
klass.attribute :wibble, Type::Value.new
assert_equal 7, klass.attribute_types.length
assert_equal 7, klass.column_defaults.length
+ assert_equal 7, klass.attribute_names.length
assert_includes klass.attribute_types, "wibble"
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 1a1d4ce039..f0ef522515 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1444,17 +1444,36 @@ class BasicsTest < ActiveRecord::TestCase
cache_columns = Developer.connection.schema_cache.columns_hash(Developer.table_name)
assert_includes cache_columns.keys, "first_name"
assert_not_includes Developer.columns_hash.keys, "first_name"
+ assert_not_includes SubDeveloper.columns_hash.keys, "first_name"
+ assert_not_includes SymbolIgnoredDeveloper.columns_hash.keys, "first_name"
end
test "ignored columns have no attribute methods" do
refute Developer.new.respond_to?(:first_name)
refute Developer.new.respond_to?(:first_name=)
refute Developer.new.respond_to?(:first_name?)
+ refute SubDeveloper.new.respond_to?(:first_name)
+ refute SubDeveloper.new.respond_to?(:first_name=)
+ refute SubDeveloper.new.respond_to?(:first_name?)
+ refute SymbolIgnoredDeveloper.new.respond_to?(:first_name)
+ refute SymbolIgnoredDeveloper.new.respond_to?(:first_name=)
+ refute SymbolIgnoredDeveloper.new.respond_to?(:first_name?)
end
test "ignored columns don't prevent explicit declaration of attribute methods" do
assert Developer.new.respond_to?(:last_name)
assert Developer.new.respond_to?(:last_name=)
assert Developer.new.respond_to?(:last_name?)
+ assert SubDeveloper.new.respond_to?(:last_name)
+ assert SubDeveloper.new.respond_to?(:last_name=)
+ assert SubDeveloper.new.respond_to?(:last_name?)
+ assert SymbolIgnoredDeveloper.new.respond_to?(:last_name)
+ assert SymbolIgnoredDeveloper.new.respond_to?(:last_name=)
+ assert SymbolIgnoredDeveloper.new.respond_to?(:last_name?)
+ end
+
+ test "ignored columns are stored as an array of string" do
+ assert_equal(%w(first_name last_name), Developer.ignored_columns)
+ assert_equal(%w(first_name last_name), SymbolIgnoredDeveloper.ignored_columns)
end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 53c1e61ad1..be8aeed5ac 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -586,32 +586,15 @@ class EachTest < ActiveRecord::TestCase
end
end
- test ".error_on_ignored_order_or_limit= is deprecated" do
- begin
- prev = ActiveRecord::Base.error_on_ignored_order
- assert_deprecated "Please use error_on_ignored_order= instead." do
- ActiveRecord::Base.error_on_ignored_order_or_limit = true
- end
- assert ActiveRecord::Base.error_on_ignored_order
- ensure
- ActiveRecord::Base.error_on_ignored_order = prev
- end
- end
-
- test ".error_on_ignored_order_or_limit is deprecated" do
- expected = ActiveRecord::Base.error_on_ignored_order
- actual = assert_deprecated "Please use error_on_ignored_order instead." do
- ActiveRecord::Base.error_on_ignored_order_or_limit
- end
- assert_equal expected, actual
- end
+ test ".find_each respects table alias" do
+ assert_queries(1) do
+ table_alias = Post.arel_table.alias("omg_posts")
+ table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias)
+ predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
- test "#error_on_ignored_order_or_limit is deprecated" do
- expected = ActiveRecord::Base.error_on_ignored_order
- actual = assert_deprecated "Please use error_on_ignored_order instead." do
- Post.new.error_on_ignored_order_or_limit
+ posts = ActiveRecord::Relation.create(Post, table_alias, predicate_builder)
+ posts.find_each {}
end
- assert_equal expected, actual
end
test ".find_each bypasses the query cache for its own queries" do
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 39dff19b78..66bc14b5ab 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -260,6 +260,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 3, Account.joins(:firm).distinct.order(:firm_id).limit(3).offset(2).count
end
+ def test_distinct_count_with_group_by_and_order_and_limit
+ assert_equal({ 6 => 2 }, Account.group(:firm_id).distinct.order("1 DESC").limit(1).count)
+ end
+
def test_should_group_by_summed_field_having_condition
c = Account.group(:firm_id).having("sum(credit_limit) > 50").sum(:credit_limit)
assert_nil c[1]
@@ -518,8 +522,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_sum_expression
- # Oracle adapter returns floating point value 636.0 after SUM
- if current_adapter?(:OracleAdapter)
+ if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter)
assert_equal 636, Account.sum("2 * credit_limit")
else
assert_equal 636, Account.sum("2 * credit_limit").to_i
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index dbe6857487..c137693211 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -55,6 +55,24 @@ module ActiveRecord
assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
end
+ test "cache_key for relation with table alias" do
+ table_alias = Developer.arel_table.alias("omg_developers")
+ table_metadata = ActiveRecord::TableMetadata.new(Developer, table_alias)
+ predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
+
+ developers = ActiveRecord::Relation.create(Developer, table_alias, predicate_builder)
+ developers = developers.where(salary: 100000).order(updated_at: :desc)
+ last_developer_timestamp = developers.first.updated_at
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
+
+ /\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
+
+ assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
+ assert_equal developers.count.to_s, $2
+ assert_equal last_developer_timestamp.to_s(ActiveRecord::Base.cache_timestamp_format), $3
+ end
+
test "it triggers at most one query" do
developers = Developer.where(name: "David")
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
index 1bcafd4b55..584e03d196 100644
--- a/activerecord/test/cases/comment_test.rb
+++ b/activerecord/test/cases/comment_test.rb
@@ -142,5 +142,27 @@ if ActiveRecord::Base.connection.supports_comments?
assert_match %r[t\.string\s+"absent_comment"\n], output
assert_no_match %r[t\.string\s+"absent_comment", comment:\n], output
end
+
+ def test_change_table_comment
+ @connection.change_table_comment :commenteds, "Edited table comment"
+ assert_equal "Edited table comment", @connection.table_comment("commenteds")
+ end
+
+ def test_change_table_comment_to_nil
+ @connection.change_table_comment :commenteds, nil
+ assert_nil @connection.table_comment("commenteds")
+ end
+
+ def test_change_column_comment
+ @connection.change_column_comment :commenteds, :name, "Edited column comment"
+ column = Commented.columns_hash["name"]
+ assert_equal "Edited column comment", column.comment
+ end
+
+ def test_change_column_comment_to_nil
+ @connection.change_column_comment :commenteds, :name, nil
+ column = Commented.columns_hash["name"]
+ assert_nil column.comment
+ end
end
end
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 3fa0ca8366..5b80f16a44 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -19,7 +19,7 @@ module ActiveRecord
spec "ridiculous://foo?encoding=utf8"
end
- assert_match "Could not load 'active_record/connection_adapters/ridiculous_adapter'", error.message
+ assert_match "Could not load the 'ridiculous' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", error.message
end
# The abstract adapter is used simply to bypass the bit of code that
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 55496147c1..d8bc917e7f 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -9,6 +9,7 @@ require "models/company"
require "models/tagging"
require "models/topic"
require "models/reply"
+require "models/rating"
require "models/entrant"
require "models/project"
require "models/developer"
@@ -156,6 +157,32 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(NoMethodError) { Topic.exists?([1, 2]) }
end
+ def test_exists_with_scope
+ davids = Author.where(name: "David")
+ assert_equal true, davids.exists?
+ assert_equal true, davids.exists?(authors(:david).id)
+ assert_equal false, davids.exists?(authors(:mary).id)
+ assert_equal false, davids.exists?("42")
+ assert_equal false, davids.exists?(42)
+ assert_equal false, davids.exists?(davids.new.id)
+
+ fake = Author.where(name: "fake author")
+ assert_equal false, fake.exists?
+ assert_equal false, fake.exists?(authors(:david).id)
+ end
+
+ def test_exists_uses_existing_scope
+ post = authors(:david).posts.first
+ authors = Author.includes(:posts).where(name: "David", posts: { id: post.id })
+ assert_equal true, authors.exists?(authors(:david).id)
+ end
+
+ def test_any_with_scope_on_hash_includes
+ post = authors(:david).posts.first
+ categories = Categorization.includes(author: :posts).where(posts: { id: post.id })
+ assert_equal true, categories.exists?
+ end
+
def test_exists_with_polymorphic_relation
post = Post.create!(title: "Post", body: "default", taggings: [Tagging.new(comment: "tagging comment")])
relation = Post.tagged_with_comment("tagging comment")
@@ -244,6 +271,13 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(1).exists?
end
+ def test_exists_should_reference_correct_aliases_while_joining_tables_of_has_many_through_association
+ assert_nothing_raised do
+ developer = developers(:david)
+ developer.ratings.includes(comment: :post).where(posts: { id: 1 }).exists?
+ end
+ end
+
def test_exists_with_empty_table_and_no_args_given
Topic.delete_all
assert_equal false, Topic.exists?
diff --git a/activerecord/test/cases/instrumentation_test.rb b/activerecord/test/cases/instrumentation_test.rb
new file mode 100644
index 0000000000..e6e8468757
--- /dev/null
+++ b/activerecord/test/cases/instrumentation_test.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/book"
+
+module ActiveRecord
+ class InstrumentationTest < ActiveRecord::TestCase
+ def test_payload_name_on_load
+ Book.create(name: "test book")
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ if event.payload[:sql].match "SELECT"
+ assert_equal "Book Load", event.payload[:name]
+ end
+ end
+ Book.first
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ def test_payload_name_on_create
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ if event.payload[:sql].match "INSERT"
+ assert_equal "Book Create", event.payload[:name]
+ end
+ end
+ Book.create(name: "test book")
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ def test_payload_name_on_update
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ if event.payload[:sql].match "UPDATE"
+ assert_equal "Book Update", event.payload[:name]
+ end
+ end
+ book = Book.create(name: "test book")
+ book.update_attribute(:name, "new name")
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ def test_payload_name_on_update_all
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ if event.payload[:sql].match "UPDATE"
+ assert_equal "Book Update All", event.payload[:name]
+ end
+ end
+ Book.create(name: "test book")
+ Book.update_all(name: "new name")
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+
+ def test_payload_name_on_destroy
+ subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ if event.payload[:sql].match "DELETE"
+ assert_equal "Book Destroy", event.payload[:name]
+ end
+ end
+ book = Book.create(name: "test book")
+ book.destroy
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
+ end
+ end
+end
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 20e747142b..60c628511f 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -295,6 +295,8 @@ module ActiveRecord
migration2.migrate(:down)
assert_equal false, Horse.connection.extension_enabled?("hstore")
+ ensure
+ enable_extension!("hstore", ActiveRecord::Base.connection)
end
end
diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
index 56ec8c8a82..a71485982c 100644
--- a/activerecord/test/cases/json_shared_test_cases.rb
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -30,6 +30,7 @@ module JSONSharedTestCases
end
def test_change_table_supports_json
+ skip unless @connection.supports_json?
@connection.change_table("json_data_type") do |t|
t.public_send column_type, "users"
end
@@ -40,6 +41,7 @@ module JSONSharedTestCases
end
def test_schema_dumping
+ skip unless @connection.supports_json?
output = dump_table_schema("json_data_type")
assert_match(/t\.#{column_type}\s+"settings"/, output)
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 743680ba92..6791d50940 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -565,18 +565,18 @@ unless in_memory_db?
end
end
- # Locking a record reloads it.
- def test_sane_lock_method
+ def test_lock_does_not_raise_when_the_object_is_not_dirty
+ person = Person.find 1
assert_nothing_raised do
- Person.transaction do
- person = Person.find 1
- old, person.first_name = person.first_name, "fooman"
- # Locking a dirty record is deprecated
- assert_deprecated do
- person.lock!
- end
- assert_equal old, person.first_name
- end
+ person.lock!
+ end
+ end
+
+ def test_lock_raises_when_the_record_is_dirty
+ person = Person.find 1
+ person.first_name = "fooman"
+ assert_raises(RuntimeError) do
+ person.lock!
end
end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 0b5e983f14..58bc558619 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -213,6 +213,11 @@ module ActiveRecord
assert_equal [:remove_index, [:table, { name: "new_index" }]], remove
end
+ def test_invert_add_index_with_algorithm_option
+ remove = @recorder.inverse_of :add_index, [:table, :one, algorithm: :concurrently]
+ assert_equal [:remove_index, [:table, { column: :one, algorithm: :concurrently }]], remove
+ end
+
def test_invert_remove_index
add = @recorder.inverse_of :remove_index, [:table, :one]
assert_equal [:add_index, [:table, :one]], add
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index cb3b02c02a..2fef2f796e 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -130,11 +130,9 @@ module ActiveRecord
end
end
-class LegacyPrimaryKeyTest < ActiveRecord::TestCase
+module LegacyPrimaryKeyTestCases
include SchemaDumpingHelper
- self.use_transactional_tests = false
-
class LegacyPrimaryKey < ActiveRecord::Base
end
@@ -152,7 +150,7 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
end
def test_legacy_primary_key_should_be_auto_incremented
- @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ @migration = Class.new(migration_class) {
def change
create_table :legacy_primary_keys do |t|
t.references :legacy_ref
@@ -182,7 +180,7 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
def test_legacy_integer_primary_key_should_not_be_auto_incremented
skip if current_adapter?(:SQLite3Adapter)
- @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ @migration = Class.new(migration_class) {
def change
create_table :legacy_primary_keys, id: :integer do |t|
end
@@ -199,9 +197,90 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
assert_match %r{create_table "legacy_primary_keys", id: :integer, default: nil}, schema
end
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+ def test_legacy_primary_key_in_create_table_should_be_integer
+ @migration = Class.new(migration_class) {
+ def change
+ create_table :legacy_primary_keys, id: false do |t|
+ t.primary_key :id
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", id: :(?:integer|serial), (?!default: nil)}, schema
+ end
+
+ def test_legacy_primary_key_in_change_table_should_be_integer
+ @migration = Class.new(migration_class) {
+ def change
+ create_table :legacy_primary_keys, id: false do |t|
+ t.integer :dummy
+ end
+ change_table :legacy_primary_keys do |t|
+ t.primary_key :id
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", id: :(?:integer|serial), (?!default: nil)}, schema
+ end
+
+ def test_add_column_with_legacy_primary_key_should_be_integer
+ @migration = Class.new(migration_class) {
+ def change
+ create_table :legacy_primary_keys, id: false do |t|
+ t.integer :dummy
+ end
+ add_column :legacy_primary_keys, :id, :primary_key
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", id: :(?:integer|serial), (?!default: nil)}, schema
+ end
+ end
+
+ def test_legacy_join_table_foreign_keys_should_be_integer
+ @migration = Class.new(migration_class) {
+ def change
+ create_join_table :apples, :bananas do |t|
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ schema = dump_table_schema "apples_bananas"
+ assert_match %r{integer "apple_id", null: false}, schema
+ assert_match %r{integer "banana_id", null: false}, schema
+ end
+
+ def test_legacy_join_table_column_options_should_be_overwritten
+ @migration = Class.new(migration_class) {
+ def change
+ create_join_table :apples, :bananas, column_options: { type: :bigint } do |t|
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ schema = dump_table_schema "apples_bananas"
+ assert_match %r{bigint "apple_id", null: false}, schema
+ assert_match %r{bigint "banana_id", null: false}, schema
+ end
+
if current_adapter?(:Mysql2Adapter)
def test_legacy_bigint_primary_key_should_be_auto_incremented
- @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ @migration = Class.new(migration_class) {
def change
create_table :legacy_primary_keys, id: :bigint
end
@@ -218,7 +297,7 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
end
else
def test_legacy_bigint_primary_key_should_not_be_auto_incremented
- @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ @migration = Class.new(migration_class) {
def change
create_table :legacy_primary_keys, id: :bigint do |t|
end
@@ -236,3 +315,27 @@ class LegacyPrimaryKeyTest < ActiveRecord::TestCase
end
end
end
+
+module LegacyPrimaryKeyTest
+ class V5_0 < ActiveRecord::TestCase
+ include LegacyPrimaryKeyTestCases
+
+ self.use_transactional_tests = false
+
+ private
+ def migration_class
+ ActiveRecord::Migration[5.0]
+ end
+ end
+
+ class V4_2 < ActiveRecord::TestCase
+ include LegacyPrimaryKeyTestCases
+
+ self.use_transactional_tests = false
+
+ private
+ def migration_class
+ ActiveRecord::Migration[4.2]
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index bf1ebdb4c5..b25c6d84bc 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -33,10 +33,8 @@ module ActiveRecord
connection.add_index(table_name, [:foo], name: "old_idx")
connection.rename_index(table_name, "old_idx", "new_idx")
- assert_deprecated do
- assert_not connection.index_name_exists?(table_name, "old_idx", false)
- assert connection.index_name_exists?(table_name, "new_idx", true)
- end
+ assert_not connection.index_name_exists?(table_name, "old_idx")
+ assert connection.index_name_exists?(table_name, "new_idx")
end
def test_rename_index_too_long
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 07afa89779..b18af2ab55 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1106,17 +1106,4 @@ class CopyMigrationsTest < ActiveRecord::TestCase
def test_unknown_migration_version_should_raise_an_argument_error
assert_raise(ArgumentError) { ActiveRecord::Migration[1.0] }
end
-
- def test_deprecate_initialize_internal_tables
- assert_deprecated { ActiveRecord::Base.connection.initialize_schema_migrations_table }
- assert_deprecated { ActiveRecord::Base.connection.initialize_internal_metadata_table }
- end
-
- def test_deprecate_supports_migrations
- assert_deprecated { ActiveRecord::Base.connection.supports_migrations? }
- end
-
- def test_deprecate_schema_migrations_table_name
- assert_deprecated { ActiveRecord::Migrator.schema_migrations_table_name }
- end
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 170fd02b6f..6cbe18cc8c 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -70,10 +70,10 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_update_many
- topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" }, nil => {} }
updated = Topic.update(topic_data.keys, topic_data.values)
- assert_equal 2, updated.size
+ assert_equal [1, 2], updated.map(&:id)
assert_equal "1 updated", Topic.find(1).content
assert_equal "2 updated", Topic.find(2).content
end
@@ -81,9 +81,8 @@ class PersistenceTest < ActiveRecord::TestCase
def test_class_level_update_is_affected_by_scoping
topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
- assert_raise(ActiveRecord::RecordNotFound) do
- Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) }
- end
+ assert_equal [], Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) }
+
assert_not_equal "1 updated", Topic.find(1).content
assert_not_equal "2 updated", Topic.find(2).content
end
@@ -175,7 +174,7 @@ class PersistenceTest < ActiveRecord::TestCase
clients = Client.all.merge!(order: "id").find([2, 3])
assert_difference("Client.count", -2) do
- destroyed = Client.destroy([2, 3]).sort_by(&:id)
+ destroyed = Client.destroy([2, 3, nil]).sort_by(&:id)
assert_equal clients, destroyed
assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
end
@@ -917,7 +916,9 @@ class PersistenceTest < ActiveRecord::TestCase
should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
Topic.find(1).replies << should_be_destroyed_reply
- Topic.destroy(1)
+ topic = Topic.destroy(1)
+ assert topic.destroyed?
+
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
end
@@ -926,9 +927,8 @@ class PersistenceTest < ActiveRecord::TestCase
should_not_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
Topic.find(1).replies << should_not_be_destroyed_reply
- assert_raise(ActiveRecord::RecordNotFound) do
- Topic.where("1=0").scoping { Topic.destroy(1) }
- end
+ assert_nil Topic.where("1=0").scoping { Topic.destroy(1) }
+
assert_nothing_raised { Topic.find(1) }
assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) }
end
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index e0888f5fe9..7f6c2382ca 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -156,10 +156,6 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_nothing_raised { MixedCaseMonkey.find(1).destroy }
end
- def test_deprecate_supports_primary_key
- assert_deprecated { ActiveRecord::Base.connection.supports_primary_key? }
- end
-
def test_primary_key_returns_value_if_it_exists
klass = Class.new(ActiveRecord::Base) do
self.table_name = "developers"
@@ -334,16 +330,26 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
t.string :region
t.integer :code
end
+ @connection.create_table(:travels, primary_key: ["from", "to"], force: true) do |t|
+ t.string :from
+ t.string :to
+ end
end
def teardown
- @connection.drop_table(:uber_barcodes, if_exists: true)
+ @connection.drop_table :uber_barcodes, if_exists: true
+ @connection.drop_table :barcodes_reverse, if_exists: true
+ @connection.drop_table :travels, if_exists: true
end
def test_composite_primary_key
assert_equal ["region", "code"], @connection.primary_keys("uber_barcodes")
end
+ def test_composite_primary_key_with_reserved_words
+ assert_equal ["from", "to"], @connection.primary_keys("travels")
+ end
+
def test_composite_primary_key_out_of_order
skip if current_adapter?(:SQLite3Adapter)
assert_equal ["code", "region"], @connection.primary_keys("barcodes_reverse")
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index d3f4b5bf75..5cb537b623 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -302,14 +302,10 @@ class QueryCacheTest < ActiveRecord::TestCase
end
end
- def test_cache_does_not_wrap_string_results_in_arrays
+ def test_cache_does_not_wrap_results_in_arrays
Task.cache do
- # Oracle adapter returns count() as Integer or Float
- if current_adapter?(:OracleAdapter)
- assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- elsif current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter)
- # Future versions of the sqlite3 adapter will return numeric
- assert_instance_of 0.class, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
+ if current_adapter?(:SQLite3Adapter, :Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter)
+ assert_equal 2, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
else
assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
end
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 59d3bbb573..897d252cf8 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -83,23 +83,6 @@ module ActiveRecord
end
end
- class QuotedOne
- def quoted_id
- 1
- end
- end
- class SubQuotedOne < QuotedOne
- end
- def test_quote_with_quoted_id
- assert_deprecated(/defined on \S+::QuotedOne at .*quoting_test\.rb:[0-9]/) do
- assert_equal 1, @quoter.quote(QuotedOne.new)
- end
-
- assert_deprecated(/defined on \S+::SubQuotedOne\(\S+::QuotedOne\) at .*quoting_test\.rb:[0-9]/) do
- assert_equal 1, @quoter.quote(SubQuotedOne.new)
- end
- end
-
def test_quote_nil
assert_equal "NULL", @quoter.quote(nil)
end
@@ -207,26 +190,6 @@ module ActiveRecord
obj = Class.new.new
assert_raise(TypeError) { @conn.type_cast(obj) }
end
-
- def test_type_cast_object_which_responds_to_quoted_id
- quoted_id_obj = Class.new {
- def quoted_id
- "'zomg'"
- end
-
- def id
- 10
- end
- }.new
- assert_equal 10, @conn.type_cast(quoted_id_obj)
-
- quoted_id_obj = Class.new {
- def quoted_id
- "'zomg'"
- end
- }.new
- assert_raise(TypeError) { @conn.type_cast(quoted_id_obj) }
- end
end
class QuoteBooleanTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 4cd2d3aedc..37c2235f1a 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -25,7 +25,6 @@ require "models/chef"
require "models/department"
require "models/cake_designer"
require "models/drink_designer"
-require "models/mocktail_designer"
require "models/recipe"
class ReflectionTest < ActiveRecord::TestCase
@@ -254,32 +253,6 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal expected, actual
end
- def test_scope_chain
- expected = [
- [Tagging.reflect_on_association(:tag).scope, Post.reflect_on_association(:first_blue_tags).scope],
- [Post.reflect_on_association(:first_taggings).scope],
- [Author.reflect_on_association(:misc_posts).scope]
- ]
- actual = assert_deprecated do
- Author.reflect_on_association(:misc_post_first_blue_tags).scope_chain
- end
- assert_equal expected, actual
-
- expected = [
- [
- Tagging.reflect_on_association(:blue_tag).scope,
- Post.reflect_on_association(:first_blue_tags_2).scope,
- Author.reflect_on_association(:misc_post_first_blue_tags_2).scope
- ],
- [],
- []
- ]
- actual = assert_deprecated do
- Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
- end
- assert_equal expected, actual
- end
-
def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case
@hotel = Hotel.create!
@department = @hotel.departments.create!
@@ -366,9 +339,16 @@ class ReflectionTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.active_record_primary_key }
end
+ def test_type
+ assert_equal "taggable_type", Post.reflect_on_association(:taggings).type.to_s
+ assert_equal "imageable_class", Post.reflect_on_association(:images).type.to_s
+ assert_nil Post.reflect_on_association(:readers).type
+ end
+
def test_foreign_type
assert_equal "sponsorable_type", Sponsor.reflect_on_association(:sponsorable).foreign_type.to_s
assert_equal "sponsorable_type", Sponsor.reflect_on_association(:thing).foreign_type.to_s
+ assert_nil Sponsor.reflect_on_association(:sponsor_club).foreign_type
end
def test_collection_association
@@ -409,26 +389,15 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "category_id", Post.reflect_on_association(:categorizations).foreign_key.to_s
end
- def test_through_reflection_scope_chain_does_not_modify_other_reflections
- orig_conds = assert_deprecated do
- Post.reflect_on_association(:first_blue_tags_2).scope_chain
- end.inspect
- assert_deprecated do
- Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
- end
- assert_equal orig_conds, assert_deprecated {
- Post.reflect_on_association(:first_blue_tags_2).scope_chain
- }.inspect
- end
-
def test_symbol_for_class_name
assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass
end
def test_class_for_class_name
- assert_deprecated do
- assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { class_name: Client }, Firm), :validate?
+ error = assert_raises(ArgumentError) do
+ ActiveRecord::Reflection.create(:has_many, :clients, nil, { class_name: Client }, Firm)
end
+ assert_equal "A class was passed to `:class_name` but we are expecting a string.", error.message
end
def test_join_table
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index 955e9fc9ce..7e418f9c7d 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -31,7 +31,7 @@ module ActiveRecord
end
def test_or_with_bind_params
- assert_equal Post.find([1, 2]), Post.where(id: 1).or(Post.where(id: 2)).to_a
+ assert_equal Post.find([1, 2]).sort_by(&:id), Post.where(id: 1).or(Post.where(id: 2)).sort_by(&:id)
end
def test_or_with_null_both
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index c1805aa592..fd5985ffe7 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -73,11 +73,6 @@ module ActiveRecord
assert_equal({}, relation.where_values_hash)
end
- def test_table_name_delegates_to_klass
- relation = Relation.new(FakeKlass, :b, Post.predicate_builder)
- assert_equal "posts", relation.table_name
- end
-
def test_scope_for_create
relation = Relation.new(FakeKlass, :b, nil)
assert_equal({}, relation.scope_for_create)
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index ae1dc35bff..72433d1e8e 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -19,12 +19,12 @@ require "models/tyre"
require "models/minivan"
require "models/possession"
require "models/reader"
+require "models/category"
require "models/categorization"
require "models/edge"
class RelationTest < ActiveRecord::TestCase
- fixtures :authors, :author_addresses, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
- :tags, :taggings, :cars, :minivans
+ fixtures :authors, :author_addresses, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :categories_posts, :posts, :comments, :tags, :taggings, :cars, :minivans
class TopicWithCallbacks < ActiveRecord::Base
self.table_name = :topics
@@ -845,32 +845,6 @@ class RelationTest < ActiveRecord::TestCase
}
end
- def test_exists
- davids = Author.where(name: "David")
- assert davids.exists?
- assert davids.exists?(authors(:david).id)
- assert ! davids.exists?(authors(:mary).id)
- assert ! davids.exists?("42")
- assert ! davids.exists?(42)
- assert ! davids.exists?(davids.new.id)
-
- fake = Author.where(name: "fake author")
- assert ! fake.exists?
- assert ! fake.exists?(authors(:david).id)
- end
-
- def test_exists_uses_existing_scope
- post = authors(:david).posts.first
- authors = Author.includes(:posts).where(name: "David", posts: { id: post.id })
- assert authors.exists?(authors(:david).id)
- end
-
- def test_any_with_scope_on_hash_includes
- post = authors(:david).posts.first
- categories = Categorization.includes(author: :posts).where(posts: { id: post.id })
- assert categories.exists?
- end
-
def test_last
authors = Author.all
assert_equal authors(:bob), authors.last
@@ -1806,6 +1780,14 @@ class RelationTest < ActiveRecord::TestCase
assert_equal post, custom_post_relation.joins(:author).where!(title: post.title).take
end
+ test "arel_attribute respects a custom table" do
+ assert_equal [posts(:welcome)], custom_post_relation.ranked_by_comments.limit_by(1).to_a
+ end
+
+ test "alias_tracker respects a custom table" do
+ assert_equal posts(:welcome), custom_post_relation("categories_posts").joins(:categories).first
+ end
+
test "#load" do
relation = Post.all
assert_queries(1) do
@@ -1912,9 +1894,22 @@ class RelationTest < ActiveRecord::TestCase
end
end
+ test "#where with set" do
+ david = authors(:david)
+ mary = authors(:mary)
+
+ authors = Author.where(name: ["David", "Mary"].to_set)
+ assert_equal [david, mary], authors
+ end
+
+ test "#where with empty set" do
+ authors = Author.where(name: Set.new)
+ assert_empty authors
+ end
+
private
- def custom_post_relation
- table_alias = Post.arel_table.alias("omg_posts")
+ def custom_post_relation(alias_name = "omg_posts")
+ table_alias = Post.arel_table.alias(alias_name)
table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias)
predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 082d663675..85a555fe35 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -153,18 +153,6 @@ class SanitizeTest < ActiveRecord::TestCase
assert_equal "name=#{quoted_bambi_and_thumper}", bind("name=?", "Bambi\nand\nThumper".mb_chars)
end
- def test_bind_record
- o = Class.new {
- def quoted_id
- 1
- end
- }.new
- assert_deprecated { assert_equal "1", bind("?", o) }
-
- os = [o] * 3
- assert_deprecated { assert_equal "1,1,1", bind("?", os) }
- end
-
def test_named_bind_with_postgresql_type_casts
l = Proc.new { bind(":a::integer '2009-01-01'::date", a: "10") }
assert_nothing_raised(&l)
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 799a65c61e..60e3bc17f2 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -294,34 +294,32 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{t\.oid\s+"obj_id"$}, output
end
- if ActiveRecord::Base.connection.supports_extensions?
- def test_schema_dump_includes_extensions
- connection = ActiveRecord::Base.connection
-
- connection.stubs(:extensions).returns(["hstore"])
- output = perform_schema_dump
- assert_match "# These are extensions that must be enabled", output
- assert_match %r{enable_extension "hstore"}, output
-
- connection.stubs(:extensions).returns([])
- output = perform_schema_dump
- assert_no_match "# These are extensions that must be enabled", output
- assert_no_match %r{enable_extension}, output
- end
+ def test_schema_dump_includes_extensions
+ connection = ActiveRecord::Base.connection
+
+ connection.stubs(:extensions).returns(["hstore"])
+ output = perform_schema_dump
+ assert_match "# These are extensions that must be enabled", output
+ assert_match %r{enable_extension "hstore"}, output
+
+ connection.stubs(:extensions).returns([])
+ output = perform_schema_dump
+ assert_no_match "# These are extensions that must be enabled", output
+ assert_no_match %r{enable_extension}, output
+ end
- def test_schema_dump_includes_extensions_in_alphabetic_order
- connection = ActiveRecord::Base.connection
+ def test_schema_dump_includes_extensions_in_alphabetic_order
+ connection = ActiveRecord::Base.connection
- connection.stubs(:extensions).returns(["hstore", "uuid-ossp", "xml2"])
- output = perform_schema_dump
- enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten
- assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions
+ connection.stubs(:extensions).returns(["hstore", "uuid-ossp", "xml2"])
+ output = perform_schema_dump
+ enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten
+ assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions
- connection.stubs(:extensions).returns(["uuid-ossp", "xml2", "hstore"])
- output = perform_schema_dump
- enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten
- assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions
- end
+ connection.stubs(:extensions).returns(["uuid-ossp", "xml2", "hstore"])
+ output = perform_schema_dump
+ enabled_extensions = output.scan(%r{enable_extension "(.+)"}).flatten
+ assert_equal ["hstore", "uuid-ossp", "xml2"], enabled_extensions
end
end
diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb
index 6302e84884..ca1defa332 100644
--- a/activerecord/test/cases/tasks/postgresql_rake_test.rb
+++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb
@@ -229,7 +229,6 @@ if current_adapter?(:PostgreSQLAdapter)
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
- Kernel.stubs(:system)
end
def teardown
@@ -333,7 +332,6 @@ if current_adapter?(:PostgreSQLAdapter)
ActiveRecord::Base.stubs(:connection).returns(@connection)
ActiveRecord::Base.stubs(:establish_connection).returns(true)
- Kernel.stubs(:system)
end
def test_structure_load
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 09958ca257..e9eba9be2e 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -21,7 +21,8 @@ class Author < ActiveRecord::Base
end
has_many :comments_containing_the_letter_e, through: :posts, source: :comments
has_many :comments_with_order_and_conditions, -> { order("comments.body").where("comments.body like 'Thank%'") }, through: :posts, source: :comments
- has_many :comments_with_include, -> { includes(:post) }, through: :posts, source: :comments
+ has_many :comments_with_include, -> { includes(:post).where(posts: { type: "Post" }) }, through: :posts, source: :comments
+ has_many :comments_for_first_author, -> { for_first_author }, through: :posts, source: :comments
has_many :first_posts
has_many :comments_on_first_posts, -> { order("posts.id desc, comments.id asc") }, through: :first_posts, source: :comments
@@ -40,6 +41,7 @@ class Author < ActiveRecord::Base
class_name: "Post"
has_many :comments_desc, -> { order("comments.id DESC") }, through: :posts, source: :comments
+ has_many :unordered_comments, -> { unscope(:order).distinct }, through: :posts_sorted_by_id_limited, source: :comments
has_many :funky_comments, through: :posts, source: :comments
has_many :ordered_uniq_comments, -> { distinct.order("comments.id") }, through: :posts, source: :comments
has_many :ordered_uniq_comments_desc, -> { distinct.order("comments.id DESC") }, through: :posts, source: :comments
@@ -78,7 +80,7 @@ class Author < ActiveRecord::Base
after_add: [:log_after_adding, Proc.new { |o, r| o.post_log << "after_adding_proc#{r.id || '<new>'}" }]
has_many :unchangeable_posts, class_name: "Post", before_add: :raise_exception, after_add: :log_after_adding
- has_many :categorizations
+ has_many :categorizations, -> {}
has_many :categories, through: :categorizations
has_many :named_categories, through: :categorizations
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 56aafca60b..8881c69368 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -87,6 +87,17 @@ class Developer < ActiveRecord::Base
private :track_instance_count
end
+class SubDeveloper < Developer
+end
+
+class SymbolIgnoredDeveloper < ActiveRecord::Base
+ self.table_name = "developers"
+ self.ignored_columns = [:first_name, :last_name]
+
+ attr_accessor :last_name
+ define_attribute_method "last_name"
+end
+
class AuditLog < ActiveRecord::Base
belongs_to :developer, validate: true
belongs_to :unvalidated_developer, class_name: "Developer"
diff --git a/activerecord/test/models/drink_designer.rb b/activerecord/test/models/drink_designer.rb
index 1c407844c5..eb6701b84e 100644
--- a/activerecord/test/models/drink_designer.rb
+++ b/activerecord/test/models/drink_designer.rb
@@ -3,3 +3,6 @@
class DrinkDesigner < ActiveRecord::Base
has_one :chef, as: :employable
end
+
+class MocktailDesigner < DrinkDesigner
+end
diff --git a/activerecord/test/models/mocktail_designer.rb b/activerecord/test/models/mocktail_designer.rb
deleted file mode 100644
index 123ff4fb3d..0000000000
--- a/activerecord/test/models/mocktail_designer.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-class MocktailDesigner < DrinkDesigner
-end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 4c8e847354..935a11e811 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -21,7 +21,7 @@ class Post < ActiveRecord::Base
scope :containing_the_letter_a, -> { where("body LIKE '%a%'") }
scope :titled_with_an_apostrophe, -> { where("title LIKE '%''%'") }
- scope :ranked_by_comments, -> { order("comments_count DESC") }
+ scope :ranked_by_comments, -> { order(arel_attribute(:comments_count).desc) }
scope :limit_by, lambda { |l| limit(l) }
scope :locked, -> { lock }
diff --git a/activestorage/README.md b/activestorage/README.md
index 17d98978bb..78e4463c5a 100644
--- a/activestorage/README.md
+++ b/activestorage/README.md
@@ -12,6 +12,10 @@ A key difference to how Active Storage works compared to other attachment soluti
`Blob` models store attachment metadata (filename, content-type, etc.), and their identifier key in the storage service. Blob models do not store the actual binary data. They are intended to be immutable in spirit. One file, one blob. You can associate the same blob with multiple application models as well. And if you want to do transformations of a given `Blob`, the idea is that you'll simply create a new one, rather than attempt to mutate the existing one (though of course you can delete the previous version later if you don't need it).
+## Installation
+
+Run `rails active_storage:install` to copy over active_storage migrations.
+
## Examples
One attachment:
@@ -24,7 +28,7 @@ class User < ApplicationRecord
end
# Attach an avatar to the user.
-user.avatar.attach(io: File.open("~/face.jpg"), filename: "avatar.jpg", content_type: "image/jpg")
+user.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
# Does the user have an avatar?
user.avatar.attached? # => true
@@ -63,7 +67,7 @@ end
```
```erb
-<%= form_with model: @message do |form| %>
+<%= form_with model: @message, local: true do |form| %>
<%= form.text_field :title, placeholder: "Title" %><br>
<%= form.text_area :content %><br><br>
diff --git a/activestorage/Rakefile b/activestorage/Rakefile
index aa71a65f6e..2aa4d2a76f 100644
--- a/activestorage/Rakefile
+++ b/activestorage/Rakefile
@@ -11,4 +11,6 @@ Rake::TestTask.new do |test|
test.warning = false
end
+task :package
+
task default: :test
diff --git a/activestorage/app/assets/javascripts/activestorage.js b/activestorage/app/assets/javascripts/activestorage.js
index 33dc5cdc58..c1c0a2f6d9 100644
--- a/activestorage/app/assets/javascripts/activestorage.js
+++ b/activestorage/app/assets/javascripts/activestorage.js
@@ -1 +1 @@
-!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=r.bubbles,i=r.cancelable,a=r.detail,u=document.createEvent("Event");return u.initEvent(e,n||!0,i||!0),u.detail=a||{},t.dispatchEvent(u),u}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i<r;i++)n[i-2]=arguments[i];return t[e].apply(t,n)}}r.d(e,"a",function(){return c});var a=r(6),u=r(8),o=r(9),s=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),f=0,c=function(){function t(e,r,i){n(this,t),this.id=++f,this.file=e,this.url=r,this.delegate=i}return s(t,[{key:"create",value:function(t){var e=this;a.a.create(this.file,function(r,n){var a=new u.a(e.file,n,e.url);i(e.delegate,"directUploadWillCreateBlobWithXHR",a.xhr),a.create(function(r){if(r)t(r);else{var n=new o.a(a);i(e.delegate,"directUploadWillStoreFileWithXHR",n.xhr),n.create(function(e){e?t(e):t(null,a.toJSON())})}})})}}]),t}()},function(t,e,r){"use strict";function n(){window.ActiveStorage&&Object(i.a)()}Object.defineProperty(e,"__esModule",{value:!0});var i=r(3),a=r(1);r.d(e,"start",function(){return i.a}),r.d(e,"DirectUpload",function(){return a.a}),setTimeout(n,1)},function(t,e,r){"use strict";function n(){d||(d=!0,document.addEventListener("submit",i),document.addEventListener("ajax:before",a))}function i(t){u(t)}function a(t){"FORM"==t.target.tagName&&u(t)}function u(t){var e=t.target;if(e.hasAttribute(l))return void t.preventDefault();var r=new c.a(e),n=r.inputs;n.length&&(t.preventDefault(),e.setAttribute(l,""),n.forEach(s),r.start(function(t){e.removeAttribute(l),t?n.forEach(f):o(e)}))}function o(t){var e=Object(h.b)(t,"input[type=submit]");if(e){var r=e,n=r.disabled;e.disabled=!1,e.click(),e.disabled=n}else e=document.createElement("input"),e.type="submit",e.style="display:none",t.appendChild(e),e.click(),t.removeChild(e)}function s(t){t.disabled=!0}function f(t){t.disabled=!1}e.a=n;var c=r(4),h=r(0),l="data-direct-uploads-processing",d=!1},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(5),a=r(0),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o="input[type=file][data-direct-upload-url]:not([disabled])",s=function(){function t(e){n(this,t),this.form=e,this.inputs=Object(a.c)(e,o).filter(function(t){return t.files.length})}return u(t,[{key:"start",value:function(t){var e=this,r=this.createDirectUploadControllers();this.dispatch("start"),function n(){var i=r.shift();i?i.start(function(r){r?(t(r),e.dispatch("end")):n()}):(t(),e.dispatch("end"))}()}},{key:"createDirectUploadControllers",value:function(){var t=[];return this.inputs.forEach(function(e){Object(a.e)(e.files).forEach(function(r){var n=new i.a(e,r);t.push(n)})}),t}},{key:"dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o=function(){function t(e,r){n(this,t),this.input=e,this.file=r,this.directUpload=new i.a(this.file,this.url,this),this.dispatch("initialize")}return u(t,[{key:"start",value:function(t){var e=this,r=document.createElement("input");r.type="hidden",r.name=this.input.name,this.input.insertAdjacentElement("beforebegin",r),this.dispatch("start"),this.directUpload.create(function(n,i){n?(r.parentNode.removeChild(r),e.dispatchError(n)):r.value=i.signed_id,e.dispatch("end"),t(n)})}},{key:"uploadRequestDidProgress",value:function(t){var e=t.loaded/t.total*100;e&&this.dispatch("progress",{progress:e})}},{key:"dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o=File.prototype.slice||File.prototype.mozSlice||File.prototype.webkitSlice,s=function(){function t(e){n(this,t),this.file=e,this.chunkSize=2097152,this.chunkCount=Math.ceil(this.file.size/this.chunkSize),this.chunkIndex=0}return u(t,null,[{key:"create",value:function(e,r){new t(e).create(r)}}]),u(t,[{key:"create",value:function(t){var e=this;this.callback=t,this.md5Buffer=new a.a.ArrayBuffer,this.fileReader=new FileReader,this.fileReader.addEventListener("load",function(t){return e.fileReaderDidLoad(t)}),this.fileReader.addEventListener("error",function(t){return e.fileReaderDidError(t)}),this.readNextChunk()}},{key:"fileReaderDidLoad",value:function(t){if(this.md5Buffer.append(t.target.result),!this.readNextChunk()){var e=this.md5Buffer.end(!0),r=btoa(e);this.callback(null,r)}}},{key:"fileReaderDidError",value:function(t){this.callback("Error reading "+this.file.name)}},{key:"readNextChunk",value:function(){if(this.chunkIndex<this.chunkCount){var t=this.chunkIndex*this.chunkSize,e=Math.min(t+this.chunkSize,this.file.size),r=o.call(this.file,t,e);return this.fileReader.readAsArrayBuffer(r),this.chunkIndex++,!0}return!1}}]),t}()},function(t,e,r){!function(e){t.exports=e()}(function(t){"use strict";function e(t,e){var r=t[0],n=t[1],i=t[2],a=t[3];r+=(n&i|~n&a)+e[0]-680876936|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n<i;n+=1)a[n>>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64<f?t.subarray(r-64):new Uint8Array(0),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],r=0;r<i;r+=1)a[r>>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e<t.length;e+=1)t[e]=u(t[e]);return t.join("")}function s(t){return/[\u0080-\uFFFF]/.test(t)&&(t=unescape(encodeURIComponent(t))),t}function f(t,e){var r,n=t.length,i=new ArrayBuffer(n),a=new Uint8Array(i);for(r=0;r<n;r+=1)a[r]=t.charCodeAt(r);return e?a:i}function c(t){return String.fromCharCode.apply(null,new Uint8Array(t))}function h(t,e,r){var n=new Uint8Array(t.byteLength+e.byteLength);return n.set(new Uint8Array(t)),n.set(new Uint8Array(e),t.byteLength),r?n:n.buffer}function l(t){var e,r=[],n=t.length;for(e=0;e<n-1;e+=2)r.push(parseInt(t.substr(e,2),16));return String.fromCharCode.apply(String,r)}function d(){this.reset()}var p=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];return"5d41402abc4b2a76b9719d911017c592"!==o(i("hello"))&&function(t,e){var r=(65535&t)+(65535&e);return(t>>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<i;e+=1)a[e>>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64<a?new Uint8Array(i.buffer.slice(r-64)):new Uint8Array(0),this},d.ArrayBuffer.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<i;e+=1)a[e>>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),u=function(){function t(e,r,a){var u=this;n(this,t),this.file=e,this.attributes={filename:e.name,content_type:e.type,byte_size:e.size,checksum:r},this.xhr=new XMLHttpRequest,this.xhr.open("POST",a,!0),this.xhr.responseType="json",this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.setRequestHeader("Accept","application/json"),this.xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this.xhr.setRequestHeader("X-CSRF-Token",Object(i.d)("csrf-token")),this.xhr.addEventListener("load",function(t){return u.requestDidLoad(t)}),this.xhr.addEventListener("error",function(t){return u.requestDidError(t)})}return a(t,[{key:"create",value:function(t){this.callback=t,this.xhr.send(JSON.stringify({blob:this.attributes}))}},{key:"requestDidLoad",value:function(t){var e=this.xhr,r=e.status,n=e.response;if(r>=200&&r<300){var i=n.direct_upload;delete n.direct_upload,this.attributes=n,this.directUploadData=i,this.callback(null,this.toJSON())}else this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.xhr.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),a=function(){function t(e){var r=this;n(this,t),this.blob=e,this.file=e.file;var i=e.directUploadData,a=i.url,u=i.headers;this.xhr=new XMLHttpRequest,this.xhr.open("PUT",a,!0);for(var o in u)this.xhr.setRequestHeader(o,u[o]);this.xhr.addEventListener("load",function(t){return r.requestDidLoad(t)}),this.xhr.addEventListener("error",function(t){return r.requestDidError(t)})}return i(t,[{key:"create",value:function(t){this.callback=t,this.xhr.send(this.file)}},{key:"requestDidLoad",value:function(t){var e=this.xhr,r=e.status,n=e.response;r>=200&&r<300?this.callback(null,n):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])}); \ No newline at end of file
+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=r.bubbles,i=r.cancelable,a=r.detail,u=document.createEvent("Event");return u.initEvent(e,n||!0,i||!0),u.detail=a||{},t.dispatchEvent(u),u}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i<r;i++)n[i-2]=arguments[i];return t[e].apply(t,n)}}r.d(e,"a",function(){return c});var a=r(6),u=r(8),o=r(9),s=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),f=0,c=function(){function t(e,r,i){n(this,t),this.id=++f,this.file=e,this.url=r,this.delegate=i}return s(t,[{key:"create",value:function(t){var e=this;a.a.create(this.file,function(r,n){var a=new u.a(e.file,n,e.url);i(e.delegate,"directUploadWillCreateBlobWithXHR",a.xhr),a.create(function(r){if(r)t(r);else{var n=new o.a(a);i(e.delegate,"directUploadWillStoreFileWithXHR",n.xhr),n.create(function(e){e?t(e):t(null,a.toJSON())})}})})}}]),t}()},function(t,e,r){"use strict";function n(){window.ActiveStorage&&Object(i.a)()}Object.defineProperty(e,"__esModule",{value:!0});var i=r(3),a=r(1);r.d(e,"start",function(){return i.a}),r.d(e,"DirectUpload",function(){return a.a}),setTimeout(n,1)},function(t,e,r){"use strict";function n(){d||(d=!0,document.addEventListener("submit",i),document.addEventListener("ajax:before",a))}function i(t){u(t)}function a(t){"FORM"==t.target.tagName&&u(t)}function u(t){var e=t.target;if(e.hasAttribute(l))return void t.preventDefault();var r=new c.a(e),n=r.inputs;n.length&&(t.preventDefault(),e.setAttribute(l,""),n.forEach(s),r.start(function(t){e.removeAttribute(l),t?n.forEach(f):o(e)}))}function o(t){var e=Object(h.b)(t,"input[type=submit]");if(e){var r=e,n=r.disabled;e.disabled=!1,e.focus(),e.click(),e.disabled=n}else e=document.createElement("input"),e.type="submit",e.style="display:none",t.appendChild(e),e.click(),t.removeChild(e)}function s(t){t.disabled=!0}function f(t){t.disabled=!1}e.a=n;var c=r(4),h=r(0),l="data-direct-uploads-processing",d=!1},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(5),a=r(0),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o="input[type=file][data-direct-upload-url]:not([disabled])",s=function(){function t(e){n(this,t),this.form=e,this.inputs=Object(a.c)(e,o).filter(function(t){return t.files.length})}return u(t,[{key:"start",value:function(t){var e=this,r=this.createDirectUploadControllers();this.dispatch("start"),function n(){var i=r.shift();i?i.start(function(r){r?(t(r),e.dispatch("end")):n()}):(t(),e.dispatch("end"))}()}},{key:"createDirectUploadControllers",value:function(){var t=[];return this.inputs.forEach(function(e){Object(a.e)(e.files).forEach(function(r){var n=new i.a(e,r);t.push(n)})}),t}},{key:"dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o=function(){function t(e,r){n(this,t),this.input=e,this.file=r,this.directUpload=new i.a(this.file,this.url,this),this.dispatch("initialize")}return u(t,[{key:"start",value:function(t){var e=this,r=document.createElement("input");r.type="hidden",r.name=this.input.name,this.input.insertAdjacentElement("beforebegin",r),this.dispatch("start"),this.directUpload.create(function(n,i){n?(r.parentNode.removeChild(r),e.dispatchError(n)):r.value=i.signed_id,e.dispatch("end"),t(n)})}},{key:"uploadRequestDidProgress",value:function(t){var e=t.loaded/t.total*100;e&&this.dispatch("progress",{progress:e})}},{key:"dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o=File.prototype.slice||File.prototype.mozSlice||File.prototype.webkitSlice,s=function(){function t(e){n(this,t),this.file=e,this.chunkSize=2097152,this.chunkCount=Math.ceil(this.file.size/this.chunkSize),this.chunkIndex=0}return u(t,null,[{key:"create",value:function(e,r){new t(e).create(r)}}]),u(t,[{key:"create",value:function(t){var e=this;this.callback=t,this.md5Buffer=new a.a.ArrayBuffer,this.fileReader=new FileReader,this.fileReader.addEventListener("load",function(t){return e.fileReaderDidLoad(t)}),this.fileReader.addEventListener("error",function(t){return e.fileReaderDidError(t)}),this.readNextChunk()}},{key:"fileReaderDidLoad",value:function(t){if(this.md5Buffer.append(t.target.result),!this.readNextChunk()){var e=this.md5Buffer.end(!0),r=btoa(e);this.callback(null,r)}}},{key:"fileReaderDidError",value:function(t){this.callback("Error reading "+this.file.name)}},{key:"readNextChunk",value:function(){if(this.chunkIndex<this.chunkCount){var t=this.chunkIndex*this.chunkSize,e=Math.min(t+this.chunkSize,this.file.size),r=o.call(this.file,t,e);return this.fileReader.readAsArrayBuffer(r),this.chunkIndex++,!0}return!1}}]),t}()},function(t,e,r){!function(e){t.exports=e()}(function(t){"use strict";function e(t,e){var r=t[0],n=t[1],i=t[2],a=t[3];r+=(n&i|~n&a)+e[0]-680876936|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n<i;n+=1)a[n>>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64<f?t.subarray(r-64):new Uint8Array(0),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],r=0;r<i;r+=1)a[r>>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e<t.length;e+=1)t[e]=u(t[e]);return t.join("")}function s(t){return/[\u0080-\uFFFF]/.test(t)&&(t=unescape(encodeURIComponent(t))),t}function f(t,e){var r,n=t.length,i=new ArrayBuffer(n),a=new Uint8Array(i);for(r=0;r<n;r+=1)a[r]=t.charCodeAt(r);return e?a:i}function c(t){return String.fromCharCode.apply(null,new Uint8Array(t))}function h(t,e,r){var n=new Uint8Array(t.byteLength+e.byteLength);return n.set(new Uint8Array(t)),n.set(new Uint8Array(e),t.byteLength),r?n:n.buffer}function l(t){var e,r=[],n=t.length;for(e=0;e<n-1;e+=2)r.push(parseInt(t.substr(e,2),16));return String.fromCharCode.apply(String,r)}function d(){this.reset()}var p=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];return"5d41402abc4b2a76b9719d911017c592"!==o(i("hello"))&&function(t,e){var r=(65535&t)+(65535&e);return(t>>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<i;e+=1)a[e>>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64<a?new Uint8Array(i.buffer.slice(r-64)):new Uint8Array(0),this},d.ArrayBuffer.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<i;e+=1)a[e>>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),u=function(){function t(e,r,a){var u=this;n(this,t),this.file=e,this.attributes={filename:e.name,content_type:e.type,byte_size:e.size,checksum:r},this.xhr=new XMLHttpRequest,this.xhr.open("POST",a,!0),this.xhr.responseType="json",this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.setRequestHeader("Accept","application/json"),this.xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this.xhr.setRequestHeader("X-CSRF-Token",Object(i.d)("csrf-token")),this.xhr.addEventListener("load",function(t){return u.requestDidLoad(t)}),this.xhr.addEventListener("error",function(t){return u.requestDidError(t)})}return a(t,[{key:"create",value:function(t){this.callback=t,this.xhr.send(JSON.stringify({blob:this.attributes}))}},{key:"requestDidLoad",value:function(t){var e=this.xhr,r=e.status,n=e.response;if(r>=200&&r<300){var i=n.direct_upload;delete n.direct_upload,this.attributes=n,this.directUploadData=i,this.callback(null,this.toJSON())}else this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.xhr.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),a=function(){function t(e){var r=this;n(this,t),this.blob=e,this.file=e.file;var i=e.directUploadData,a=i.url,u=i.headers;this.xhr=new XMLHttpRequest,this.xhr.open("PUT",a,!0),this.xhr.responseType="text";for(var o in u)this.xhr.setRequestHeader(o,u[o]);this.xhr.addEventListener("load",function(t){return r.requestDidLoad(t)}),this.xhr.addEventListener("error",function(t){return r.requestDidError(t)})}return i(t,[{key:"create",value:function(t){this.callback=t,this.xhr.send(this.file)}},{key:"requestDidLoad",value:function(t){var e=this.xhr,r=e.status,n=e.response;r>=200&&r<300?this.callback(null,n):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])}); \ No newline at end of file
diff --git a/activestorage/app/controllers/active_storage/blobs_controller.rb b/activestorage/app/controllers/active_storage/blobs_controller.rb
index 00aa8567c8..a17e3852f9 100644
--- a/activestorage/app/controllers/active_storage/blobs_controller.rb
+++ b/activestorage/app/controllers/active_storage/blobs_controller.rb
@@ -6,20 +6,11 @@
# authenticated redirection controller.
class ActiveStorage::BlobsController < ActionController::Base
def show
- if blob = find_signed_blob
- expires_in 5.minutes # service_url defaults to 5 minutes
- redirect_to blob.service_url(disposition: disposition_param)
+ if blob = ActiveStorage::Blob.find_signed(params[:signed_id])
+ expires_in ActiveStorage::Blob.service.url_expires_in
+ redirect_to blob.service_url(disposition: params[:disposition])
else
head :not_found
end
end
-
- private
- def find_signed_blob
- ActiveStorage::Blob.find_signed(params[:signed_id])
- end
-
- def disposition_param
- params[:disposition].presence_in(%w( inline attachment )) || "inline"
- end
end
diff --git a/activestorage/app/controllers/active_storage/disk_controller.rb b/activestorage/app/controllers/active_storage/disk_controller.rb
index 41e6d61bff..a4fd427cb2 100644
--- a/activestorage/app/controllers/active_storage/disk_controller.rb
+++ b/activestorage/app/controllers/active_storage/disk_controller.rb
@@ -8,7 +8,7 @@ class ActiveStorage::DiskController < ActionController::Base
def show
if key = decode_verified_key
send_data disk_service.download(key),
- disposition: disposition_param, content_type: params[:content_type]
+ disposition: params[:disposition], content_type: params[:content_type]
else
head :not_found
end
@@ -38,10 +38,6 @@ class ActiveStorage::DiskController < ActionController::Base
ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
end
- def disposition_param
- params[:disposition].presence || "inline"
- end
-
def decode_verified_token
ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
diff --git a/activestorage/app/controllers/active_storage/previews_controller.rb b/activestorage/app/controllers/active_storage/previews_controller.rb
new file mode 100644
index 0000000000..9e8cf27b6e
--- /dev/null
+++ b/activestorage/app/controllers/active_storage/previews_controller.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class ActiveStorage::PreviewsController < ActionController::Base
+ def show
+ if blob = ActiveStorage::Blob.find_signed(params[:signed_blob_id])
+ expires_in ActiveStorage::Blob.service.url_expires_in
+ redirect_to ActiveStorage::Preview.new(blob, params[:variation_key]).processed.service_url(disposition: params[:disposition])
+ else
+ head :not_found
+ end
+ end
+end
diff --git a/activestorage/app/controllers/active_storage/variants_controller.rb b/activestorage/app/controllers/active_storage/variants_controller.rb
index 02e3010626..dc5e78ecc0 100644
--- a/activestorage/app/controllers/active_storage/variants_controller.rb
+++ b/activestorage/app/controllers/active_storage/variants_controller.rb
@@ -6,24 +6,11 @@
# authenticated redirection controller.
class ActiveStorage::VariantsController < ActionController::Base
def show
- if blob = find_signed_blob
- expires_in 5.minutes # service_url defaults to 5 minutes
- redirect_to ActiveStorage::Variant.new(blob, decoded_variation).processed.service_url(disposition: disposition_param)
+ if blob = ActiveStorage::Blob.find_signed(params[:signed_blob_id])
+ expires_in ActiveStorage::Blob.service.url_expires_in
+ redirect_to ActiveStorage::Variant.new(blob, params[:variation_key]).processed.service_url(disposition: params[:disposition])
else
head :not_found
end
end
-
- private
- def find_signed_blob
- ActiveStorage::Blob.find_signed(params[:signed_blob_id])
- end
-
- def decoded_variation
- ActiveStorage::Variation.decode(params[:variation_key])
- end
-
- def disposition_param
- params[:disposition].presence_in(%w( inline attachment )) || "inline"
- end
end
diff --git a/activestorage/app/javascript/activestorage/blob_upload.js b/activestorage/app/javascript/activestorage/blob_upload.js
index 99bf0c9e30..180a7415e7 100644
--- a/activestorage/app/javascript/activestorage/blob_upload.js
+++ b/activestorage/app/javascript/activestorage/blob_upload.js
@@ -7,6 +7,7 @@ export class BlobUpload {
this.xhr = new XMLHttpRequest
this.xhr.open("PUT", url, true)
+ this.xhr.responseType = "text"
for (const key in headers) {
this.xhr.setRequestHeader(key, headers[key])
}
diff --git a/activestorage/app/javascript/activestorage/ujs.js b/activestorage/app/javascript/activestorage/ujs.js
index a2ce2cfc58..1dda02936f 100644
--- a/activestorage/app/javascript/activestorage/ujs.js
+++ b/activestorage/app/javascript/activestorage/ujs.js
@@ -53,6 +53,7 @@ function submitForm(form) {
if (button) {
const { disabled } = button
button.disabled = false
+ button.focus()
button.click()
button.disabled = disabled
} else {
diff --git a/activestorage/app/jobs/active_storage/analyze_job.rb b/activestorage/app/jobs/active_storage/analyze_job.rb
new file mode 100644
index 0000000000..a11a73d030
--- /dev/null
+++ b/activestorage/app/jobs/active_storage/analyze_job.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# Provides asynchronous analysis of ActiveStorage::Blob records via ActiveStorage::Blob#analyze_later.
+class ActiveStorage::AnalyzeJob < ActiveJob::Base
+ def perform(blob)
+ blob.analyze
+ end
+end
diff --git a/activestorage/app/jobs/active_storage/purge_job.rb b/activestorage/app/jobs/active_storage/purge_job.rb
index 990ab27c9f..188840f702 100644
--- a/activestorage/app/jobs/active_storage/purge_job.rb
+++ b/activestorage/app/jobs/active_storage/purge_job.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# Provides delayed purging of ActiveStorage::Blob records via ActiveStorage::Blob#purge_later.
+# Provides asynchronous purging of ActiveStorage::Blob records via ActiveStorage::Blob#purge_later.
class ActiveStorage::PurgeJob < ActiveJob::Base
# FIXME: Limit this to a custom ActiveStorage error
retry_on StandardError
diff --git a/activestorage/app/models/active_storage/attachment.rb b/activestorage/app/models/active_storage/attachment.rb
index 29226e8ee9..9f61a5dbf3 100644
--- a/activestorage/app/models/active_storage/attachment.rb
+++ b/activestorage/app/models/active_storage/attachment.rb
@@ -14,6 +14,8 @@ class ActiveStorage::Attachment < ActiveRecord::Base
delegate_missing_to :blob
+ after_create_commit :analyze_blob_later
+
# Synchronously purges the blob (deletes it from the configured service) and destroys the attachment.
def purge
blob.purge
@@ -25,4 +27,9 @@ class ActiveStorage::Attachment < ActiveRecord::Base
blob.purge_later
destroy
end
+
+ private
+ def analyze_blob_later
+ blob.analyze_later unless blob.analyzed?
+ end
end
diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index e6cf08ce83..99823e14c6 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_storage/analyzer/null_analyzer"
+
# A blob is a record that contains the metadata about a file and a key for where that file resides on the service.
# Blobs can be created in two ways:
#
@@ -14,13 +16,20 @@
# update a blob's metadata on a subsequent pass, but you should not update the key or change the uploaded file.
# If you need to create a derivative or otherwise change the blob, simply create a new blob and purge the old one.
class ActiveStorage::Blob < ActiveRecord::Base
+ class UnpreviewableError < StandardError; end
+ class UnrepresentableError < StandardError; end
+
self.table_name = "active_storage_blobs"
has_secure_token :key
- store :metadata, coder: JSON
+ store :metadata, accessors: [ :analyzed ], coder: JSON
class_attribute :service
+ has_many :attachments
+
+ has_one_attached :preview_image
+
class << self
# You can used the signed ID of a blob to refer to it on the client side without fear of tampering.
# This is particularly helpful for direct uploads where the client-side needs to refer to the blob
@@ -101,24 +110,76 @@ class ActiveStorage::Blob < ActiveRecord::Base
content_type.start_with?("text")
end
- # Returns an ActiveStorage::Variant instance with the set of +transformations+
- # passed in. This is only relevant for image files, and it allows any image to
- # be transformed for size, colors, and the like. Example:
+ # Returns an ActiveStorage::Variant instance with the set of +transformations+ provided. This is only relevant for image
+ # files, and it allows any image to be transformed for size, colors, and the like. Example:
#
# avatar.variant(resize: "100x100").processed.service_url
#
- # This will create and process a variant of the avatar blob that's constrained to a height and width of 100.
+ # This will create and process a variant of the avatar blob that's constrained to a height and width of 100px.
# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
#
# Frequently, though, you don't actually want to transform the variant right away. But rather simply refer to a
# specific variant that can be created by a controller on-demand. Like so:
#
- # <%= image_tag url_for(Current.user.avatar.variant(resize: "100x100")) %>
+ # <%= image_tag Current.user.avatar.variant(resize: "100x100") %>
#
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::VariantsController
# can then produce on-demand.
def variant(transformations)
- ActiveStorage::Variant.new(self, ActiveStorage::Variation.new(transformations))
+ ActiveStorage::Variant.new(self, ActiveStorage::Variation.wrap(transformations))
+ end
+
+
+ # Returns an ActiveStorage::Preview instance with the set of +transformations+ provided. A preview is an image generated
+ # from a non-image blob. Active Storage comes with built-in previewers for videos and PDF documents. The video previewer
+ # extracts the first frame from a video and the PDF previewer extracts the first page from a PDF document.
+ #
+ # blob.preview(resize: "100x100").processed.service_url
+ #
+ # Avoid processing previews synchronously in views. Instead, link to a controller action that processes them on demand.
+ # Active Storage provides one, but you may want to create your own (for example, if you need authentication). Here’s
+ # how to use the built-in version:
+ #
+ # <%= image_tag video.preview(resize: "100x100") %>
+ #
+ # This method raises ActiveStorage::Blob::UnpreviewableError if no previewer accepts the receiving blob. To determine
+ # whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?.
+ def preview(transformations)
+ if previewable?
+ ActiveStorage::Preview.new(self, ActiveStorage::Variation.wrap(transformations))
+ else
+ raise UnpreviewableError
+ end
+ end
+
+ # Returns true if any registered previewer accepts the blob. By default, this will return true for videos and PDF documents.
+ def previewable?
+ ActiveStorage.previewers.any? { |klass| klass.accept?(self) }
+ end
+
+
+ # Returns an ActiveStorage::Preview instance for a previewable blob or an ActiveStorage::Variant instance for an image blob.
+ #
+ # blob.representation(resize: "100x100").processed.service_url
+ #
+ # Raises ActiveStorage::Blob::UnrepresentableError if the receiving blob is neither an image nor previewable. Call
+ # ActiveStorage::Blob#representable? to determine whether a blob is representable.
+ #
+ # See ActiveStorage::Blob#preview and ActiveStorage::Blob#variant for more information.
+ def representation(transformations)
+ case
+ when previewable?
+ preview transformations
+ when image?
+ variant transformations
+ else
+ raise UnrepresentableError
+ end
+ end
+
+ # Returns true if the blob is an image or is previewable.
+ def representable?
+ image? || previewable?
end
@@ -126,13 +187,13 @@ class ActiveStorage::Blob < ActiveRecord::Base
# with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL.
# Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And
# it allows permanent URLs that redirect to the +service_url+ to be cached in the view.
- def service_url(expires_in: 5.minutes, disposition: :inline)
- service.url key, expires_in: expires_in, disposition: "#{disposition}; #{filename.parameters}", filename: filename, content_type: content_type
+ def service_url(expires_in: service.url_expires_in, disposition: "inline")
+ service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
end
# Returns a URL that can be used to directly upload a file for this blob on the service. This URL is intended to be
# short-lived for security and only generated on-demand by the client-side JavaScript responsible for doing the uploading.
- def service_url_for_direct_upload(expires_in: 5.minutes)
+ def service_url_for_direct_upload(expires_in: service.url_expires_in)
service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size, checksum: checksum
end
@@ -165,6 +226,46 @@ class ActiveStorage::Blob < ActiveRecord::Base
end
+ # Extracts and stores metadata from the file associated with this blob using a relevant analyzer. Active Storage comes
+ # with built-in analyzers for images and videos. See ActiveStorage::Analyzer::ImageAnalyzer and
+ # ActiveStorage::Analyzer::VideoAnalyzer for information about the specific attributes they extract and the third-party
+ # libraries they require.
+ #
+ # To choose the analyzer for a blob, Active Storage calls +accept?+ on each registered analyzer in order. It uses the
+ # first analyzer for which +accept?+ returns true when given the blob. If no registered analyzer accepts the blob, no
+ # metadata is extracted from it.
+ #
+ # In a Rails application, add or remove analyzers by manipulating +Rails.application.config.active_storage.analyzers+
+ # in an initializer:
+ #
+ # # Add a custom analyzer for Microsoft Office documents:
+ # Rails.application.config.active_storage.analyzers.append DOCXAnalyzer
+ #
+ # # Remove the built-in video analyzer:
+ # Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer
+ #
+ # Outside of a Rails application, manipulate +ActiveStorage.analyzers+ instead.
+ #
+ # You won't ordinarily need to call this method from a Rails application. New blobs are automatically and asynchronously
+ # analyzed via #analyze_later when they're attached for the first time.
+ def analyze
+ update! metadata: extract_metadata_via_analyzer
+ end
+
+ # Enqueues an ActiveStorage::AnalyzeJob which calls #analyze.
+ #
+ # This method is automatically called for a blob when it's attached for the first time. You can call it to analyze a blob
+ # again (e.g. if you add a new analyzer or modify an existing one).
+ def analyze_later
+ ActiveStorage::AnalyzeJob.perform_later(self)
+ end
+
+ # Returns true if the blob has been analyzed.
+ def analyzed?
+ analyzed
+ end
+
+
# Deletes the file on the service that's associated with this blob. This should only be done if the blob is going to be
# deleted as well or you will essentially have a dead reference. It's recommended to use the +#purge+ and +#purge_later+
# methods in most circumstances.
@@ -196,4 +297,17 @@ class ActiveStorage::Blob < ActiveRecord::Base
io.rewind
end.base64digest
end
+
+
+ def extract_metadata_via_analyzer
+ analyzer.metadata.merge(analyzed: true)
+ end
+
+ def analyzer
+ analyzer_class.new(self)
+ end
+
+ def analyzer_class
+ ActiveStorage.analyzers.detect { |klass| klass.accept?(self) } || ActiveStorage::Analyzer::NullAnalyzer
+ end
end
diff --git a/activestorage/app/models/active_storage/filename.rb b/activestorage/app/models/active_storage/filename.rb
index dead6b6d33..79d55dc889 100644
--- a/activestorage/app/models/active_storage/filename.rb
+++ b/activestorage/app/models/active_storage/filename.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-# Encapsulates a string representing a filename to provide convenience access to parts of it and a sanitized version.
-# This is what's returned by ActiveStorage::Blob#filename. A Filename instance is comparable so it can be used for sorting.
+# Encapsulates a string representing a filename to provide convenient access to parts of it and sanitization.
+# A Filename instance is returned by ActiveStorage::Blob#filename, and is comparable so it can be used for sorting.
class ActiveStorage::Filename
include Comparable
@@ -9,23 +9,31 @@ class ActiveStorage::Filename
@filename = filename
end
- # Returns the basename of the filename.
+ # Returns the part of the filename preceding any extension.
#
# ActiveStorage::Filename.new("racecar.jpg").base # => "racecar"
+ # ActiveStorage::Filename.new("racecar").base # => "racecar"
+ # ActiveStorage::Filename.new(".gitignore").base # => ".gitignore"
def base
File.basename @filename, extension_with_delimiter
end
- # Returns the extension with delimiter of the filename.
+ # Returns the extension of the filename (i.e. the substring following the last dot, excluding a dot at the
+ # beginning) with the dot that precedes it. If the filename has no extension, an empty string is returned.
#
# ActiveStorage::Filename.new("racecar.jpg").extension_with_delimiter # => ".jpg"
+ # ActiveStorage::Filename.new("racecar").extension_with_delimiter # => ""
+ # ActiveStorage::Filename.new(".gitignore").extension_with_delimiter # => ""
def extension_with_delimiter
File.extname @filename
end
- # Returns the extension without delimiter of the filename.
+ # Returns the extension of the filename (i.e. the substring following the last dot, excluding a dot at
+ # the beginning). If the filename has no extension, an empty string is returned.
#
# ActiveStorage::Filename.new("racecar.jpg").extension_without_delimiter # => "jpg"
+ # ActiveStorage::Filename.new("racecar").extension_without_delimiter # => ""
+ # ActiveStorage::Filename.new(".gitignore").extension_without_delimiter # => ""
def extension_without_delimiter
extension_with_delimiter.from(1).to_s
end
@@ -37,7 +45,7 @@ class ActiveStorage::Filename
# ActiveStorage::Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg"
# ActiveStorage::Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg"
#
- # ...and any other character unsafe for URLs or storage is converted or stripped.
+ # Characters considered unsafe for storage (e.g. \, $, and the RTL override character) are replaced with a dash.
def sanitized
@filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
end
diff --git a/activestorage/app/models/active_storage/preview.rb b/activestorage/app/models/active_storage/preview.rb
new file mode 100644
index 0000000000..be5053edae
--- /dev/null
+++ b/activestorage/app/models/active_storage/preview.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+# Some non-image blobs can be previewed: that is, they can be presented as images. A video blob can be previewed by
+# extracting its first frame, and a PDF blob can be previewed by extracting its first page.
+#
+# A previewer extracts a preview image from a blob. Active Storage provides previewers for videos and PDFs:
+# ActiveStorage::Previewer::VideoPreviewer and ActiveStorage::Previewer::PDFPreviewer. Build custom previewers by
+# subclassing ActiveStorage::Previewer and implementing the requisite methods. Consult the ActiveStorage::Previewer
+# documentation for more details on what's required of previewers.
+#
+# To choose the previewer for a blob, Active Storage calls +accept?+ on each registered previewer in order. It uses the
+# first previewer for which +accept?+ returns true when given the blob. In a Rails application, add or remove previewers
+# by manipulating +Rails.application.config.active_storage.previewers+ in an initializer:
+#
+# Rails.application.config.active_storage.previewers
+# # => [ ActiveStorage::Previewer::PDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
+#
+# # Add a custom previewer for Microsoft Office documents:
+# Rails.application.config.active_storage.previewers << DOCXPreviewer
+# # => [ ActiveStorage::Previewer::PDFPreviewer, ActiveStorage::Previewer::VideoPreviewer, DOCXPreviewer ]
+#
+# Outside of a Rails application, modify +ActiveStorage.previewers+ instead.
+#
+# The built-in previewers rely on third-party system libraries:
+#
+# * {ffmpeg}[https://www.ffmpeg.org]
+# * {mupdf}[https://mupdf.com]
+#
+# These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you
+# install and use third-party software, make sure you understand the licensing implications of doing so.
+class ActiveStorage::Preview
+ class UnprocessedError < StandardError; end
+
+ attr_reader :blob, :variation
+
+ def initialize(blob, variation_or_variation_key)
+ @blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key)
+ end
+
+ # Processes the preview if it has not been processed yet. Returns the receiving Preview instance for convenience:
+ #
+ # blob.preview(resize: "100x100").processed.service_url
+ #
+ # Processing a preview generates an image from its blob and attaches the preview image to the blob. Because the preview
+ # image is stored with the blob, it is only generated once.
+ def processed
+ process unless processed?
+ self
+ end
+
+ # Returns the blob's attached preview image.
+ def image
+ blob.preview_image
+ end
+
+ # Returns the URL of the preview's variant on the service. Raises ActiveStorage::Preview::UnprocessedError if the
+ # preview has not been processed yet.
+ #
+ # This method synchronously processes a variant of the preview image, so do not call it in views. Instead, generate
+ # a stable URL that redirects to the short-lived URL returned by this method.
+ def service_url(**options)
+ if processed?
+ variant.service_url(options)
+ else
+ raise UnprocessedError
+ end
+ end
+
+ private
+ def processed?
+ image.attached?
+ end
+
+ def process
+ previewer.preview { |attachable| image.attach(attachable) }
+ end
+
+ def variant
+ ActiveStorage::Variant.new(image, variation).processed
+ end
+
+
+ def previewer
+ previewer_class.new(blob)
+ end
+
+ def previewer_class
+ ActiveStorage.previewers.detect { |klass| klass.accept?(blob) }
+ end
+end
diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb
index 02bf32b352..fa5aa69bd3 100644
--- a/activestorage/app/models/active_storage/variant.rb
+++ b/activestorage/app/models/active_storage/variant.rb
@@ -38,8 +38,8 @@ class ActiveStorage::Variant
attr_reader :blob, :variation
delegate :service, to: :blob
- def initialize(blob, variation)
- @blob, @variation = blob, variation
+ def initialize(blob, variation_or_variation_key)
+ @blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key)
end
# Returns the variant instance itself after it's been processed or an existing processing has been found on the service.
@@ -50,7 +50,7 @@ class ActiveStorage::Variant
# Returns a combination key of the blob and the variation that together identifies a specific variant.
def key
- "variants/#{blob.key}/#{variation.key}"
+ "variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}"
end
# Returns the URL of the variant on the service. This URL is intended to be short-lived for security and not used directly
@@ -61,10 +61,14 @@ class ActiveStorage::Variant
# Use <tt>url_for(variant)</tt> (or the implied form, like +link_to variant+ or +redirect_to variant+) to get the stable URL
# for a variant that points to the ActiveStorage::VariantsController, which in turn will use this +service_call+ method
# for its redirection.
- def service_url(expires_in: 5.minutes, disposition: :inline)
+ def service_url(expires_in: service.url_expires_in, disposition: :inline)
service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename, content_type: blob.content_type
end
+ # Returns the receiving variant. Allows ActiveStorage::Variant and ActiveStorage::Preview instances to be used interchangeably.
+ def image
+ self
+ end
private
def processed?
diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb
index bf269e2a8f..13bad87cac 100644
--- a/activestorage/app/models/active_storage/variation.rb
+++ b/activestorage/app/models/active_storage/variation.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/core_ext/object/inclusion"
-
# A set of transformations that can be applied to a blob to create a variant. This class is exposed via
# the ActiveStorage::Blob#variant method and should rarely be used directly.
#
@@ -15,7 +13,21 @@ class ActiveStorage::Variation
attr_reader :transformations
class << self
- # Returns a variation instance with the transformations that were encoded by +encode+.
+ # Returns a Variation instance based on the given variator. If the variator is a Variation, it is
+ # returned unmodified. If it is a String, it is passed to ActiveStorage::Variation.decode. Otherwise,
+ # it is assumed to be a transformations Hash and is passed directly to the constructor.
+ def wrap(variator)
+ case variator
+ when self
+ variator
+ when String
+ decode variator
+ else
+ new variator
+ end
+ end
+
+ # Returns a Variation instance with the transformations that were encoded by +encode+.
def decode(key)
new ActiveStorage.verifier.verify(key, purpose: :variation)
end
diff --git a/activestorage/config/routes.rb b/activestorage/config/routes.rb
index 168788475c..c659e079fd 100644
--- a/activestorage/config/routes.rb
+++ b/activestorage/config/routes.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
Rails.application.routes.draw do
- get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
+ get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob, internal: true
direct :rails_blob do |blob|
route_for(:rails_service_blob, blob.signed_id, blob.filename)
@@ -11,7 +11,7 @@ Rails.application.routes.draw do
resolve("ActiveStorage::Attachment") { |attachment| route_for(:rails_blob, attachment.blob) }
- get "/rails/active_storage/variants/:signed_blob_id/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation
+ get "/rails/active_storage/variants/:signed_blob_id/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation, internal: true
direct :rails_variant do |variant|
signed_blob_id = variant.blob.signed_id
@@ -24,7 +24,20 @@ Rails.application.routes.draw do
resolve("ActiveStorage::Variant") { |variant| route_for(:rails_variant, variant) }
- get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service
- put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service
- post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads
+ get "/rails/active_storage/previews/:signed_blob_id/:variation_key/*filename" => "active_storage/previews#show", as: :rails_blob_preview, internal: true
+
+ direct :rails_preview do |preview|
+ signed_blob_id = preview.blob.signed_id
+ variation_key = preview.variation.key
+ filename = preview.blob.filename
+
+ route_for(:rails_blob_preview, signed_blob_id, variation_key, filename)
+ end
+
+ resolve("ActiveStorage::Preview") { |preview| route_for(:rails_preview, preview) }
+
+
+ get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service, internal: true
+ put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service, internal: true
+ post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads, internal: true
end
diff --git a/activestorage/lib/active_storage.rb b/activestorage/lib/active_storage.rb
index ccc1d4a163..cfdb2a8acd 100644
--- a/activestorage/lib/active_storage.rb
+++ b/activestorage/lib/active_storage.rb
@@ -26,13 +26,18 @@
require "active_record"
require "active_support"
require "active_support/rails"
-require_relative "active_storage/version"
+require "active_storage/version"
module ActiveStorage
extend ActiveSupport::Autoload
autoload :Attached
autoload :Service
+ autoload :Previewer
+ autoload :Analyzer
+ mattr_accessor :logger
mattr_accessor :verifier
+ mattr_accessor :previewers, default: []
+ mattr_accessor :analyzers, default: []
end
diff --git a/activestorage/lib/active_storage/analyzer.rb b/activestorage/lib/active_storage/analyzer.rb
new file mode 100644
index 0000000000..837785a12b
--- /dev/null
+++ b/activestorage/lib/active_storage/analyzer.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require "active_storage/downloading"
+
+module ActiveStorage
+ # This is an abstract base class for analyzers, which extract metadata from blobs. See
+ # ActiveStorage::Analyzer::ImageAnalyzer for an example of a concrete subclass.
+ class Analyzer
+ include Downloading
+
+ attr_reader :blob
+
+ # Implement this method in a concrete subclass. Have it return true when given a blob from which
+ # the analyzer can extract metadata.
+ def self.accept?(blob)
+ false
+ end
+
+ def initialize(blob)
+ @blob = blob
+ end
+
+ # Override this method in a concrete subclass. Have it return a Hash of metadata.
+ def metadata
+ raise NotImplementedError
+ end
+
+ private
+ def logger
+ ActiveStorage.logger
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/analyzer/image_analyzer.rb b/activestorage/lib/active_storage/analyzer/image_analyzer.rb
new file mode 100644
index 0000000000..25e0251e6e
--- /dev/null
+++ b/activestorage/lib/active_storage/analyzer/image_analyzer.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ # Extracts width and height in pixels from an image blob.
+ #
+ # Example:
+ #
+ # ActiveStorage::Analyzer::ImageAnalyzer.new(blob).metadata
+ # # => { width: 4104, height: 2736 }
+ #
+ # This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem. MiniMagick requires
+ # the {ImageMagick}[http://www.imagemagick.org] system library. These libraries are not provided by Rails; you must
+ # install them yourself to use this analyzer.
+ class Analyzer::ImageAnalyzer < Analyzer
+ def self.accept?(blob)
+ blob.image?
+ end
+
+ def metadata
+ read_image do |image|
+ { width: image.width, height: image.height }
+ end
+ rescue LoadError
+ logger.info "Skipping image analysis because the mini_magick gem isn't installed"
+ {}
+ end
+
+ private
+ def read_image
+ download_blob_to_tempfile do |file|
+ require "mini_magick"
+ yield MiniMagick::Image.new(file.path)
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/analyzer/null_analyzer.rb b/activestorage/lib/active_storage/analyzer/null_analyzer.rb
new file mode 100644
index 0000000000..8ff7ce48e5
--- /dev/null
+++ b/activestorage/lib/active_storage/analyzer/null_analyzer.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Analyzer::NullAnalyzer < Analyzer # :nodoc:
+ def self.accept?(blob)
+ true
+ end
+
+ def metadata
+ {}
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/analyzer/video_analyzer.rb b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
new file mode 100644
index 0000000000..408b5e58e9
--- /dev/null
+++ b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require "active_support/core_ext/hash/compact"
+
+module ActiveStorage
+ # Extracts the following from a video blob:
+ #
+ # * Width (pixels)
+ # * Height (pixels)
+ # * Duration (seconds)
+ # * Angle (degrees)
+ # * Aspect ratio
+ #
+ # Example:
+ #
+ # ActiveStorage::VideoAnalyzer.new(blob).metadata
+ # # => { width: 640, height: 480, duration: 5.0, angle: 0, aspect_ratio: [4, 3] }
+ #
+ # This analyzer requires the {ffmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails. You must
+ # install ffmpeg yourself to use this analyzer.
+ class Analyzer::VideoAnalyzer < Analyzer
+ def self.accept?(blob)
+ blob.video?
+ end
+
+ def metadata
+ { width: width, height: height, duration: duration, angle: angle, aspect_ratio: aspect_ratio }.compact
+ end
+
+ private
+ def width
+ Integer(video_stream["width"]) if video_stream["width"]
+ end
+
+ def height
+ Integer(video_stream["height"]) if video_stream["height"]
+ end
+
+ def duration
+ Float(video_stream["duration"]) if video_stream["duration"]
+ end
+
+ def angle
+ Integer(tags["rotate"]) if tags["rotate"]
+ end
+
+ def aspect_ratio
+ if descriptor = video_stream["display_aspect_ratio"]
+ descriptor.split(":", 2).collect(&:to_i)
+ end
+ end
+
+
+ def tags
+ @tags ||= video_stream["tags"] || {}
+ end
+
+ def video_stream
+ @video_stream ||= streams.detect { |stream| stream["codec_type"] == "video" } || {}
+ end
+
+ def streams
+ probe["streams"] || []
+ end
+
+ def probe
+ download_blob_to_tempfile { |file| probe_from(file) }
+ end
+
+ def probe_from(file)
+ IO.popen([ "ffprobe", "-print_format", "json", "-show_streams", "-v", "error", file.path ]) do |output|
+ JSON.parse(output.read)
+ end
+ rescue Errno::ENOENT
+ logger.info "Skipping video analysis because ffmpeg isn't installed"
+ {}
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/macros.rb b/activestorage/lib/active_storage/attached/macros.rb
index 35a081adc4..f0256718ac 100644
--- a/activestorage/lib/active_storage/attached/macros.rb
+++ b/activestorage/lib/active_storage/attached/macros.rb
@@ -12,6 +12,10 @@ module ActiveStorage
# There is no column defined on the model side, Active Storage takes
# care of the mapping between your records and the attachment.
#
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
+ #
+ # User.with_attached_avatar
+ #
# Under the covers, this relationship is implemented as a +has_one+ association to a
# ActiveStorage::Attachment record and a +has_one-through+ association to a
# ActiveStorage::Blob record. These associations are available as +avatar_attachment+
@@ -33,6 +37,8 @@ module ActiveStorage
has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record
has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
+ scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
+
if dependent == :purge_later
before_destroy { public_send(name).purge_later }
end
diff --git a/activestorage/lib/active_storage/attached/many.rb b/activestorage/lib/active_storage/attached/many.rb
index 59b7d7d559..1e0657c33c 100644
--- a/activestorage/lib/active_storage/attached/many.rb
+++ b/activestorage/lib/active_storage/attached/many.rb
@@ -17,7 +17,7 @@ module ActiveStorage
#
# document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
# document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
- # document.images.attach(io: File.open("~/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
+ # document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
# document.images.attach([ first_blob, second_blob ])
def attach(*attachables)
attachables.flatten.collect do |attachable|
diff --git a/activestorage/lib/active_storage/attached/one.rb b/activestorage/lib/active_storage/attached/one.rb
index ac90f32d95..dc19512484 100644
--- a/activestorage/lib/active_storage/attached/one.rb
+++ b/activestorage/lib/active_storage/attached/one.rb
@@ -18,7 +18,7 @@ module ActiveStorage
#
# person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
# person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
- # person.avatar.attach(io: File.open("~/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
+ # person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
# person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
def attach(attachable)
if attached? && dependent == :purge_later
@@ -59,12 +59,16 @@ module ActiveStorage
def replace(attachable)
blob.tap do
transaction do
- destroy
+ destroy_attachment
write_attachment create_attachment_from(attachable)
end
end.purge_later
end
+ def destroy_attachment
+ attachment.destroy
+ end
+
def create_attachment_from(attachable)
ActiveStorage::Attachment.create!(record: record, name: name, blob: create_blob_from(attachable))
end
diff --git a/activestorage/lib/active_storage/downloading.rb b/activestorage/lib/active_storage/downloading.rb
new file mode 100644
index 0000000000..ceb7cce0c7
--- /dev/null
+++ b/activestorage/lib/active_storage/downloading.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ module Downloading
+ private
+ # Opens a new tempfile and copies blob data into it. Yields the tempfile.
+ def download_blob_to_tempfile # :doc:
+ Tempfile.open("ActiveStorage") do |file|
+ download_blob_to file
+ yield file
+ end
+ end
+
+ # Efficiently downloads blob data into the given file.
+ def download_blob_to(file) # :doc:
+ file.binmode
+ blob.download { |chunk| file.write(chunk) }
+ file.rewind
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb
index a5562b32d3..a01a14cd83 100644
--- a/activestorage/lib/active_storage/engine.rb
+++ b/activestorage/lib/active_storage/engine.rb
@@ -3,19 +3,25 @@
require "rails"
require "active_storage"
+require "active_storage/previewer/pdf_previewer"
+require "active_storage/previewer/video_previewer"
+
+require "active_storage/analyzer/image_analyzer"
+require "active_storage/analyzer/video_analyzer"
+
module ActiveStorage
class Engine < Rails::Engine # :nodoc:
isolate_namespace ActiveStorage
config.active_storage = ActiveSupport::OrderedOptions.new
+ config.active_storage.previewers = [ ActiveStorage::Previewer::PDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ]
+ config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
config.eager_load_namespaces << ActiveStorage
initializer "active_storage.logger" do
- require "active_storage/service"
-
config.after_initialize do |app|
- ActiveStorage::Service.logger = app.config.active_storage.logger || Rails.logger
+ ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
end
end
@@ -29,9 +35,7 @@ module ActiveStorage
initializer "active_storage.verifier" do
config.after_initialize do |app|
- if app.secrets.secret_key_base.present?
- ActiveStorage.verifier = app.message_verifier("ActiveStorage")
- end
+ ActiveStorage.verifier = app.message_verifier("ActiveStorage")
end
end
@@ -61,5 +65,17 @@ module ActiveStorage
end
end
end
+
+ initializer "active_storage.previewers" do
+ config.after_initialize do |app|
+ ActiveStorage.previewers = app.config.active_storage.previewers || []
+ end
+ end
+
+ initializer "active_storage.analyzers" do
+ config.after_initialize do |app|
+ ActiveStorage.analyzers = app.config.active_storage.analyzers || []
+ end
+ end
end
end
diff --git a/activestorage/lib/active_storage/log_subscriber.rb b/activestorage/lib/active_storage/log_subscriber.rb
index 0d00a75c0e..5cbf4bd1a5 100644
--- a/activestorage/lib/active_storage/log_subscriber.rb
+++ b/activestorage/lib/active_storage/log_subscriber.rb
@@ -27,7 +27,7 @@ module ActiveStorage
end
def logger
- ActiveStorage::Service.logger
+ ActiveStorage.logger
end
private
diff --git a/activestorage/lib/active_storage/previewer.rb b/activestorage/lib/active_storage/previewer.rb
new file mode 100644
index 0000000000..460f6d5678
--- /dev/null
+++ b/activestorage/lib/active_storage/previewer.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require "active_storage/downloading"
+
+module ActiveStorage
+ # This is an abstract base class for previewers, which generate images from blobs. See
+ # ActiveStorage::Previewer::PDFPreviewer and ActiveStorage::Previewer::VideoPreviewer for examples of
+ # concrete subclasses.
+ class Previewer
+ include Downloading
+
+ attr_reader :blob
+
+ # Implement this method in a concrete subclass. Have it return true when given a blob from which
+ # the previewer can generate an image.
+ def self.accept?(blob)
+ false
+ end
+
+ def initialize(blob)
+ @blob = blob
+ end
+
+ # Override this method in a concrete subclass. Have it yield an attachable preview image (i.e.
+ # anything accepted by ActiveStorage::Attached::One#attach).
+ def preview
+ raise NotImplementedError
+ end
+
+ private
+ # Executes a system command, capturing its binary output in a tempfile. Yields the tempfile.
+ #
+ # Use this method to shell out to a system library (e.g. mupdf or ffmpeg) for preview image
+ # generation. The resulting tempfile can be used as the +:io+ value in an attachable Hash:
+ #
+ # def preview
+ # download_blob_to_tempfile do |input|
+ # draw "my-drawing-command", input.path, "--format", "png", "-" do |output|
+ # yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
+ # end
+ # end
+ # end
+ def draw(*argv) # :doc:
+ Tempfile.open("ActiveStorage") do |file|
+ capture(*argv, to: file)
+ yield file
+ end
+ end
+
+ def capture(*argv, to:)
+ to.binmode
+ IO.popen(argv) { |out| IO.copy_stream(out, to) }
+ to.rewind
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/previewer/pdf_previewer.rb b/activestorage/lib/active_storage/previewer/pdf_previewer.rb
new file mode 100644
index 0000000000..a2f05c74a6
--- /dev/null
+++ b/activestorage/lib/active_storage/previewer/pdf_previewer.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Previewer::PDFPreviewer < Previewer
+ def self.accept?(blob)
+ blob.content_type == "application/pdf"
+ end
+
+ def preview
+ download_blob_to_tempfile do |input|
+ draw "mutool", "draw", "-F", "png", "-o", "-", input.path, "1" do |output|
+ yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
+ end
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/previewer/video_previewer.rb b/activestorage/lib/active_storage/previewer/video_previewer.rb
new file mode 100644
index 0000000000..49f128d142
--- /dev/null
+++ b/activestorage/lib/active_storage/previewer/video_previewer.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Previewer::VideoPreviewer < Previewer
+ def self.accept?(blob)
+ blob.video?
+ end
+
+ def preview
+ download_blob_to_tempfile do |input|
+ draw_relevant_frame_from input do |output|
+ yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
+ end
+ end
+ end
+
+ private
+ def draw_relevant_frame_from(file, &block)
+ draw "ffmpeg", "-i", file.path, "-y", "-vcodec", "png",
+ "-vf", "thumbnail", "-vframes", "1", "-f", "image2", "-", &block
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/service.rb b/activestorage/lib/active_storage/service.rb
index b80fdea1ab..aa150e4d8a 100644
--- a/activestorage/lib/active_storage/service.rb
+++ b/activestorage/lib/active_storage/service.rb
@@ -4,6 +4,7 @@ require "active_storage/log_subscriber"
module ActiveStorage
class IntegrityError < StandardError; end
+
# Abstract class serving as an interface for concrete services.
#
# The available services are:
@@ -40,7 +41,7 @@ module ActiveStorage
extend ActiveSupport::Autoload
autoload :Configurator
- class_attribute :logger
+ class_attribute :url_expires_in, default: 5.minutes
class << self
# Configure an Active Storage service by name from a set of configurations,
@@ -113,5 +114,9 @@ module ActiveStorage
# ActiveStorage::Service::DiskService => Disk
self.class.name.split("::").third.remove("Service")
end
+
+ def content_disposition_with(type: "inline", filename:)
+ (type.to_s.presence_in(%w( attachment inline )) || "inline") + "; #{filename.parameters}"
+ end
end
end
diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb
index 895cc9c2f1..27dd192ce6 100644
--- a/activestorage/lib/active_storage/service/azure_storage_service.rb
+++ b/activestorage/lib/active_storage/service/azure_storage_service.rb
@@ -66,7 +66,7 @@ module ActiveStorage
URI(base_url), false,
permissions: "r",
expiry: format_expiry(expires_in),
- content_disposition: disposition,
+ content_disposition: content_disposition_with(type: disposition, filename: filename),
content_type: content_type
).to_s
diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb
index f600753a08..52eaba4e7b 100644
--- a/activestorage/lib/active_storage/service/disk_service.rb
+++ b/activestorage/lib/active_storage/service/disk_service.rb
@@ -64,9 +64,10 @@ module ActiveStorage
if defined?(Rails.application)
Rails.application.routes.url_helpers.rails_disk_service_path \
verified_key_with_expiration,
- filename: filename, disposition: disposition, content_type: content_type
+ filename: filename, disposition: content_disposition_with(type: disposition, filename: filename), content_type: content_type
else
- "/rails/active_storage/disk/#{verified_key_with_expiration}/#{filename}?content_type=#{content_type}&disposition=#{disposition}"
+ "/rails/active_storage/disk/#{verified_key_with_expiration}/#{filename}?content_type=#{content_type}" \
+ "&disposition=#{content_disposition_with(type: disposition, filename: filename)}"
end
payload[:url] = generated_url
diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb
index b3fe592097..b4ffeeeb8a 100644
--- a/activestorage/lib/active_storage/service/gcs_service.rb
+++ b/activestorage/lib/active_storage/service/gcs_service.rb
@@ -9,8 +9,8 @@ module ActiveStorage
class Service::GCSService < Service
attr_reader :client, :bucket
- def initialize(project:, keyfile:, bucket:)
- @client = Google::Cloud::Storage.new(project: project, keyfile: keyfile)
+ def initialize(project:, keyfile:, bucket:, **options)
+ @client = Google::Cloud::Storage.new(project: project, keyfile: keyfile, **options)
@bucket = @client.bucket(bucket)
end
@@ -24,19 +24,24 @@ module ActiveStorage
end
end
- # FIXME: Add streaming when given a block
+ # FIXME: Download in chunks when given a block.
def download(key)
instrument :download, key do
io = file_for(key).download
io.rewind
- io.read
+
+ if block_given?
+ yield io.read
+ else
+ io.read
+ end
end
end
def delete(key)
instrument :delete, key do
begin
- file_for(key).try(:delete)
+ file_for(key).delete
rescue Google::Cloud::NotFoundError
# Ignore files already deleted
end
@@ -45,7 +50,7 @@ module ActiveStorage
def exist?(key)
instrument :exist, key do |payload|
- answer = file_for(key).present?
+ answer = file_for(key).exists?
payload[:exist] = answer
answer
end
@@ -54,7 +59,7 @@ module ActiveStorage
def url(key, expires_in:, filename:, content_type:, disposition:)
instrument :url, key do |payload|
generated_url = file_for(key).signed_url expires: expires_in, query: {
- "response-content-disposition" => disposition,
+ "response-content-disposition" => content_disposition_with(type: disposition, filename: filename),
"response-content-type" => content_type
}
@@ -81,7 +86,7 @@ module ActiveStorage
private
def file_for(key)
- bucket.file(key)
+ bucket.file(key, skip_lookup: true)
end
end
end
diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb
index e5d1e56e05..3e93cdd072 100644
--- a/activestorage/lib/active_storage/service/s3_service.rb
+++ b/activestorage/lib/active_storage/service/s3_service.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "aws-sdk"
+require "aws-sdk-s3"
require "active_support/core_ext/numeric/bytes"
module ActiveStorage
@@ -54,8 +54,8 @@ module ActiveStorage
def url(key, expires_in:, filename:, disposition:, content_type:)
instrument :url, key do |payload|
- generated_url = object_for(key).presigned_url :get, expires_in: expires_in,
- response_content_disposition: disposition,
+ generated_url = object_for(key).presigned_url :get, expires_in: expires_in.to_i,
+ response_content_disposition: content_disposition_with(type: disposition, filename: filename),
response_content_type: content_type
payload[:url] = generated_url
@@ -66,7 +66,7 @@ module ActiveStorage
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
instrument :url, key do |payload|
- generated_url = object_for(key).presigned_url :put, expires_in: expires_in,
+ generated_url = object_for(key).presigned_url :put, expires_in: expires_in.to_i,
content_type: content_type, content_length: content_length, content_md5: checksum
payload[:url] = generated_url
diff --git a/activestorage/package.json b/activestorage/package.json
index b481295a8d..8e6dd1c57f 100644
--- a/activestorage/package.json
+++ b/activestorage/package.json
@@ -1,6 +1,6 @@
{
"name": "activestorage",
- "version": "5.2.0.alpha",
+ "version": "5.2.0-alpha",
"description": "Attach cloud and local files in Rails applications",
"main": "app/assets/javascripts/activestorage.js",
"files": [
diff --git a/activestorage/test/analyzer/image_analyzer_test.rb b/activestorage/test/analyzer/image_analyzer_test.rb
new file mode 100644
index 0000000000..9087072215
--- /dev/null
+++ b/activestorage/test/analyzer/image_analyzer_test.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+require "active_storage/analyzer/image_analyzer"
+
+class ActiveStorage::Analyzer::ImageAnalyzerTest < ActiveSupport::TestCase
+ test "analyzing an image" do
+ blob = create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg")
+ metadata = blob.tap(&:analyze).metadata
+
+ assert_equal 4104, metadata[:width]
+ assert_equal 2736, metadata[:height]
+ end
+end
diff --git a/activestorage/test/analyzer/video_analyzer_test.rb b/activestorage/test/analyzer/video_analyzer_test.rb
new file mode 100644
index 0000000000..4a3c4a8bfc
--- /dev/null
+++ b/activestorage/test/analyzer/video_analyzer_test.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+require "active_storage/analyzer/video_analyzer"
+
+class ActiveStorage::Analyzer::VideoAnalyzerTest < ActiveSupport::TestCase
+ test "analyzing a video" do
+ blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4")
+ metadata = blob.tap(&:analyze).metadata
+
+ assert_equal 640, metadata[:width]
+ assert_equal 480, metadata[:height]
+ assert_equal [4, 3], metadata[:aspect_ratio]
+ assert_equal 5.166648, metadata[:duration]
+ assert_not_includes metadata, :angle
+ end
+
+ test "analyzing a rotated video" do
+ blob = create_file_blob(filename: "rotated_video.mp4", content_type: "video/mp4")
+ metadata = blob.tap(&:analyze).metadata
+
+ assert_equal 640, metadata[:width]
+ assert_equal 480, metadata[:height]
+ assert_equal [4, 3], metadata[:aspect_ratio]
+ assert_equal 5.227975, metadata[:duration]
+ assert_equal 90, metadata[:angle]
+ end
+
+ test "analyzing a video without a video stream" do
+ blob = create_file_blob(filename: "video_without_video_stream.mp4", content_type: "video/mp4")
+ assert_equal({ "analyzed" => true }, blob.tap(&:analyze).metadata)
+ end
+end
diff --git a/activestorage/test/controllers/blobs_controller_test.rb b/activestorage/test/controllers/blobs_controller_test.rb
index c37b9c8a10..97177e64c2 100644
--- a/activestorage/test/controllers/blobs_controller_test.rb
+++ b/activestorage/test/controllers/blobs_controller_test.rb
@@ -5,7 +5,7 @@ require "database/setup"
class ActiveStorage::BlobsControllerTest < ActionDispatch::IntegrationTest
setup do
- @blob = create_image_blob filename: "racecar.jpg"
+ @blob = create_file_blob filename: "racecar.jpg"
end
test "showing blob utilizes browser caching" do
diff --git a/activestorage/test/controllers/previews_controller_test.rb b/activestorage/test/controllers/previews_controller_test.rb
new file mode 100644
index 0000000000..c3151a710e
--- /dev/null
+++ b/activestorage/test/controllers/previews_controller_test.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+class ActiveStorage::PreviewsControllerTest < ActionDispatch::IntegrationTest
+ setup do
+ @blob = create_file_blob filename: "report.pdf", content_type: "application/pdf"
+ end
+
+ test "showing preview inline" do
+ get rails_blob_preview_url(
+ filename: @blob.filename,
+ signed_blob_id: @blob.signed_id,
+ variation_key: ActiveStorage::Variation.encode(resize: "100x100"))
+
+ assert @blob.preview_image.attached?
+ assert_redirected_to(/report\.png\?.*disposition=inline/)
+
+ image = read_image(@blob.preview_image.variant(resize: "100x100"))
+ assert_equal 77, image.width
+ assert_equal 100, image.height
+ end
+end
diff --git a/activestorage/test/controllers/variants_controller_test.rb b/activestorage/test/controllers/variants_controller_test.rb
index 0a049f3bc4..6c70d73786 100644
--- a/activestorage/test/controllers/variants_controller_test.rb
+++ b/activestorage/test/controllers/variants_controller_test.rb
@@ -5,7 +5,7 @@ require "database/setup"
class ActiveStorage::VariantsControllerTest < ActionDispatch::IntegrationTest
setup do
- @blob = create_image_blob filename: "racecar.jpg"
+ @blob = create_file_blob filename: "racecar.jpg"
end
test "showing variant inline" do
@@ -16,7 +16,7 @@ class ActiveStorage::VariantsControllerTest < ActionDispatch::IntegrationTest
assert_redirected_to(/racecar\.jpg\?.*disposition=inline/)
- image = read_image_variant(@blob.variant(resize: "100x100"))
+ image = read_image(@blob.variant(resize: "100x100"))
assert_equal 100, image.width
assert_equal 67, image.height
end
diff --git a/activestorage/test/database/setup.rb b/activestorage/test/database/setup.rb
index 87564499e6..705650a25d 100644
--- a/activestorage/test/database/setup.rb
+++ b/activestorage/test/database/setup.rb
@@ -3,5 +3,5 @@
require_relative "create_users_migration"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
-ActiveRecord::Migrator.migrate File.expand_path("../../../db/migrate", __FILE__)
+ActiveRecord::Migrator.migrate File.expand_path("../../db/migrate", __dir__)
ActiveStorageCreateUsers.migrate(:up)
diff --git a/activestorage/test/dummy/bin/bundle b/activestorage/test/dummy/bin/bundle
index 277e128251..5015ba6f8b 100755
--- a/activestorage/test/dummy/bin/bundle
+++ b/activestorage/test/dummy/bin/bundle
@@ -1,5 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
-ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
load Gem.bin_path("bundler", "bundle")
diff --git a/activestorage/test/fixtures/files/report.pdf b/activestorage/test/fixtures/files/report.pdf
new file mode 100644
index 0000000000..cccb9b5d64
--- /dev/null
+++ b/activestorage/test/fixtures/files/report.pdf
Binary files differ
diff --git a/activestorage/test/fixtures/files/rotated_video.mp4 b/activestorage/test/fixtures/files/rotated_video.mp4
new file mode 100644
index 0000000000..4c7a4e9e57
--- /dev/null
+++ b/activestorage/test/fixtures/files/rotated_video.mp4
Binary files differ
diff --git a/activestorage/test/fixtures/files/video.mp4 b/activestorage/test/fixtures/files/video.mp4
new file mode 100644
index 0000000000..8fb1c5b24d
--- /dev/null
+++ b/activestorage/test/fixtures/files/video.mp4
Binary files differ
diff --git a/activestorage/test/fixtures/files/video_without_video_stream.mp4 b/activestorage/test/fixtures/files/video_without_video_stream.mp4
new file mode 100644
index 0000000000..e6a55f868b
--- /dev/null
+++ b/activestorage/test/fixtures/files/video_without_video_stream.mp4
Binary files differ
diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb
index ac346c0087..47f2bd7911 100644
--- a/activestorage/test/models/attachments_test.rb
+++ b/activestorage/test/models/attachments_test.rb
@@ -15,7 +15,7 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
assert_equal "funky.jpg", @user.avatar.filename.to_s
end
- test "attach existing sgid blob" do
+ test "attach existing blob from a signed ID" do
@user.avatar.attach create_blob(filename: "funky.jpg").signed_id
assert_equal "funky.jpg", @user.avatar.filename.to_s
end
@@ -63,6 +63,31 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
assert_equal "funky.jpg", @user.avatar_attachment.blob.filename.to_s
end
+ test "analyze newly-attached blob" do
+ perform_enqueued_jobs do
+ @user.avatar.attach create_file_blob
+ end
+
+ assert_equal 4104, @user.avatar.reload.metadata[:width]
+ assert_equal 2736, @user.avatar.metadata[:height]
+ end
+
+ test "analyze attached blob only once" do
+ blob = create_file_blob
+
+ perform_enqueued_jobs do
+ @user.avatar.attach blob
+ end
+
+ assert blob.reload.analyzed?
+
+ @user.avatar.attachment.destroy
+
+ assert_no_enqueued_jobs do
+ @user.reload.avatar.attach blob
+ end
+ end
+
test "purge attached blob" do
@user.avatar.attach create_blob(filename: "funky.jpg")
avatar_key = @user.avatar.key
@@ -84,6 +109,19 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
end
end
+ test "find with attached blob" do
+ records = %w[alice bob].map do |name|
+ User.create!(name: name).tap do |user|
+ user.avatar.attach create_blob(filename: "#{name}.jpg")
+ end
+ end
+
+ users = User.where(id: records.map(&:id)).with_attached_avatar.all
+
+ assert_equal "alice.jpg", users.first.avatar.filename.to_s
+ assert_equal "bob.jpg", users.second.avatar.filename.to_s
+ end
+
test "attach existing blobs" do
@user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
@@ -122,6 +160,38 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
assert_equal "town.jpg", @user.highlights_attachments.first.blob.filename.to_s
end
+ test "analyze newly-attached blobs" do
+ perform_enqueued_jobs do
+ @user.highlights.attach(
+ create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg"),
+ create_file_blob(filename: "video.mp4", content_type: "video/mp4"))
+ end
+
+ assert_equal 4104, @user.highlights.first.metadata[:width]
+ assert_equal 2736, @user.highlights.first.metadata[:height]
+
+ assert_equal 640, @user.highlights.second.metadata[:width]
+ assert_equal 480, @user.highlights.second.metadata[:height]
+ end
+
+ test "analyze attached blobs only once" do
+ blobs = [
+ create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg"),
+ create_file_blob(filename: "video.mp4", content_type: "video/mp4")
+ ]
+
+ perform_enqueued_jobs do
+ @user.highlights.attach(blobs)
+ end
+
+ assert blobs.each(&:reload).all?(&:analyzed?)
+
+ @user.highlights.attachments.destroy_all
+
+ assert_no_enqueued_jobs do
+ @user.highlights.attach(blobs)
+ end
+ end
test "purge attached blobs" do
@user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
diff --git a/activestorage/test/models/preview_test.rb b/activestorage/test/models/preview_test.rb
new file mode 100644
index 0000000000..bcd8442f4b
--- /dev/null
+++ b/activestorage/test/models/preview_test.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+class ActiveStorage::PreviewTest < ActiveSupport::TestCase
+ test "previewing a PDF" do
+ blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf")
+ preview = blob.preview(resize: "640x280").processed
+
+ assert preview.image.attached?
+ assert_equal "report.png", preview.image.filename.to_s
+ assert_equal "image/png", preview.image.content_type
+
+ image = read_image(preview.image)
+ assert_equal 612, image.width
+ assert_equal 792, image.height
+ end
+
+ test "previewing an MP4 video" do
+ blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4")
+ preview = blob.preview(resize: "640x280").processed
+
+ assert preview.image.attached?
+ assert_equal "video.png", preview.image.filename.to_s
+ assert_equal "image/png", preview.image.content_type
+
+ image = read_image(preview.image)
+ assert_equal 640, image.width
+ assert_equal 480, image.height
+ end
+
+ test "previewing an unpreviewable blob" do
+ blob = create_file_blob
+
+ assert_raises ActiveStorage::Blob::UnpreviewableError do
+ blob.preview resize: "640x280"
+ end
+ end
+end
diff --git a/activestorage/test/models/representation_test.rb b/activestorage/test/models/representation_test.rb
new file mode 100644
index 0000000000..29fe61aee4
--- /dev/null
+++ b/activestorage/test/models/representation_test.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+class ActiveStorage::RepresentationTest < ActiveSupport::TestCase
+ test "representing an image" do
+ blob = create_file_blob
+ representation = blob.representation(resize: "100x100").processed
+
+ image = read_image(representation.image)
+ assert_equal 100, image.width
+ assert_equal 67, image.height
+ end
+
+ test "representing a PDF" do
+ blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf")
+ representation = blob.representation(resize: "640x280").processed
+
+ image = read_image(representation.image)
+ assert_equal 612, image.width
+ assert_equal 792, image.height
+ end
+
+ test "representing an MP4 video" do
+ blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4")
+ representation = blob.representation(resize: "640x280").processed
+
+ image = read_image(representation.image)
+ assert_equal 640, image.width
+ assert_equal 480, image.height
+ end
+
+ test "representing an unrepresentable blob" do
+ blob = create_blob
+
+ assert_raises ActiveStorage::Blob::UnrepresentableError do
+ blob.representation resize: "100x100"
+ end
+ end
+end
diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb
index ca112ab907..b7d20ab55a 100644
--- a/activestorage/test/models/variant_test.rb
+++ b/activestorage/test/models/variant_test.rb
@@ -5,14 +5,14 @@ require "database/setup"
class ActiveStorage::VariantTest < ActiveSupport::TestCase
setup do
- @blob = create_image_blob filename: "racecar.jpg"
+ @blob = create_file_blob filename: "racecar.jpg"
end
test "resized variation" do
variant = @blob.variant(resize: "100x100").processed
assert_match(/racecar\.jpg/, variant.service_url)
- image = read_image_variant(variant)
+ image = read_image(variant)
assert_equal 100, image.width
assert_equal 67, image.height
end
@@ -21,9 +21,14 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
variant = @blob.variant(resize: "100x100", monochrome: true).processed
assert_match(/racecar\.jpg/, variant.service_url)
- image = read_image_variant(variant)
+ image = read_image(variant)
assert_equal 100, image.width
assert_equal 67, image.height
assert_match(/Gray/, image.colorspace)
end
+
+ test "service_url doesn't grow in length despite long variant options" do
+ variant = @blob.variant(font: "a" * 10_000).processed
+ assert_operator variant.service_url.length, :<, 500
+ end
end
diff --git a/activestorage/test/previewer/pdf_previewer_test.rb b/activestorage/test/previewer/pdf_previewer_test.rb
new file mode 100644
index 0000000000..fe32f39be4
--- /dev/null
+++ b/activestorage/test/previewer/pdf_previewer_test.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+require "active_storage/previewer/pdf_previewer"
+
+class ActiveStorage::Previewer::PDFPreviewerTest < ActiveSupport::TestCase
+ setup do
+ @blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf")
+ end
+
+ test "previewing a PDF document" do
+ ActiveStorage::Previewer::PDFPreviewer.new(@blob).preview do |attachable|
+ assert_equal "image/png", attachable[:content_type]
+ assert_equal "report.png", attachable[:filename]
+
+ image = MiniMagick::Image.read(attachable[:io])
+ assert_equal 612, image.width
+ assert_equal 792, image.height
+ end
+ end
+end
diff --git a/activestorage/test/previewer/video_previewer_test.rb b/activestorage/test/previewer/video_previewer_test.rb
new file mode 100644
index 0000000000..dba9b0d7e2
--- /dev/null
+++ b/activestorage/test/previewer/video_previewer_test.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+require "active_storage/previewer/video_previewer"
+
+class ActiveStorage::Previewer::VideoPreviewerTest < ActiveSupport::TestCase
+ setup do
+ @blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4")
+ end
+
+ test "previewing an MP4 video" do
+ ActiveStorage::Previewer::VideoPreviewer.new(@blob).preview do |attachable|
+ assert_equal "image/png", attachable[:content_type]
+ assert_equal "video.png", attachable[:filename]
+
+ image = MiniMagick::Image.read(attachable[:io])
+ assert_equal 640, image.width
+ assert_equal 480, image.height
+ end
+ end
+end
diff --git a/activestorage/test/service/azure_storage_service_test.rb b/activestorage/test/service/azure_storage_service_test.rb
index 4729bdfbc5..be31bbe858 100644
--- a/activestorage/test/service/azure_storage_service_test.rb
+++ b/activestorage/test/service/azure_storage_service_test.rb
@@ -11,9 +11,9 @@ if SERVICE_CONFIGURATIONS[:azure]
test "signed URL generation" do
url = @service.url(FIXTURE_KEY, expires_in: 5.minutes,
- disposition: "inline; filename=\"avatar.png\"", filename: "avatar.png", content_type: "image/png")
+ disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png")
- assert_match(/(\S+)&rscd=inline%3B\+filename%3D%22avatar.png%22&rsct=image%2Fpng/, url)
+ assert_match(/(\S+)&rscd=inline%3B\+filename%3D%22avatar\.png%22%3B\+filename\*%3DUTF-8%27%27avatar\.png&rsct=image%2Fpng/, url)
assert_match SERVICE_CONFIGURATIONS[:azure][:container], url
end
end
diff --git a/activestorage/test/service/configurations.yml b/activestorage/test/service/configurations.example.yml
index 56ed37be5d..56ed37be5d 100644
--- a/activestorage/test/service/configurations.yml
+++ b/activestorage/test/service/configurations.example.yml
diff --git a/activestorage/test/service/disk_service_test.rb b/activestorage/test/service/disk_service_test.rb
index e07d1d88bc..4a6361b920 100644
--- a/activestorage/test/service/disk_service_test.rb
+++ b/activestorage/test/service/disk_service_test.rb
@@ -9,6 +9,6 @@ class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase
test "url generation" do
assert_match(/rails\/active_storage\/disk\/.*\/avatar\.png\?content_type=image%2Fpng&disposition=inline/,
- @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: "inline; filename=\"avatar.png\"", filename: "avatar.png", content_type: "image/png"))
+ @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png"))
end
end
diff --git a/activestorage/test/service/gcs_service_test.rb b/activestorage/test/service/gcs_service_test.rb
index f664cee90b..5566c664a9 100644
--- a/activestorage/test/service/gcs_service_test.rb
+++ b/activestorage/test/service/gcs_service_test.rb
@@ -34,10 +34,10 @@ if SERVICE_CONFIGURATIONS[:gcs]
test "signed URL generation" do
freeze_time do
url = SERVICE.bucket.signed_url(FIXTURE_KEY, expires: 120) +
- "&response-content-disposition=inline%3B+filename%3D%22test.txt%22" +
+ "&response-content-disposition=inline%3B+filename%3D%22test.txt%22%3B+filename%2A%3DUTF-8%27%27test.txt" +
"&response-content-type=text%2Fplain"
- assert_equal url, @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: "inline; filename=\"test.txt\"", filename: "test.txt", content_type: "text/plain")
+ assert_equal url, @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
end
end
end
diff --git a/activestorage/test/service/mirror_service_test.rb b/activestorage/test/service/mirror_service_test.rb
index 93e86eff70..92101b1282 100644
--- a/activestorage/test/service/mirror_service_test.rb
+++ b/activestorage/test/service/mirror_service_test.rb
@@ -47,9 +47,11 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
end
test "URL generation in primary service" do
+ filename = ActiveStorage::Filename.new("test.txt")
+
freeze_time do
- assert_equal SERVICE.primary.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt", content_type: "text/plain"),
- @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: "test.txt", content_type: "text/plain")
+ assert_equal SERVICE.primary.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: filename, content_type: "text/plain"),
+ @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: filename, content_type: "text/plain")
end
end
diff --git a/activestorage/test/service/s3_service_test.rb b/activestorage/test/service/s3_service_test.rb
index c07d6396b1..c3818422aa 100644
--- a/activestorage/test/service/s3_service_test.rb
+++ b/activestorage/test/service/s3_service_test.rb
@@ -33,7 +33,7 @@ if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].pr
test "signed URL generation" do
url = @service.url(FIXTURE_KEY, expires_in: 5.minutes,
- disposition: "inline; filename=\"avatar.png\"", filename: "avatar.png", content_type: "image/png")
+ disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png")
assert_match(/s3\.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png.*response-content-type=image%2Fpng/, url)
assert_match SERVICE_CONFIGURATIONS[:s3][:bucket], url
diff --git a/activestorage/test/template/image_tag_test.rb b/activestorage/test/template/image_tag_test.rb
index dedc58452e..80f183e0e3 100644
--- a/activestorage/test/template/image_tag_test.rb
+++ b/activestorage/test/template/image_tag_test.rb
@@ -7,7 +7,7 @@ class ActiveStorage::ImageTagTest < ActionView::TestCase
tests ActionView::Helpers::AssetTagHelper
setup do
- @blob = create_image_blob filename: "racecar.jpg"
+ @blob = create_file_blob filename: "racecar.jpg"
end
test "blob" do
@@ -19,6 +19,12 @@ class ActiveStorage::ImageTagTest < ActionView::TestCase
assert_dom_equal %(<img src="#{polymorphic_url variant}" />), image_tag(variant)
end
+ test "preview" do
+ blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf")
+ preview = blob.preview(resize: "100x100")
+ assert_dom_equal %(<img src="#{polymorphic_url preview}" />), image_tag(preview)
+ end
+
test "attachment" do
attachment = ActiveStorage::Attachment.new(blob: @blob)
assert_dom_equal %(<img src="#{polymorphic_url attachment}" />), image_tag(attachment)
diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb
index 2a969fa326..38408cdad3 100644
--- a/activestorage/test/test_helper.rb
+++ b/activestorage/test/test_helper.rb
@@ -1,11 +1,12 @@
# frozen_string_literal: true
-require File.expand_path("../../test/dummy/config/environment.rb", __FILE__)
+require_relative "dummy/config/environment.rb"
require "bundler/setup"
require "active_support"
require "active_support/test_case"
require "active_support/testing/autorun"
+require "mini_magick"
begin
require "byebug"
@@ -22,7 +23,7 @@ Minitest.backtrace_filter = Minitest::BacktraceFilter.new
require "yaml"
SERVICE_CONFIGURATIONS = begin
- erb = ERB.new(Pathname.new(File.expand_path("../service/configurations.yml", __FILE__)).read)
+ erb = ERB.new(Pathname.new(File.expand_path("service/configurations.yml", __dir__)).read)
configuration = YAML.load(erb.result) || {}
configuration.deep_symbolize_keys
rescue Errno::ENOENT
@@ -32,30 +33,28 @@ end
require "tmpdir"
ActiveStorage::Blob.service = ActiveStorage::Service::DiskService.new(root: Dir.mktmpdir("active_storage_tests"))
-ActiveStorage::Service.logger = ActiveSupport::Logger.new(nil)
+ActiveStorage.logger = ActiveSupport::Logger.new(nil)
ActiveStorage.verifier = ActiveSupport::MessageVerifier.new("Testing")
class ActiveSupport::TestCase
- self.file_fixture_path = File.expand_path("../fixtures/files", __FILE__)
+ self.file_fixture_path = File.expand_path("fixtures/files", __dir__)
private
def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain")
ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type
end
- def create_image_blob(filename: "racecar.jpg", content_type: "image/jpeg")
- ActiveStorage::Blob.create_after_upload! \
- io: file_fixture(filename).open,
- filename: filename, content_type: content_type
+ def create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg")
+ ActiveStorage::Blob.create_after_upload! io: file_fixture(filename).open, filename: filename, content_type: content_type
end
def create_blob_before_direct_upload(filename: "hello.txt", byte_size:, checksum:, content_type: "text/plain")
ActiveStorage::Blob.create_before_direct_upload! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type
end
- def read_image_variant(variant)
- MiniMagick::Image.open variant.service.send(:path_for, variant.key)
+ def read_image(blob_or_variant)
+ MiniMagick::Image.open blob_or_variant.service.send(:path_for, blob_or_variant.key)
end
end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index ac1043df78..8e8e9b9440 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,73 @@
+* `IO#to_json` now returns the `to_s` representation, rather than
+ attempting to convert to an array. This fixes a bug where `IO#to_json`
+ would raise an `IOError` when called on an unreadable object.
+
+ Fixes #26132.
+
+ *Paul Kuruvilla*
+
+* Remove deprecated `halt_callback_chains_on_return_false` option.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `:if` and `:unless` string filter for callbacks.
+
+ *Rafael Mendonça França*
+
+* `Hash#slice` now falls back to Ruby 2.5+'s built-in definition if defined.
+
+ *Akira Matsuda*
+
+* Deprecate `secrets.secret_token`.
+
+ The architecture for secrets had a big upgrade between Rails 3 and Rails 4,
+ when the default changed from using `secret_token` to `secret_key_base`.
+
+ `secret_token` has been soft deprecated in documentation for four years
+ but is still in place to support apps created before Rails 4.
+ Deprecation warnings have been added to help developers upgrade their
+ applications to `secret_key_base`.
+
+ *claudiob*, *Kasper Timm Hansen*
+
+* Return an instance of `HashWithIndifferentAccess` from `HashWithIndifferentAccess#transform_keys`.
+
+ *Yuji Yaginuma*
+
+* Add key rotation support to `MessageEncryptor` and `MessageVerifier`
+
+ This change introduces a `rotate` method to both the `MessageEncryptor` and
+ `MessageVerifier` classes. This method accepts the same arguments and
+ options as the given classes' constructor. The `encrypt_and_verify` method
+ for `MessageEncryptor` and the `verified` method for `MessageVerifier` also
+ accept an optional keyword argument `:on_rotation` block which is called
+ when a rotated instance is used to decrypt or verify the message.
+
+ *Michael J Coyne*
+
+* Deprecate `Module#reachable?` method.
+
+ *bogdanvlviv*
+
+* Add `config/credentials.yml.enc` to store production app secrets.
+
+ Allows saving any authentication credentials for third party services
+ directly in repo encrypted with `config/master.key` or `ENV["RAILS_MASTER_KEY"]`.
+
+ This will eventually replace `Rails.application.secrets` and the encrypted
+ secrets introduced in Rails 5.1.
+
+ *DHH*, *Kasper Timm Hansen*
+
+* Add `ActiveSupport::EncryptedFile` and `ActiveSupport::EncryptedConfiguration`.
+
+ Allows for stashing encrypted files or configuration directly in repo by
+ encrypting it with a key.
+
+ Backs the new credentials setup above, but can also be used independently.
+
+ *DHH*, *Kasper Timm Hansen*
+
* `Module#delegate_missing_to` now raises `DelegationError` if target is nil,
similar to `Module#delegate`.
@@ -11,12 +81,12 @@
Previously:
'one_two'.camelize(true)
- => nil
+ # => nil
Now:
'one_two'.camelize(true)
- => ArgumentError: Invalid option, use either :upper or :lower.
+ # => ArgumentError: Invalid option, use either :upper or :lower.
*Ricardo Díaz*
@@ -31,12 +101,12 @@
Prior to Rails 5.1:
5.minutes % 2.minutes
- => 60
+ # => 60
Now:
5.minutes % 2.minutes
- => 1 minute
+ # => 1 minute
Fixes #29603 and #29743.
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 79b12f36b9..68be94f99d 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -24,11 +24,11 @@
#++
require "securerandom"
-require_relative "active_support/dependencies/autoload"
-require_relative "active_support/version"
-require_relative "active_support/logger"
-require_relative "active_support/lazy_load_hooks"
-require_relative "active_support/core_ext/date_and_time/compatibility"
+require "active_support/dependencies/autoload"
+require "active_support/version"
+require "active_support/logger"
+require "active_support/lazy_load_hooks"
+require "active_support/core_ext/date_and_time/compatibility"
module ActiveSupport
extend ActiveSupport::Autoload
@@ -82,18 +82,6 @@ module ActiveSupport
cattr_accessor :test_order # :nodoc:
- def self.halt_callback_chains_on_return_false
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- ActiveSupport.halt_callback_chains_on_return_false is deprecated and will be removed in Rails 5.2.
- MSG
- end
-
- def self.halt_callback_chains_on_return_false=(value)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- ActiveSupport.halt_callback_chains_on_return_false= is deprecated and will be removed in Rails 5.2.
- MSG
- end
-
def self.to_time_preserves_timezone
DateAndTime::Compatibility.preserve_timezone
end
diff --git a/activesupport/lib/active_support/all.rb b/activesupport/lib/active_support/all.rb
index 6638b25419..4adf446af8 100644
--- a/activesupport/lib/active_support/all.rb
+++ b/activesupport/lib/active_support/all.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
require "active_support"
-require_relative "time"
-require_relative "core_ext"
+require "active_support/time"
+require "active_support/core_ext"
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index 5573f6750e..f481d68198 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "core_ext/benchmark"
-require_relative "core_ext/hash/keys"
+require "active_support/core_ext/benchmark"
+require "active_support/core_ext/hash/keys"
module ActiveSupport
module Benchmarkable
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 49d8965cb1..976104e0c4 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
require "zlib"
-require_relative "core_ext/array/extract_options"
-require_relative "core_ext/array/wrap"
-require_relative "core_ext/module/attribute_accessors"
-require_relative "core_ext/numeric/bytes"
-require_relative "core_ext/numeric/time"
-require_relative "core_ext/object/to_param"
-require_relative "core_ext/string/inflections"
+require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/array/wrap"
+require "active_support/core_ext/module/attribute_accessors"
+require "active_support/core_ext/numeric/bytes"
+require "active_support/core_ext/numeric/time"
+require "active_support/core_ext/object/to_param"
+require "active_support/core_ext/string/inflections"
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 28df2c066e..0812cc34c7 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "../core_ext/marshal"
-require_relative "../core_ext/file/atomic"
-require_relative "../core_ext/string/conversions"
+require "active_support/core_ext/marshal"
+require "active_support/core_ext/file/atomic"
+require "active_support/core_ext/string/conversions"
require "uri/common"
module ActiveSupport
@@ -39,9 +39,8 @@ module ActiveSupport
def cleanup(options = nil)
options = merged_options(options)
search_dir(cache_path) do |fname|
- key = file_path_key(fname)
- entry = read_entry(key, options)
- delete_entry(key, options) if entry && entry.expired?
+ entry = read_entry(fname, options)
+ delete_entry(fname, options) if entry && entry.expired?
end
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index d7bd914722..9242a334f2 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -8,8 +8,8 @@ rescue LoadError => e
end
require "digest/md5"
-require_relative "../core_ext/marshal"
-require_relative "../core_ext/array/extract_options"
+require "active_support/core_ext/marshal"
+require "active_support/core_ext/array/extract_options"
module ActiveSupport
module Cache
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index a3eef1190a..aaa9638fa8 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "../../core_ext/object/duplicable"
-require_relative "../../core_ext/string/inflections"
-require_relative "../../per_thread_registry"
+require "active_support/core_ext/object/duplicable"
+require "active_support/core_ext/string/inflections"
+require "active_support/per_thread_registry"
module ActiveSupport
module Cache
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index e1cbfc945c..0ed4681b7d 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require_relative "concern"
-require_relative "descendants_tracker"
-require_relative "core_ext/array/extract_options"
-require_relative "core_ext/class/attribute"
-require_relative "core_ext/kernel/reporting"
-require_relative "core_ext/kernel/singleton_class"
-require_relative "core_ext/string/filters"
-require_relative "deprecation"
+require "active_support/concern"
+require "active_support/descendants_tracker"
+require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/class/attribute"
+require "active_support/core_ext/kernel/reporting"
+require "active_support/core_ext/kernel/singleton_class"
+require "active_support/core_ext/string/filters"
+require "active_support/deprecation"
require "thread"
module ActiveSupport
@@ -298,8 +298,8 @@ module ActiveSupport
@kind = kind
@filter = filter
@key = compute_identifier filter
- @if = Array(options[:if])
- @unless = Array(options[:unless])
+ @if = check_conditionals(Array(options[:if]))
+ @unless = check_conditionals(Array(options[:unless]))
end
def filter; @key; end
@@ -323,7 +323,7 @@ module ActiveSupport
def duplicates?(other)
case @filter
- when Symbol, String
+ when Symbol
matches?(other.kind, other.filter)
else
false
@@ -350,9 +350,21 @@ module ActiveSupport
end
private
+ def check_conditionals(conditionals)
+ if conditionals.any? { |c| c.is_a?(String) }
+ raise ArgumentError, <<-MSG.squish
+ Passing string to be evaluated in :if and :unless conditional
+ options is not supported. Pass a symbol for an instance method,
+ or a lambda, proc or block, instead.
+ MSG
+ end
+
+ conditionals
+ end
+
def compute_identifier(filter)
case filter
- when String, ::Proc
+ when ::Proc
filter.object_id
else
filter
@@ -427,7 +439,6 @@ module ActiveSupport
# Filters support:
#
# Symbols:: A method to call.
- # Strings:: Some content to evaluate.
# Procs:: A proc to call with the object.
# Objects:: An object with a <tt>before_foo</tt> method on it to call.
#
@@ -437,8 +448,6 @@ module ActiveSupport
case filter
when Symbol
new(nil, filter, [], nil)
- when String
- new(nil, :instance_exec, [:value], compile_lambda(filter))
when Conditionals::Value
new(filter, :call, [:target, :value], nil)
when ::Proc
@@ -455,10 +464,6 @@ module ActiveSupport
new(filter, method_to_call, [:target], nil)
end
end
-
- def self.compile_lambda(filter)
- eval("lambda { |value| #{filter} }")
- end
end
# Execute before and after filters in a sequence instead of
@@ -651,26 +656,17 @@ module ActiveSupport
#
# ===== Options
#
- # * <tt>:if</tt> - A symbol, a string (deprecated) or an array of symbols,
- # each naming an instance method or a proc; the callback will be called
- # only when they all return a true value.
- # * <tt>:unless</tt> - A symbol, a string (deprecated) or an array of symbols,
- # each naming an instance method or a proc; the callback will be called
- # only when they all return a false value.
+ # * <tt>:if</tt> - A symbol or an array of symbols, each naming an instance
+ # method or a proc; the callback will be called only when they all return
+ # a true value.
+ # * <tt>:unless</tt> - A symbol or an array of symbols, each naming an
+ # instance method or a proc; the callback will be called only when they
+ # all return a false value.
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
# existing chain rather than appended.
def set_callback(name, *filter_list, &block)
type, filters, options = normalize_callback_params(filter_list, block)
- if options[:if].is_a?(String) || options[:unless].is_a?(String)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing string to be evaluated in :if and :unless conditional
- options is deprecated and will be removed in Rails 5.2 without
- replacement. Pass a symbol for an instance method, or a lambda,
- proc or block, instead.
- MSG
- end
-
self_chain = get_callbacks name
mapped = filters.map do |filter|
Callback.build(self_chain, filter, type, options)
@@ -695,13 +691,6 @@ module ActiveSupport
def skip_callback(name, *filter_list, &block)
type, filters, options = normalize_callback_params(filter_list, block)
- if options[:if].is_a?(String) || options[:unless].is_a?(String)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing string to :if and :unless conditional options is deprecated
- and will be removed in Rails 5.2 without replacement.
- MSG
- end
-
options[:raise] = true unless options.key?(:raise)
__update_callbacks(name) do |target, chain|
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index 8e8e27b4ec..4d6f7819bb 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require_relative "concern"
-require_relative "ordered_options"
-require_relative "core_ext/array/extract_options"
-require_relative "core_ext/regexp"
+require "active_support/concern"
+require "active_support/ordered_options"
+require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/regexp"
module ActiveSupport
# Configurable provides a <tt>config</tt> method to store and retrieve
diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb
index fdc209ef68..6d83b76882 100644
--- a/activesupport/lib/active_support/core_ext/array.rb
+++ b/activesupport/lib/active_support/core_ext/array.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require_relative "array/wrap"
-require_relative "array/access"
-require_relative "array/conversions"
-require_relative "array/extract_options"
-require_relative "array/grouping"
-require_relative "array/prepend_and_append"
-require_relative "array/inquiry"
+require "active_support/core_ext/array/wrap"
+require "active_support/core_ext/array/access"
+require "active_support/core_ext/array/conversions"
+require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/array/grouping"
+require "active_support/core_ext/array/prepend_and_append"
+require "active_support/core_ext/array/inquiry"
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 6e17a81f0f..ea688ed2ea 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require_relative "../../xml_mini"
-require_relative "../hash/keys"
-require_relative "../string/inflections"
-require_relative "../object/to_param"
-require_relative "../object/to_query"
+require "active_support/xml_mini"
+require "active_support/core_ext/hash/keys"
+require "active_support/core_ext/string/inflections"
+require "active_support/core_ext/object/to_param"
+require "active_support/core_ext/object/to_query"
class Array
# Converts the array to a comma-separated sentence where the last element is
@@ -181,7 +181,7 @@ class Array
# </messages>
#
def to_xml(options = {})
- require_relative "../../builder" unless defined?(Builder)
+ require "active_support/builder" unless defined?(Builder)
options = options.dup
options[:indent] ||= 2
diff --git a/activesupport/lib/active_support/core_ext/array/inquiry.rb b/activesupport/lib/active_support/core_ext/array/inquiry.rb
index 4f4a9fa361..92c61bf201 100644
--- a/activesupport/lib/active_support/core_ext/array/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/array/inquiry.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../array_inquirer"
+require "active_support/array_inquirer"
class Array
# Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way
diff --git a/activesupport/lib/active_support/core_ext/big_decimal.rb b/activesupport/lib/active_support/core_ext/big_decimal.rb
index 5568db689b..9e6a9d6331 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal.rb
@@ -1,3 +1,3 @@
# frozen_string_literal: true
-require_relative "big_decimal/conversions"
+require "active_support/core_ext/big_decimal/conversions"
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index 65e1d68399..1c110fd07b 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,4 +1,4 @@
# frozen_string_literal: true
-require_relative "class/attribute"
-require_relative "class/subclasses"
+require "active_support/core_ext/class/attribute"
+require "active_support/core_ext/class/subclasses"
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 3453fc4ac6..7928efb871 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "../kernel/singleton_class"
-require_relative "../module/redefine_method"
-require_relative "../array/extract_options"
+require "active_support/core_ext/kernel/singleton_class"
+require "active_support/core_ext/module/redefine_method"
+require "active_support/core_ext/array/extract_options"
class Class
# Declare a class-level attribute whose value is inheritable by subclasses.
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index b4dbcc6698..a77354e153 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -3,4 +3,4 @@
# cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d,
# but we keep this around for libraries that directly require it knowing they
# want cattr_*. No need to deprecate.
-require_relative "../module/attribute_accessors"
+require "active_support/core_ext/module/attribute_accessors"
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 4c910feb44..75e65337b7 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -1,8 +1,5 @@
# frozen_string_literal: true
-require_relative "../module/anonymous"
-require_relative "../module/reachable"
-
class Class
begin
# Test if this Ruby supports each_object against singleton_class
diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb
index 2a2ed5496f..cce73f2db2 100644
--- a/activesupport/lib/active_support/core_ext/date.rb
+++ b/activesupport/lib/active_support/core_ext/date.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "date/acts_like"
-require_relative "date/blank"
-require_relative "date/calculations"
-require_relative "date/conversions"
-require_relative "date/zones"
+require "active_support/core_ext/date/acts_like"
+require "active_support/core_ext/date/blank"
+require "active_support/core_ext/date/calculations"
+require "active_support/core_ext/date/conversions"
+require "active_support/core_ext/date/zones"
diff --git a/activesupport/lib/active_support/core_ext/date/acts_like.rb b/activesupport/lib/active_support/core_ext/date/acts_like.rb
index 81769129b5..c8077f3774 100644
--- a/activesupport/lib/active_support/core_ext/date/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/date/acts_like.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../object/acts_like"
+require "active_support/core_ext/object/acts_like"
class Date
# Duck-types as a Date-like class. See Object#acts_like?.
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 1a19ee8b1e..1cd7acb05d 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
require "date"
-require_relative "../../duration"
-require_relative "../object/acts_like"
-require_relative "zones"
-require_relative "../time/zones"
-require_relative "../date_and_time/calculations"
+require "active_support/duration"
+require "active_support/core_ext/object/acts_like"
+require "active_support/core_ext/date/zones"
+require "active_support/core_ext/time/zones"
+require "active_support/core_ext/date_and_time/calculations"
class Date
include DateAndTime::Calculations
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 88fb572725..870119dc7f 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
require "date"
-require_relative "../../inflector/methods"
-require_relative "zones"
-require_relative "../module/redefine_method"
+require "active_support/inflector/methods"
+require "active_support/core_ext/date/zones"
+require "active_support/core_ext/module/redefine_method"
class Date
DATE_FORMATS = {
diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb
index 1aed070df0..2dcf97cff8 100644
--- a/activesupport/lib/active_support/core_ext/date/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date/zones.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "date"
-require_relative "../date_and_time/zones"
+require "active_support/core_ext/date_and_time/zones"
class Date
include DateAndTime::Zones
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index 8546f7e57b..265c6949e1 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../object/try"
+require "active_support/core_ext/object/try"
module DateAndTime
module Calculations
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
index 5b3aabf6a5..d33c36ef73 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../module/attribute_accessors"
+require "active_support/core_ext/module/attribute_accessors"
module DateAndTime
module Compatibility
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index b3d6773e4f..790dbeec1b 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "date_time/acts_like"
-require_relative "date_time/blank"
-require_relative "date_time/calculations"
-require_relative "date_time/compatibility"
-require_relative "date_time/conversions"
+require "active_support/core_ext/date_time/acts_like"
+require "active_support/core_ext/date_time/blank"
+require "active_support/core_ext/date_time/calculations"
+require "active_support/core_ext/date_time/compatibility"
+require "active_support/core_ext/date_time/conversions"
diff --git a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb
index 02acb5fe21..5dccdfe219 100644
--- a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "date"
-require_relative "../object/acts_like"
+require "active_support/core_ext/object/acts_like"
class DateTime
# Duck-types as a Date-like class. See Object#acts_like?.
diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
index bb9714545e..424f64d6fa 100644
--- a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../date_and_time/compatibility"
-require_relative "../module/redefine_method"
+require "active_support/core_ext/date_and_time/compatibility"
+require "active_support/core_ext/module/redefine_method"
class DateTime
include DateAndTime::Compatibility
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index e4c8f9898d..29725c89f7 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
require "date"
-require_relative "../../inflector/methods"
-require_relative "../time/conversions"
-require_relative "calculations"
-require_relative "../../values/time_zone"
+require "active_support/inflector/methods"
+require "active_support/core_ext/time/conversions"
+require "active_support/core_ext/date_time/calculations"
+require "active_support/values/time_zone"
class DateTime
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
diff --git a/activesupport/lib/active_support/core_ext/file.rb b/activesupport/lib/active_support/core_ext/file.rb
index 3c2364167d..64553bfa4e 100644
--- a/activesupport/lib/active_support/core_ext/file.rb
+++ b/activesupport/lib/active_support/core_ext/file.rb
@@ -1,3 +1,3 @@
# frozen_string_literal: true
-require_relative "file/atomic"
+require "active_support/core_ext/file/atomic"
diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb
index a74a8c15a6..e19aeaa983 100644
--- a/activesupport/lib/active_support/core_ext/hash.rb
+++ b/activesupport/lib/active_support/core_ext/hash.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require_relative "hash/compact"
-require_relative "hash/conversions"
-require_relative "hash/deep_merge"
-require_relative "hash/except"
-require_relative "hash/indifferent_access"
-require_relative "hash/keys"
-require_relative "hash/reverse_merge"
-require_relative "hash/slice"
-require_relative "hash/transform_values"
+require "active_support/core_ext/hash/compact"
+require "active_support/core_ext/hash/conversions"
+require "active_support/core_ext/hash/deep_merge"
+require "active_support/core_ext/hash/except"
+require "active_support/core_ext/hash/indifferent_access"
+require "active_support/core_ext/hash/keys"
+require "active_support/core_ext/hash/reverse_merge"
+require "active_support/core_ext/hash/slice"
+require "active_support/core_ext/hash/transform_values"
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 0c0701134e..11d28d12a1 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -1,13 +1,13 @@
# frozen_string_literal: true
-require_relative "../../xml_mini"
-require_relative "../../time"
-require_relative "../object/blank"
-require_relative "../object/to_param"
-require_relative "../object/to_query"
-require_relative "../array/wrap"
-require_relative "reverse_merge"
-require_relative "../string/inflections"
+require "active_support/xml_mini"
+require "active_support/time"
+require "active_support/core_ext/object/blank"
+require "active_support/core_ext/object/to_param"
+require "active_support/core_ext/object/to_query"
+require "active_support/core_ext/array/wrap"
+require "active_support/core_ext/hash/reverse_merge"
+require "active_support/core_ext/string/inflections"
class Hash
# Returns a string containing an XML representation of its receiver:
@@ -73,7 +73,7 @@ class Hash
# configure your own builder with the <tt>:builder</tt> option. The method also accepts
# options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
def to_xml(options = {})
- require_relative "../../builder" unless defined?(Builder)
+ require "active_support/builder" unless defined?(Builder)
options = options.dup
options[:indent] ||= 2
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index 4681d986db..a38f33f128 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../hash_with_indifferent_access"
+require "active_support/hash_with_indifferent_access"
class Hash
# Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index 94dc225edb..2bd0a56ea4 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -21,9 +21,8 @@ class Hash
# valid_keys = [:mass, :velocity, :time]
# search(options.slice(*valid_keys))
def slice(*keys)
- keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
- keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
- end
+ keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
+ end unless method_defined?(:slice)
# Replaces the hash with only the given keys.
# Returns a hash containing the removed key/value pairs.
@@ -31,7 +30,6 @@ class Hash
# { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
# # => {:c=>3, :d=>4}
def slice!(*keys)
- keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
omit = slice(*self.keys - keys)
hash = slice(*keys)
hash.default = default
diff --git a/activesupport/lib/active_support/core_ext/integer.rb b/activesupport/lib/active_support/core_ext/integer.rb
index df80e7ffdb..d22701306a 100644
--- a/activesupport/lib/active_support/core_ext/integer.rb
+++ b/activesupport/lib/active_support/core_ext/integer.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-require_relative "integer/multiple"
-require_relative "integer/inflections"
-require_relative "integer/time"
+require "active_support/core_ext/integer/multiple"
+require "active_support/core_ext/integer/inflections"
+require "active_support/core_ext/integer/time"
diff --git a/activesupport/lib/active_support/core_ext/integer/inflections.rb b/activesupport/lib/active_support/core_ext/integer/inflections.rb
index 7192e92346..aef3266f28 100644
--- a/activesupport/lib/active_support/core_ext/integer/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/integer/inflections.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../inflector"
+require "active_support/inflector"
class Integer
# Ordinalize turns a number into an ordinal string used to denote the
diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb
index 30c8f3bcf2..5efb89cf9f 100644
--- a/activesupport/lib/active_support/core_ext/integer/time.rb
+++ b/activesupport/lib/active_support/core_ext/integer/time.rb
@@ -1,29 +1,20 @@
# frozen_string_literal: true
-require_relative "../../duration"
-require_relative "../numeric/time"
+require "active_support/duration"
+require "active_support/core_ext/numeric/time"
class Integer
- # Enables the use of time calculations and declarations, like <tt>45.minutes +
- # 2.hours + 4.years</tt>.
+ # Returns a Duration instance matching the number of months provided.
#
- # These methods use Time#advance for precise date calculations when using
- # <tt>from_now</tt>, +ago+, etc. as well as adding or subtracting their
- # results from a Time object.
- #
- # # equivalent to Time.now.advance(months: 1)
- # 1.month.from_now
- #
- # # equivalent to Time.now.advance(years: 2)
- # 2.years.from_now
- #
- # # equivalent to Time.now.advance(months: 4, years: 5)
- # (4.months + 5.years).from_now
+ # 2.months # => 2 months
def months
ActiveSupport::Duration.months(self)
end
alias :month :months
+ # Returns a Duration instance matching the number of years provided.
+ #
+ # 2.years # => 2 years
def years
ActiveSupport::Duration.years(self)
end
diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb
index 30810ce315..0f4356fbdd 100644
--- a/activesupport/lib/active_support/core_ext/kernel.rb
+++ b/activesupport/lib/active_support/core_ext/kernel.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "kernel/agnostics"
-require_relative "kernel/concern"
-require_relative "kernel/reporting"
-require_relative "kernel/singleton_class"
+require "active_support/core_ext/kernel/agnostics"
+require "active_support/core_ext/kernel/concern"
+require "active_support/core_ext/kernel/reporting"
+require "active_support/core_ext/kernel/singleton_class"
diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb
index 32af9981a5..0b2baed780 100644
--- a/activesupport/lib/active_support/core_ext/kernel/concern.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../module/concerning"
+require "active_support/core_ext/module/concerning"
module Kernel
module_function
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index 8445643730..d91e3fba6a 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -1,14 +1,14 @@
# frozen_string_literal: true
-require_relative "module/aliasing"
-require_relative "module/introspection"
-require_relative "module/anonymous"
-require_relative "module/reachable"
-require_relative "module/attribute_accessors"
-require_relative "module/attribute_accessors_per_thread"
-require_relative "module/attr_internal"
-require_relative "module/concerning"
-require_relative "module/delegation"
-require_relative "module/deprecation"
-require_relative "module/redefine_method"
-require_relative "module/remove_method"
+require "active_support/core_ext/module/aliasing"
+require "active_support/core_ext/module/introspection"
+require "active_support/core_ext/module/anonymous"
+require "active_support/core_ext/module/reachable"
+require "active_support/core_ext/module/attribute_accessors"
+require "active_support/core_ext/module/attribute_accessors_per_thread"
+require "active_support/core_ext/module/attr_internal"
+require "active_support/core_ext/module/concerning"
+require "active_support/core_ext/module/delegation"
+require "active_support/core_ext/module/deprecation"
+require "active_support/core_ext/module/redefine_method"
+require "active_support/core_ext/module/remove_method"
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index e667c1f4a4..580baffa2b 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../array/extract_options"
-require_relative "../regexp"
+require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/regexp"
# Extends the module object with class/module and instance accessors for
# class/module attributes, just like the native attr* accessors for instance
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
index 90a350c5dc..4b9b6ea9bd 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../array/extract_options"
-require_relative "../regexp"
+require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/regexp"
# Extends the module object with class/module and instance accessors for
# class/module attributes, just like the native attr* accessors for instance
diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index 17c202a24a..370a948eea 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../concern"
+require "active_support/concern"
class Module
# = Bite-sized separation of concerns
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 1840dc942f..a77f903db5 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "set"
-require_relative "../regexp"
+require "active_support/core_ext/regexp"
class Module
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index 540385ef6f..c5bb598bd1 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../inflector"
+require "active_support/inflector"
class Module
# Returns the name of the module containing this one.
diff --git a/activesupport/lib/active_support/core_ext/module/reachable.rb b/activesupport/lib/active_support/core_ext/module/reachable.rb
index 91b230b46c..e9cbda5245 100644
--- a/activesupport/lib/active_support/core_ext/module/reachable.rb
+++ b/activesupport/lib/active_support/core_ext/module/reachable.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
-require_relative "anonymous"
-require_relative "../string/inflections"
+require "active_support/core_ext/module/anonymous"
+require "active_support/core_ext/string/inflections"
class Module
def reachable? #:nodoc:
!anonymous? && name.safe_constantize.equal?(self)
end
+ deprecate :reachable?
end
diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb
index 704f144bdd..97eb5f9eca 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "redefine_method"
+require "active_support/core_ext/module/redefine_method"
class Module
# Removes the named method, if it exists.
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index 76e33a7cb0..0b04e359f9 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "numeric/bytes"
-require_relative "numeric/time"
-require_relative "numeric/inquiry"
-require_relative "numeric/conversions"
+require "active_support/core_ext/numeric/bytes"
+require "active_support/core_ext/numeric/time"
+require "active_support/core_ext/numeric/inquiry"
+require "active_support/core_ext/numeric/conversions"
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 05528f5069..e675f32cd4 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "../big_decimal/conversions"
-require_relative "../../number_helper"
-require_relative "../module/deprecation"
+require "active_support/core_ext/big_decimal/conversions"
+require "active_support/number_helper"
+require "active_support/core_ext/module/deprecation"
module ActiveSupport::NumericWithFormat
# Provides options for converting numbers into formatted strings.
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 0cee87cb86..bc4627f7a2 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -1,25 +1,15 @@
# frozen_string_literal: true
-require_relative "../../duration"
-require_relative "../time/calculations"
-require_relative "../time/acts_like"
-require_relative "../date/calculations"
-require_relative "../date/acts_like"
+require "active_support/duration"
+require "active_support/core_ext/time/calculations"
+require "active_support/core_ext/time/acts_like"
+require "active_support/core_ext/date/calculations"
+require "active_support/core_ext/date/acts_like"
class Numeric
- # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years.
+ # Returns a Duration instance matching the number of seconds provided.
#
- # These methods use Time#advance for precise date calculations when using from_now, ago, etc.
- # as well as adding or subtracting their results from a Time object. For example:
- #
- # # equivalent to Time.current.advance(months: 1)
- # 1.month.from_now
- #
- # # equivalent to Time.current.advance(years: 2)
- # 2.years.from_now
- #
- # # equivalent to Time.current.advance(months: 4, years: 5)
- # (4.months + 5.years).from_now
+ # 2.seconds # => 2 seconds
def seconds
ActiveSupport::Duration.seconds(self)
end
@@ -66,10 +56,10 @@ class Numeric
alias :fortnight :fortnights
# Returns the number of milliseconds equivalent to the seconds provided.
- # Used with the standard time durations, like 1.hour.in_milliseconds --
- # so we can feed them to JavaScript functions like getTime().
+ # Used with the standard time durations.
#
- # 2.in_milliseconds # => 2_000
+ # 2.in_milliseconds # => 2000
+ # 1.hour.in_milliseconds # => 3600000
def in_milliseconds
self * 1000
end
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 23f5eec8c7..efd34cc692 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -1,16 +1,16 @@
# frozen_string_literal: true
-require_relative "object/acts_like"
-require_relative "object/blank"
-require_relative "object/duplicable"
-require_relative "object/deep_dup"
-require_relative "object/try"
-require_relative "object/inclusion"
+require "active_support/core_ext/object/acts_like"
+require "active_support/core_ext/object/blank"
+require "active_support/core_ext/object/duplicable"
+require "active_support/core_ext/object/deep_dup"
+require "active_support/core_ext/object/try"
+require "active_support/core_ext/object/inclusion"
-require_relative "object/conversions"
-require_relative "object/instance_variables"
+require "active_support/core_ext/object/conversions"
+require "active_support/core_ext/object/instance_variables"
-require_relative "object/json"
-require_relative "object/to_param"
-require_relative "object/to_query"
-require_relative "object/with_options"
+require "active_support/core_ext/object/json"
+require "active_support/core_ext/object/to_param"
+require "active_support/core_ext/object/to_query"
+require "active_support/core_ext/object/with_options"
diff --git a/activesupport/lib/active_support/core_ext/object/acts_like.rb b/activesupport/lib/active_support/core_ext/object/acts_like.rb
index 2eb72f6b3a..403ee20e39 100644
--- a/activesupport/lib/active_support/core_ext/object/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/object/acts_like.rb
@@ -7,6 +7,15 @@ class Object
# <tt>x.acts_like?(:date)</tt> to do duck-type-safe comparisons, since classes that
# we want to act like Time simply need to define an <tt>acts_like_time?</tt> method.
def acts_like?(duck)
- respond_to? :"acts_like_#{duck}?"
+ case duck
+ when :time
+ respond_to? :acts_like_time?
+ when :date
+ respond_to? :acts_like_date?
+ when :string
+ respond_to? :acts_like_string?
+ else
+ respond_to? :"acts_like_#{duck}?"
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index 397adbdb5a..e42ad852dd 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../regexp"
+require "active_support/core_ext/regexp"
class Object
# An object is blank if it's false, empty, or a whitespace string.
diff --git a/activesupport/lib/active_support/core_ext/object/conversions.rb b/activesupport/lib/active_support/core_ext/object/conversions.rb
index fdc154188a..624fb8d77c 100644
--- a/activesupport/lib/active_support/core_ext/object/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/object/conversions.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "to_param"
-require_relative "to_query"
-require_relative "../array/conversions"
-require_relative "../hash/conversions"
+require "active_support/core_ext/object/to_param"
+require "active_support/core_ext/object/to_query"
+require "active_support/core_ext/array/conversions"
+require "active_support/core_ext/hash/conversions"
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index 4021b15de6..c66c5eb2d9 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "duplicable"
+require "active_support/core_ext/object/duplicable"
class Object
# Returns a deep copy of object if it's duplicable. If it's
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 30495313d2..f7c623fe13 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -5,14 +5,14 @@ require "json"
require "bigdecimal"
require "uri/generic"
require "pathname"
-require_relative "../big_decimal/conversions" # for #to_s
-require_relative "../hash/except"
-require_relative "../hash/slice"
-require_relative "instance_variables"
+require "active_support/core_ext/big_decimal/conversions" # for #to_s
+require "active_support/core_ext/hash/except"
+require "active_support/core_ext/hash/slice"
+require "active_support/core_ext/object/instance_variables"
require "time"
-require_relative "../time/conversions"
-require_relative "../date_time/conversions"
-require_relative "../date/conversions"
+require "active_support/core_ext/time/conversions"
+require "active_support/core_ext/date_time/conversions"
+require "active_support/core_ext/date/conversions"
# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
# their default behavior. That said, we need to define the basic to_json method in all of them,
@@ -135,6 +135,12 @@ module Enumerable
end
end
+class IO
+ def as_json(options = nil) #:nodoc:
+ to_s
+ end
+end
+
class Range
def as_json(options = nil) #:nodoc:
to_s
diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb
index c57488bcbc..6d2bdd70f3 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -1,3 +1,3 @@
# frozen_string_literal: true
-require_relative "to_query"
+require "active_support/core_ext/object/to_query"
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index 47766f6012..b6c464db33 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../option_merger"
+require "active_support/option_merger"
class Object
# An elegant way to factor duplication out of options passed to a series of
diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb
index 89bbbfcb81..51ae0ddd21 100644
--- a/activesupport/lib/active_support/core_ext/range.rb
+++ b/activesupport/lib/active_support/core_ext/range.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "range/conversions"
-require_relative "range/include_range"
-require_relative "range/overlaps"
-require_relative "range/each"
+require "active_support/core_ext/range/conversions"
+require "active_support/core_ext/range/include_range"
+require "active_support/core_ext/range/overlaps"
+require "active_support/core_ext/range/each"
diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb
index 37868f5875..8832fbcb3c 100644
--- a/activesupport/lib/active_support/core_ext/range/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/range/conversions.rb
@@ -2,7 +2,13 @@
module ActiveSupport::RangeWithFormat
RANGE_FORMATS = {
- db: Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" }
+ db: -> (start, stop) do
+ case start
+ when String then "BETWEEN '#{start}' AND '#{stop}'"
+ else
+ "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'"
+ end
+ end
}
# Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 491eec2fc9..757d15c51a 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -1,15 +1,15 @@
# frozen_string_literal: true
-require_relative "string/conversions"
-require_relative "string/filters"
-require_relative "string/multibyte"
-require_relative "string/starts_ends_with"
-require_relative "string/inflections"
-require_relative "string/access"
-require_relative "string/behavior"
-require_relative "string/output_safety"
-require_relative "string/exclude"
-require_relative "string/strip"
-require_relative "string/inquiry"
-require_relative "string/indent"
-require_relative "string/zones"
+require "active_support/core_ext/string/conversions"
+require "active_support/core_ext/string/filters"
+require "active_support/core_ext/string/multibyte"
+require "active_support/core_ext/string/starts_ends_with"
+require "active_support/core_ext/string/inflections"
+require "active_support/core_ext/string/access"
+require "active_support/core_ext/string/behavior"
+require "active_support/core_ext/string/output_safety"
+require "active_support/core_ext/string/exclude"
+require "active_support/core_ext/string/strip"
+require "active_support/core_ext/string/inquiry"
+require "active_support/core_ext/string/indent"
+require "active_support/core_ext/string/zones"
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index f8f6524b2b..29a88b07ad 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "date"
-require_relative "../time/calculations"
+require "active_support/core_ext/time/calculations"
class String
# Converts a string to a Time value.
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index b5bb385033..da53739efc 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../../inflector/methods"
-require_relative "../../inflector/transliterate"
+require "active_support/inflector/methods"
+require "active_support/inflector/transliterate"
# String inflections define new methods on the String class to transform names for different purposes.
# For instance, you can figure out the name of a table from the name of a class.
diff --git a/activesupport/lib/active_support/core_ext/string/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb
index 92069981b6..a796d5fb4f 100644
--- a/activesupport/lib/active_support/core_ext/string/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../string_inquirer"
+require "active_support/string_inquirer"
class String
# Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class,
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index fba5b166a2..38224ea5da 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../multibyte"
+require "active_support/multibyte"
class String
# == Multibyte proxy
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 600b41da10..b712200959 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
require "erb"
-require_relative "../kernel/singleton_class"
-require_relative "../module/redefine_method"
-require_relative "../../multibyte/unicode"
+require "active_support/core_ext/kernel/singleton_class"
+require "active_support/core_ext/module/redefine_method"
+require "active_support/multibyte/unicode"
class ERB
module Util
@@ -15,9 +15,6 @@ class ERB
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
#
- # In your ERB templates, use this method to escape any unsafe content. For example:
- # <%= h @person.name %>
- #
# puts html_escape('is a > 0 & a < 10?')
# # => is a &gt; 0 &amp; a &lt; 10?
def html_escape(s)
diff --git a/activesupport/lib/active_support/core_ext/string/zones.rb b/activesupport/lib/active_support/core_ext/string/zones.rb
index db30c03a8e..55dc231464 100644
--- a/activesupport/lib/active_support/core_ext/string/zones.rb
+++ b/activesupport/lib/active_support/core_ext/string/zones.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "conversions"
-require_relative "../time/zones"
+require "active_support/core_ext/string/conversions"
+require "active_support/core_ext/time/zones"
class String
# Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index 4e16274443..c809def05f 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "time/acts_like"
-require_relative "time/calculations"
-require_relative "time/compatibility"
-require_relative "time/conversions"
-require_relative "time/zones"
+require "active_support/core_ext/time/acts_like"
+require "active_support/core_ext/time/calculations"
+require "active_support/core_ext/time/compatibility"
+require "active_support/core_ext/time/conversions"
+require "active_support/core_ext/time/zones"
diff --git a/activesupport/lib/active_support/core_ext/time/acts_like.rb b/activesupport/lib/active_support/core_ext/time/acts_like.rb
index 309418df42..8572b49639 100644
--- a/activesupport/lib/active_support/core_ext/time/acts_like.rb
+++ b/activesupport/lib/active_support/core_ext/time/acts_like.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../object/acts_like"
+require "active_support/core_ext/object/acts_like"
class Time
# Duck-types as a Time-like class. See Object#acts_like?.
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 0e51df44ef..120768dec5 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require_relative "../../duration"
-require_relative "conversions"
-require_relative "../../time_with_zone"
-require_relative "zones"
-require_relative "../date_and_time/calculations"
-require_relative "../date/calculations"
+require "active_support/duration"
+require "active_support/core_ext/time/conversions"
+require "active_support/time_with_zone"
+require "active_support/core_ext/time/zones"
+require "active_support/core_ext/date_and_time/calculations"
+require "active_support/core_ext/date/calculations"
class Time
include DateAndTime::Calculations
diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb
index 147ae0f802..495e4f307b 100644
--- a/activesupport/lib/active_support/core_ext/time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../date_and_time/compatibility"
-require_relative "../module/redefine_method"
+require "active_support/core_ext/date_and_time/compatibility"
+require "active_support/core_ext/module/redefine_method"
class Time
include DateAndTime::Compatibility
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index e3fc930ef6..345cb2832c 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../../inflector/methods"
-require_relative "../../values/time_zone"
+require "active_support/inflector/methods"
+require "active_support/values/time_zone"
class Time
DATE_FORMATS = {
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index c48edb135f..a5588fd488 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "../../time_with_zone"
-require_relative "acts_like"
-require_relative "../date_and_time/zones"
+require "active_support/time_with_zone"
+require "active_support/core_ext/time/acts_like"
+require "active_support/core_ext/date_and_time/zones"
class Time
include DateAndTime::Zones
@@ -55,10 +55,10 @@ class Time
# end
# end
#
- # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt>
- # objects that have already been created, e.g. any model timestamp
- # attributes that have been read before the block will remain in
- # the application's default timezone.
+ # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt>
+ # objects that have already been created, e.g. any model timestamp
+ # attributes that have been read before the block will remain in
+ # the application's default timezone.
def use_zone(time_zone)
new_zone = find_zone!(time_zone)
begin
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index ad2f21205f..649b900187 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -4,17 +4,17 @@ require "set"
require "thread"
require "concurrent/map"
require "pathname"
-require_relative "core_ext/module/aliasing"
-require_relative "core_ext/module/attribute_accessors"
-require_relative "core_ext/module/introspection"
-require_relative "core_ext/module/anonymous"
-require_relative "core_ext/object/blank"
-require_relative "core_ext/kernel/reporting"
-require_relative "core_ext/load_error"
-require_relative "core_ext/name_error"
-require_relative "core_ext/string/starts_ends_with"
-require_relative "dependencies/interlock"
-require_relative "inflector"
+require "active_support/core_ext/module/aliasing"
+require "active_support/core_ext/module/attribute_accessors"
+require "active_support/core_ext/module/introspection"
+require "active_support/core_ext/module/anonymous"
+require "active_support/core_ext/object/blank"
+require "active_support/core_ext/kernel/reporting"
+require "active_support/core_ext/load_error"
+require "active_support/core_ext/name_error"
+require "active_support/core_ext/string/starts_ends_with"
+require "active_support/dependencies/interlock"
+require "active_support/inflector"
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb
index 1c3775f5eb..1cee85d98f 100644
--- a/activesupport/lib/active_support/dependencies/autoload.rb
+++ b/activesupport/lib/active_support/dependencies/autoload.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../inflector/methods"
+require "active_support/inflector/methods"
module ActiveSupport
# Autoload and eager load conveniences for your library.
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
index 4e9595ed42..948be75638 100644
--- a/activesupport/lib/active_support/dependencies/interlock.rb
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../concurrency/share_lock"
+require "active_support/concurrency/share_lock"
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index e6550fdcd0..a1ad2ca465 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -14,13 +14,13 @@ module ActiveSupport
# a circular require warning for active_support/deprecation.rb.
#
# So, we define the constant first, and load dependencies later.
- require_relative "deprecation/instance_delegator"
- require_relative "deprecation/behaviors"
- require_relative "deprecation/reporting"
- require_relative "deprecation/constant_accessor"
- require_relative "deprecation/method_wrappers"
- require_relative "deprecation/proxy_wrappers"
- require_relative "core_ext/module/deprecation"
+ require "active_support/deprecation/instance_delegator"
+ require "active_support/deprecation/behaviors"
+ require "active_support/deprecation/reporting"
+ require "active_support/deprecation/constant_accessor"
+ require "active_support/deprecation/method_wrappers"
+ require "active_support/deprecation/proxy_wrappers"
+ require "active_support/core_ext/module/deprecation"
include Singleton
include InstanceDelegator
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 967320bcb9..581db5f449 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../notifications"
+require "active_support/notifications"
module ActiveSupport
# Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>.
@@ -27,7 +27,7 @@ module ActiveSupport
if defined?(Rails.logger) && Rails.logger
Rails.logger
else
- require_relative "../logger"
+ require "active_support/logger"
ActiveSupport::Logger.new($stderr)
end
logger.warn message
diff --git a/activesupport/lib/active_support/deprecation/constant_accessor.rb b/activesupport/lib/active_support/deprecation/constant_accessor.rb
index 0dfd96d134..3d7eedf637 100644
--- a/activesupport/lib/active_support/deprecation/constant_accessor.rb
+++ b/activesupport/lib/active_support/deprecation/constant_accessor.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../inflector/methods"
+require "active_support/inflector/methods"
module ActiveSupport
class Deprecation
diff --git a/activesupport/lib/active_support/deprecation/instance_delegator.rb b/activesupport/lib/active_support/deprecation/instance_delegator.rb
index 539357c83e..8beda373a2 100644
--- a/activesupport/lib/active_support/deprecation/instance_delegator.rb
+++ b/activesupport/lib/active_support/deprecation/instance_delegator.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../core_ext/kernel/singleton_class"
-require_relative "../core_ext/module/delegation"
+require "active_support/core_ext/kernel/singleton_class"
+require "active_support/core_ext/module/delegation"
module ActiveSupport
class Deprecation
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 8c942ed9a5..d359992bf5 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../core_ext/module/aliasing"
-require_relative "../core_ext/array/extract_options"
+require "active_support/core_ext/module/aliasing"
+require "active_support/core_ext/array/extract_options"
module ActiveSupport
class Deprecation
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index 1920d75faf..f6c6648917 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../inflector/methods"
-require_relative "../core_ext/regexp"
+require "active_support/inflector/methods"
+require "active_support/core_ext/regexp"
module ActiveSupport
class Deprecation
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 34024fa8c6..1af3411a8a 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require_relative "core_ext/array/conversions"
-require_relative "core_ext/module/delegation"
-require_relative "core_ext/object/acts_like"
-require_relative "core_ext/string/filters"
-require_relative "deprecation"
+require "active_support/core_ext/array/conversions"
+require "active_support/core_ext/module/delegation"
+require "active_support/core_ext/object/acts_like"
+require "active_support/core_ext/string/filters"
+require "active_support/deprecation"
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb
index a002424cd9..9379ec7da6 100644
--- a/activesupport/lib/active_support/duration/iso8601_parser.rb
+++ b/activesupport/lib/active_support/duration/iso8601_parser.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "strscan"
-require_relative "../core_ext/regexp"
+require "active_support/core_ext/regexp"
module ActiveSupport
class Duration
diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb
index 985eac113f..bb177ae5b7 100644
--- a/activesupport/lib/active_support/duration/iso8601_serializer.rb
+++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../core_ext/object/blank"
-require_relative "../core_ext/hash/transform_values"
+require "active_support/core_ext/object/blank"
+require "active_support/core_ext/hash/transform_values"
module ActiveSupport
class Duration
diff --git a/activesupport/lib/active_support/encrypted_configuration.rb b/activesupport/lib/active_support/encrypted_configuration.rb
new file mode 100644
index 0000000000..b403048627
--- /dev/null
+++ b/activesupport/lib/active_support/encrypted_configuration.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require "yaml"
+require "active_support/encrypted_file"
+require "active_support/ordered_options"
+require "active_support/core_ext/object/inclusion"
+require "active_support/core_ext/module/delegation"
+
+module ActiveSupport
+ class EncryptedConfiguration < EncryptedFile
+ delegate :[], :fetch, to: :config
+ delegate_missing_to :options
+
+ def initialize(config_path:, key_path:, env_key:)
+ super content_path: config_path, key_path: key_path, env_key: env_key
+ end
+
+ # Allow a config to be started without a file present
+ def read
+ super
+ rescue ActiveSupport::EncryptedFile::MissingContentError
+ ""
+ end
+
+ def config
+ @config ||= deserialize(read).deep_symbolize_keys
+ end
+
+ private
+ def options
+ @options ||= ActiveSupport::InheritableOptions.new(config)
+ end
+
+ def serialize(config)
+ config.present? ? YAML.dump(config) : ""
+ end
+
+ def deserialize(config)
+ config.present? ? YAML.load(config) : {}
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/encrypted_file.rb b/activesupport/lib/active_support/encrypted_file.rb
new file mode 100644
index 0000000000..1c4c1cb457
--- /dev/null
+++ b/activesupport/lib/active_support/encrypted_file.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require "pathname"
+require "active_support/message_encryptor"
+require "active_support/core_ext/string/strip"
+require "active_support/core_ext/module/delegation"
+
+module ActiveSupport
+ class EncryptedFile
+ class MissingContentError < RuntimeError
+ def initialize(content_path)
+ super "Missing encrypted content file in #{content_path}."
+ end
+ end
+
+ class MissingKeyError < RuntimeError
+ def initialize(key_path:, env_key:)
+ super \
+ "Missing encryption key to decrypt file with. " +
+ "Ask your team for your master key and write it to #{key_path} or put it in the ENV['#{env_key}']."
+ end
+ end
+
+ CIPHER = "aes-128-gcm"
+
+ def self.generate_key
+ SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER))
+ end
+
+
+ attr_reader :content_path, :key_path, :env_key
+
+ def initialize(content_path:, key_path:, env_key:)
+ @content_path, @key_path = Pathname.new(content_path), Pathname.new(key_path)
+ @env_key = env_key
+ end
+
+ def key
+ read_env_key || read_key_file || handle_missing_key
+ end
+
+ def read
+ if content_path.exist?
+ decrypt content_path.binread
+ else
+ raise MissingContentError, content_path
+ end
+ end
+
+ def write(contents)
+ IO.binwrite "#{content_path}.tmp", encrypt(contents)
+ FileUtils.mv "#{content_path}.tmp", content_path
+ end
+
+ def change(&block)
+ writing read, &block
+ end
+
+
+ private
+ def writing(contents)
+ tmp_file = "#{content_path.basename}.#{Process.pid}"
+ tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file)
+ tmp_path.binwrite contents
+
+ yield tmp_path
+
+ updated_contents = tmp_path.binread
+
+ write(updated_contents) if updated_contents != contents
+ ensure
+ FileUtils.rm(tmp_path) if tmp_path.exist?
+ end
+
+
+ def encrypt(contents)
+ encryptor.encrypt_and_sign contents
+ end
+
+ def decrypt(contents)
+ encryptor.decrypt_and_verify contents
+ end
+
+ def encryptor
+ @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER)
+ end
+
+
+ def read_env_key
+ ENV[env_key]
+ end
+
+ def read_key_file
+ key_path.binread.strip if key_path.exist?
+ end
+
+ def handle_missing_key
+ raise MissingKeyError, key_path: key_path, env_key: env_key
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
index fd87d84795..f48c586cad 100644
--- a/activesupport/lib/active_support/execution_wrapper.rb
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "callbacks"
+require "active_support/callbacks"
module ActiveSupport
class ExecutionWrapper
diff --git a/activesupport/lib/active_support/executor.rb b/activesupport/lib/active_support/executor.rb
index e6487ba69d..ce391b07ec 100644
--- a/activesupport/lib/active_support/executor.rb
+++ b/activesupport/lib/active_support/executor.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "execution_wrapper"
+require "active_support/execution_wrapper"
module ActiveSupport
class Executor < ExecutionWrapper
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index bcf7b9de64..1a0bb10815 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "core_ext/time/calculations"
+require "active_support/core_ext/time/calculations"
module ActiveSupport
# FileUpdateChecker specifies the API used by Rails to watch files
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 12291af443..2e2ed8a25d 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "core_ext/hash/keys"
-require_relative "core_ext/hash/reverse_merge"
+require "active_support/core_ext/hash/keys"
+require "active_support/core_ext/hash/reverse_merge"
module ActiveSupport
# Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
@@ -306,6 +306,21 @@ module ActiveSupport
dup.tap { |hash| hash.transform_values!(*args, &block) }
end
+ def transform_keys(*args, &block)
+ return to_enum(:transform_keys) unless block_given?
+ dup.tap { |hash| hash.transform_keys!(*args, &block) }
+ end
+
+ def slice(*keys)
+ keys.map! { |key| convert_key(key) }
+ self.class.new(super)
+ end
+
+ def slice!(*keys)
+ keys.map! { |key| convert_key(key) }
+ super
+ end
+
def compact
dup.tap(&:compact!)
end
diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb
index 80f1475630..d60b3eff30 100644
--- a/activesupport/lib/active_support/i18n.rb
+++ b/activesupport/lib/active_support/i18n.rb
@@ -1,15 +1,15 @@
# frozen_string_literal: true
-require_relative "core_ext/hash/deep_merge"
-require_relative "core_ext/hash/except"
-require_relative "core_ext/hash/slice"
+require "active_support/core_ext/hash/deep_merge"
+require "active_support/core_ext/hash/except"
+require "active_support/core_ext/hash/slice"
begin
require "i18n"
rescue LoadError => e
$stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install"
raise e
end
-require_relative "lazy_load_hooks"
+require "active_support/lazy_load_hooks"
ActiveSupport.run_load_hooks(:i18n)
I18n.load_path << File.expand_path("locale/en.yml", __dir__)
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 369aecff69..ce8bfbfd8c 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
require "active_support"
-require_relative "file_update_checker"
-require_relative "core_ext/array/wrap"
+require "active_support/file_update_checker"
+require "active_support/core_ext/array/wrap"
# :enddoc:
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index e8e1657111..baf1cb3038 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "inflector/inflections"
+require "active_support/inflector/inflections"
#--
# Defines the standard inflection rules. These are the starting point for
diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb
index a6adb15a18..d77f04c9c5 100644
--- a/activesupport/lib/active_support/inflector.rb
+++ b/activesupport/lib/active_support/inflector.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
# in case active_support/inflector is required without the rest of active_support
-require_relative "inflector/inflections"
-require_relative "inflector/transliterate"
-require_relative "inflector/methods"
+require "active_support/inflector/inflections"
+require "active_support/inflector/transliterate"
+require "active_support/inflector/methods"
-require_relative "inflections"
-require_relative "core_ext/string/inflections"
+require "active_support/inflections"
+require "active_support/core_ext/string/inflections"
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 36dfbc5f42..a639eddf10 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
require "concurrent/map"
-require_relative "../core_ext/array/prepend_and_append"
-require_relative "../core_ext/regexp"
-require_relative "../i18n"
+require "active_support/core_ext/array/prepend_and_append"
+require "active_support/core_ext/regexp"
+require "active_support/i18n"
module ActiveSupport
module Inflector
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 9398ed60a4..72de42b1c3 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../inflections"
-require_relative "../core_ext/regexp"
+require "active_support/inflections"
+require "active_support/core_ext/regexp"
module ActiveSupport
# The Inflector transforms words from singular to plural, class names to table
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index aa7b21734e..9801f1d118 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../core_ext/string/multibyte"
-require_relative "../i18n"
+require "active_support/core_ext/string/multibyte"
+require "active_support/i18n"
module ActiveSupport
module Inflector
diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb
index b5672025fb..d7887175c0 100644
--- a/activesupport/lib/active_support/json.rb
+++ b/activesupport/lib/active_support/json.rb
@@ -1,4 +1,4 @@
# frozen_string_literal: true
-require_relative "json/decoding"
-require_relative "json/encoding"
+require "active_support/json/decoding"
+require "active_support/json/encoding"
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index caa4082dde..8c0e016dc5 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../core_ext/module/attribute_accessors"
-require_relative "../core_ext/module/delegation"
+require "active_support/core_ext/module/attribute_accessors"
+require "active_support/core_ext/module/delegation"
require "json"
module ActiveSupport
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 4016d13364..1339c75ffe 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../core_ext/object/json"
-require_relative "../core_ext/module/delegation"
+require "active_support/core_ext/object/json"
+require "active_support/core_ext/module/delegation"
module ActiveSupport
class << self
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 31de37b400..78f7d7ca8d 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -59,7 +59,7 @@ module ActiveSupport
if secret.blank?
raise ArgumentError, "A secret is required to generate an integrity hash " \
"for cookie session data. Set a secret_key_base of at least " \
- "#{SECRET_MIN_LENGTH} characters in config/secrets.yml."
+ "#{SECRET_MIN_LENGTH} characters in via `bin/rails credentials:edit`."
end
if secret.length < SECRET_MIN_LENGTH
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index 05a11221bf..0f7be06c8e 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "core_ext/module/attribute_accessors"
-require_relative "core_ext/class/attribute"
-require_relative "subscriber"
+require "active_support/core_ext/module/attribute_accessors"
+require "active_support/core_ext/class/attribute"
+require "active_support/subscriber"
module ActiveSupport
# ActiveSupport::LogSubscriber is an object set to consume
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 5b2abfc57c..3f19ef5009 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "../log_subscriber"
-require_relative "../logger"
-require_relative "../notifications"
+require "active_support/log_subscriber"
+require "active_support/logger"
+require "active_support/notifications"
module ActiveSupport
class LogSubscriber
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 3397ac4c9f..8152a182b4 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "logger_silence"
-require_relative "logger_thread_safe_level"
+require "active_support/logger_silence"
+require "active_support/logger_thread_safe_level"
require "logger"
module ActiveSupport
diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb
index 693c7a1947..89f32b6782 100644
--- a/activesupport/lib/active_support/logger_silence.rb
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "concern"
-require_relative "core_ext/module/attribute_accessors"
+require "active_support/concern"
+require "active_support/core_ext/module/attribute_accessors"
require "concurrent"
module LoggerSilence
diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb
index 3c7f53d92c..ba32813d3d 100644
--- a/activesupport/lib/active_support/logger_thread_safe_level.rb
+++ b/activesupport/lib/active_support/logger_thread_safe_level.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "concern"
+require "active_support/concern"
module ActiveSupport
module LoggerThreadSafeLevel # :nodoc:
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 27620f56be..69c95e0622 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -2,9 +2,9 @@
require "openssl"
require "base64"
-require_relative "core_ext/array/extract_options"
-require_relative "message_verifier"
-require_relative "messages/metadata"
+require "active_support/core_ext/array/extract_options"
+require "active_support/message_verifier"
+require "active_support/messages/metadata"
module ActiveSupport
# MessageEncryptor is a simple way to encrypt values which get stored
@@ -54,7 +54,33 @@ module ActiveSupport
#
# Then the messages can be verified and returned upto the expire time.
# Thereafter, verifying returns +nil+.
+ #
+ # === Rotating keys
+ #
+ # MessageEncryptor also supports rotating out old configurations by falling
+ # back to a stack of encryptors. Call `rotate` to build and add an encryptor
+ # so `decrypt_and_verify` will also try the fallback.
+ #
+ # By default any rotated encryptors use the values of the primary
+ # encryptor unless specified otherwise.
+ #
+ # You'd give your encryptor the new defaults:
+ #
+ # crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
+ #
+ # Then gradually rotate the old values out by adding them as fallbacks. Any message
+ # generated with the old values will then work until the rotation is removed.
+ #
+ # crypt.rotate old_secret # Fallback to an old secret instead of @secret.
+ # crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm.
+ #
+ # Though if both the secret and the cipher was changed at the same time,
+ # the above should be combined into:
+ #
+ # crypt.rotate old_secret, cipher: "aes-256-cbc"
class MessageEncryptor
+ prepend Messages::Rotator::Encryptor
+
class << self
attr_accessor :use_authenticated_message_encryption #:nodoc:
@@ -126,7 +152,7 @@ module ActiveSupport
# Decrypt and verify a message. We need to verify the message in order to
# avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/.
- def decrypt_and_verify(data, purpose: nil)
+ def decrypt_and_verify(data, purpose: nil, **)
_decrypt(verifier.verify(data), purpose)
end
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 7110d6d2c9..622b66ee55 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
require "base64"
-require_relative "core_ext/object/blank"
-require_relative "security_utils"
-require_relative "messages/metadata"
+require "active_support/core_ext/object/blank"
+require "active_support/security_utils"
+require "active_support/messages/metadata"
+require "active_support/messages/rotator"
module ActiveSupport
# +MessageVerifier+ makes it easy to generate and verify messages which are
@@ -73,7 +74,33 @@ module ActiveSupport
# Then the messages can be verified and returned upto the expire time.
# Thereafter, the +verified+ method returns +nil+ while +verify+ raises
# <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>.
+ #
+ # === Rotating keys
+ #
+ # MessageVerifier also supports rotating out old configurations by falling
+ # back to a stack of verifiers. Call `rotate` to build and add a verifier to
+ # so either `verified` or `verify` will also try verifying with the fallback.
+ #
+ # By default any rotated verifiers use the values of the primary
+ # verifier unless specified otherwise.
+ #
+ # You'd give your verifier the new defaults:
+ #
+ # verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON)
+ #
+ # Then gradually rotate the old values out by adding them as fallbacks. Any message
+ # generated with the old values will then work until the rotation is removed.
+ #
+ # verifier.rotate old_secret # Fallback to an old secret instead of @secret.
+ # verifier.rotate digest: "SHA256" # Fallback to an old digest instead of SHA512.
+ # verifier.rotate serializer: Marshal # Fallback to an old serializer instead of JSON.
+ #
+ # Though the above would most likely be combined into one rotation:
+ #
+ # verifier.rotate old_secret, digest: "SHA256", serializer: Marshal
class MessageVerifier
+ prepend Messages::Rotator::Verifier
+
class InvalidSignature < StandardError; end
def initialize(secret, options = {})
@@ -120,7 +147,7 @@ module ActiveSupport
#
# incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
# verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
- def verified(signed_message, purpose: nil)
+ def verified(signed_message, purpose: nil, **)
if valid_message?(signed_message)
begin
data = signed_message.split("--".freeze)[0]
@@ -145,8 +172,8 @@ module ActiveSupport
#
# other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
# other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
- def verify(signed_message, purpose: nil)
- verified(signed_message, purpose: purpose) || raise(InvalidSignature)
+ def verify(*args)
+ verified(*args) || raise(InvalidSignature)
end
# Generates a signed message for the provided value.
diff --git a/activesupport/lib/active_support/messages/rotation_configuration.rb b/activesupport/lib/active_support/messages/rotation_configuration.rb
new file mode 100644
index 0000000000..bd50d6d348
--- /dev/null
+++ b/activesupport/lib/active_support/messages/rotation_configuration.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module ActiveSupport
+ module Messages
+ class RotationConfiguration # :nodoc:
+ attr_reader :signed, :encrypted
+
+ def initialize
+ @signed, @encrypted = [], []
+ end
+
+ def rotate(kind, *args)
+ case kind
+ when :signed
+ @signed << args
+ when :encrypted
+ @encrypted << args
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/messages/rotator.rb b/activesupport/lib/active_support/messages/rotator.rb
new file mode 100644
index 0000000000..823a399d67
--- /dev/null
+++ b/activesupport/lib/active_support/messages/rotator.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module ActiveSupport
+ module Messages
+ module Rotator # :nodoc:
+ def initialize(*, **options)
+ super
+
+ @options = options
+ @rotations = []
+ end
+
+ def rotate(*secrets, **options)
+ @rotations << build_rotation(*secrets, @options.merge(options))
+ end
+
+ module Encryptor
+ include Rotator
+
+ def decrypt_and_verify(*args, on_rotation: nil, **options)
+ super
+ rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature
+ run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, options) } || raise
+ end
+
+ private
+ def build_rotation(secret = @secret, sign_secret = @sign_secret, options)
+ self.class.new(secret, sign_secret, options)
+ end
+ end
+
+ module Verifier
+ include Rotator
+
+ def verified(*args, on_rotation: nil, **options)
+ super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, options) }
+ end
+
+ private
+ def build_rotation(secret = @secret, options)
+ self.class.new(secret, options)
+ end
+ end
+
+ private
+ def run_rotations(on_rotation)
+ @rotations.find do |rotation|
+ if message = yield(rotation) rescue next
+ on_rotation.call if on_rotation
+ return message
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index d676827e8e..8152b8fd22 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require_relative "../json"
-require_relative "../core_ext/string/access"
-require_relative "../core_ext/string/behavior"
-require_relative "../core_ext/module/delegation"
-require_relative "../core_ext/regexp"
+require "active_support/json"
+require "active_support/core_ext/string/access"
+require "active_support/core_ext/string/behavior"
+require "active_support/core_ext/module/delegation"
+require "active_support/core_ext/regexp"
module ActiveSupport #:nodoc:
module Multibyte #:nodoc:
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 96a3463905..6207de8094 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "notifications/instrumenter"
-require_relative "notifications/fanout"
-require_relative "per_thread_registry"
+require "active_support/notifications/instrumenter"
+require "active_support/notifications/fanout"
+require "active_support/per_thread_registry"
module ActiveSupport
# = Notifications
diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb
index 5ea9c8f113..06ba797a13 100644
--- a/activesupport/lib/active_support/number_helper/number_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_converter.rb
@@ -1,10 +1,10 @@
# frozen_string_literal: true
-require_relative "../core_ext/big_decimal/conversions"
-require_relative "../core_ext/object/blank"
-require_relative "../core_ext/hash/keys"
-require_relative "../i18n"
-require_relative "../core_ext/class/attribute"
+require "active_support/core_ext/big_decimal/conversions"
+require "active_support/core_ext/object/blank"
+require "active_support/core_ext/hash/keys"
+require "active_support/i18n"
+require "active_support/core_ext/class/attribute"
module ActiveSupport
module NumberHelper
diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
index 1943b9e295..3f037c73ed 100644
--- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../core_ext/numeric/inquiry"
+require "active_support/core_ext/numeric/inquiry"
module ActiveSupport
module NumberHelper
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index 3b62fe6819..b7ad76bb62 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -37,18 +37,6 @@ module ActiveSupport
private
- def digits_and_rounded_number(precision)
- if zero?
- [1, 0]
- else
- digits = digit_count(number)
- multiplier = 10**(digits - precision)
- rounded_number = calculate_rounded_number(multiplier)
- digits = digit_count(rounded_number) # After rounding, the number of digits may have changed
- [digits, rounded_number]
- end
- end
-
def calculate_rounded_number(multiplier)
(number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier
end
diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb
index 42cbbe7c42..ab9ca727f6 100644
--- a/activesupport/lib/active_support/option_merger.rb
+++ b/activesupport/lib/active_support/option_merger.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "core_ext/hash/deep_merge"
+require "active_support/core_ext/hash/deep_merge"
module ActiveSupport
class OptionMerger #:nodoc:
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index fa7825b3ba..c4e419f546 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "core_ext/object/blank"
+require "active_support/core_ext/object/blank"
module ActiveSupport
# Usually key value pairs are handled something like this:
@@ -24,7 +24,7 @@ module ActiveSupport
# To raise an exception when the value is blank, append a
# bang to the key name, like:
#
- # h.dog! # => raises KeyError: key not found: :dog
+ # h.dog! # => raises KeyError: :dog is blank
#
class OrderedOptions < Hash
alias_method :_get, :[] # preserve the original #[] method
@@ -46,7 +46,7 @@ module ActiveSupport
bangs = name_string.chomp!("!")
if bangs
- fetch(name_string.to_sym).presence || raise(KeyError.new("#{name_string} is blank."))
+ self[name_string].presence || raise(KeyError.new(":#{name_string} is blank"))
else
self[name_string]
end
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index dd0cc6a604..eb92fb4371 100644
--- a/activesupport/lib/active_support/per_thread_registry.rb
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "core_ext/module/delegation"
+require "active_support/core_ext/module/delegation"
module ActiveSupport
# NOTE: This approach has been deprecated for end-user code in favor of {thread_mattr_accessor}[rdoc-ref:Module#thread_mattr_accessor] and friends.
diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb
index fe82d1cc14..5c34a0abb3 100644
--- a/activesupport/lib/active_support/rails.rb
+++ b/activesupport/lib/active_support/rails.rb
@@ -11,25 +11,25 @@
# Rails and can change anytime.
# Defines Object#blank? and Object#present?.
-require_relative "core_ext/object/blank"
+require "active_support/core_ext/object/blank"
# Rails own autoload, eager_load, etc.
-require_relative "dependencies/autoload"
+require "active_support/dependencies/autoload"
# Support for ClassMethods and the included macro.
-require_relative "concern"
+require "active_support/concern"
# Defines Class#class_attribute.
-require_relative "core_ext/class/attribute"
+require "active_support/core_ext/class/attribute"
# Defines Module#delegate.
-require_relative "core_ext/module/delegation"
+require "active_support/core_ext/module/delegation"
# Defines ActiveSupport::Deprecation.
-require_relative "deprecation"
+require "active_support/deprecation"
# Defines Regexp#match?.
#
# This should be removed when Rails needs Ruby 2.4 or later, and the require
# added where other Regexp extensions are being used (easy to grep).
-require_relative "core_ext/regexp"
+require "active_support/core_ext/regexp"
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 2efe391d01..8560eae110 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support"
-require_relative "i18n_railtie"
+require "active_support/i18n_railtie"
module ActiveSupport
class Railtie < Rails::Railtie # :nodoc:
@@ -36,19 +36,30 @@ module ActiveSupport
rescue TZInfo::DataSourceNotFound => e
raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install"
end
- require_relative "core_ext/time/zones"
+ require "active_support/core_ext/time/zones"
Time.zone_default = Time.find_zone!(app.config.time_zone)
end
# Sets the default week start
# If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised.
initializer "active_support.initialize_beginning_of_week" do |app|
- require_relative "core_ext/date/calculations"
+ require "active_support/core_ext/date/calculations"
beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week)
Date.beginning_of_week_default = beginning_of_week_default
end
+ initializer "active_support.require_master_key" do |app|
+ if app.config.respond_to?(:require_master_key) && app.config.require_master_key
+ begin
+ app.credentials.key
+ rescue ActiveSupport::EncryptedFile::MissingKeyError => error
+ $stderr.puts error.message
+ exit 1
+ end
+ end
+ end
+
initializer "active_support.set_configs" do |app|
app.config.active_support.each do |k, v|
k = "#{k}="
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
index 44062e3491..6cd11b1a26 100644
--- a/activesupport/lib/active_support/reloader.rb
+++ b/activesupport/lib/active_support/reloader.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "execution_wrapper"
+require "active_support/execution_wrapper"
module ActiveSupport
#--
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index 2f6aeef48a..e0fa29cacb 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "concern"
-require_relative "core_ext/class/attribute"
-require_relative "core_ext/string/inflections"
+require "active_support/concern"
+require "active_support/core_ext/class/attribute"
+require "active_support/core_ext/string/inflections"
module ActiveSupport
# Rescuable module adds support for easier exception handling.
diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
index 51870559ec..b6b31ef140 100644
--- a/activesupport/lib/active_support/security_utils.rb
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "digest"
+require "digest/sha2"
module ActiveSupport
module SecurityUtils
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index 7913bb815e..d6dd5474d0 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "per_thread_registry"
-require_relative "notifications"
+require "active_support/per_thread_registry"
+require "active_support/notifications"
module ActiveSupport
# ActiveSupport::Subscriber is an object set to consume
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
index fe13eaed4e..8561cba9f1 100644
--- a/activesupport/lib/active_support/tagged_logging.rb
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require_relative "core_ext/module/delegation"
-require_relative "core_ext/object/blank"
+require "active_support/core_ext/module/delegation"
+require "active_support/core_ext/object/blank"
require "logger"
-require_relative "logger"
+require "active_support/logger"
module ActiveSupport
# Wraps any standard Logger object to provide tagging capabilities.
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index bc42758f76..ee5286bd55 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -2,16 +2,16 @@
gem "minitest" # make sure we get the gem, not stdlib
require "minitest"
-require_relative "testing/tagged_logging"
-require_relative "testing/setup_and_teardown"
-require_relative "testing/assertions"
-require_relative "testing/deprecation"
-require_relative "testing/declarative"
-require_relative "testing/isolation"
-require_relative "testing/constant_lookup"
-require_relative "testing/time_helpers"
-require_relative "testing/file_fixtures"
-require_relative "core_ext/kernel/reporting"
+require "active_support/testing/tagged_logging"
+require "active_support/testing/setup_and_teardown"
+require "active_support/testing/assertions"
+require "active_support/testing/deprecation"
+require "active_support/testing/declarative"
+require "active_support/testing/isolation"
+require "active_support/testing/constant_lookup"
+require "active_support/testing/time_helpers"
+require "active_support/testing/file_fixtures"
+require "active_support/core_ext/kernel/reporting"
module ActiveSupport
class TestCase < ::Minitest::Test
diff --git a/activesupport/lib/active_support/testing/constant_lookup.rb b/activesupport/lib/active_support/testing/constant_lookup.rb
index 0fedd486fb..51167e9237 100644
--- a/activesupport/lib/active_support/testing/constant_lookup.rb
+++ b/activesupport/lib/active_support/testing/constant_lookup.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../concern"
-require_relative "../inflector"
+require "active_support/concern"
+require "active_support/inflector"
module ActiveSupport
module Testing
diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb
index 4fda4832cc..f655435729 100644
--- a/activesupport/lib/active_support/testing/deprecation.rb
+++ b/activesupport/lib/active_support/testing/deprecation.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../deprecation"
-require_relative "../core_ext/regexp"
+require "active_support/deprecation"
+require "active_support/core_ext/regexp"
module ActiveSupport
module Testing
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index 18de7185d9..1dbf3c5da0 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../concern"
-require_relative "../callbacks"
+require "active_support/concern"
+require "active_support/callbacks"
module ActiveSupport
module Testing
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index fa5f46736c..8c620e7f8c 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../core_ext/string/strip" # for strip_heredoc
-require_relative "../core_ext/time/calculations"
+require "active_support/core_ext/string/strip" # for strip_heredoc
+require "active_support/core_ext/time/calculations"
require "concurrent/map"
module ActiveSupport
diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb
index fb1c5cee1c..51854675bf 100644
--- a/activesupport/lib/active_support/time.rb
+++ b/activesupport/lib/active_support/time.rb
@@ -9,12 +9,12 @@ end
require "date"
require "time"
-require_relative "core_ext/time"
-require_relative "core_ext/date"
-require_relative "core_ext/date_time"
+require "active_support/core_ext/time"
+require "active_support/core_ext/date"
+require "active_support/core_ext/date_time"
-require_relative "core_ext/integer/time"
-require_relative "core_ext/numeric/time"
+require "active_support/core_ext/integer/time"
+require "active_support/core_ext/numeric/time"
-require_relative "core_ext/string/conversions"
-require_relative "core_ext/string/zones"
+require "active_support/core_ext/string/conversions"
+require "active_support/core_ext/string/zones"
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 59cafa193e..20650ce714 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require_relative "duration"
-require_relative "values/time_zone"
-require_relative "core_ext/object/acts_like"
-require_relative "core_ext/date_and_time/compatibility"
+require "active_support/duration"
+require "active_support/values/time_zone"
+require "active_support/core_ext/object/acts_like"
+require "active_support/core_ext/date_and_time/compatibility"
module ActiveSupport
# A Time-like class that can represent a time in any time zone. Necessary
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index c871f11422..4d1fbd4453 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -2,7 +2,7 @@
require "tzinfo"
require "concurrent/map"
-require_relative "../core_ext/object/blank"
+require "active_support/core_ext/object/blank"
module ActiveSupport
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index d49ee4508e..ee2048cb54 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -3,9 +3,9 @@
require "time"
require "base64"
require "bigdecimal"
-require_relative "core_ext/module/delegation"
-require_relative "core_ext/string/inflections"
-require_relative "core_ext/date_time/calculations"
+require "active_support/core_ext/module/delegation"
+require "active_support/core_ext/string/inflections"
+require "active_support/core_ext/date_time/calculations"
module ActiveSupport
# = XmlMini
@@ -199,7 +199,7 @@ module ActiveSupport
if name.is_a?(Module)
name
else
- require_relative "xml_mini/#{name.downcase}"
+ require "active_support/xml_mini/#{name.downcase}"
ActiveSupport.const_get("XmlMini_#{name}")
end
end
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index ecfe389f10..7f94a64016 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -5,7 +5,7 @@ raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFO
require "jruby"
include Java
-require_relative "../core_ext/object/blank"
+require "active_support/core_ext/object/blank"
java_import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder
java_import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index b1be38fadf..0b000fea60 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "libxml"
-require_relative "../core_ext/object/blank"
+require "active_support/core_ext/object/blank"
require "stringio"
module ActiveSupport
diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
index 3ad494310d..dcf16e6084 100644
--- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb
+++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "libxml"
-require_relative "../core_ext/object/blank"
+require "active_support/core_ext/object/blank"
require "stringio"
module ActiveSupport
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index 1987330c25..5ee6fc8159 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -6,7 +6,7 @@ rescue LoadError => e
$stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end
-require_relative "../core_ext/object/blank"
+require "active_support/core_ext/object/blank"
require "stringio"
module ActiveSupport
diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
index fb57eb8867..b01ed00a14 100644
--- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
@@ -6,7 +6,7 @@ rescue LoadError => e
$stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end
-require_relative "../core_ext/object/blank"
+require "active_support/core_ext/object/blank"
require "stringio"
module ActiveSupport
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 5fd8978fc0..32458d5b0d 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../core_ext/kernel/reporting"
-require_relative "../core_ext/object/blank"
+require "active_support/core_ext/kernel/reporting"
+require "active_support/core_ext/object/blank"
require "stringio"
module ActiveSupport
diff --git a/activesupport/test/cache/stores/file_store_test.rb b/activesupport/test/cache/stores/file_store_test.rb
index 391ab60b3a..66231b0a82 100644
--- a/activesupport/test/cache/stores/file_store_test.rb
+++ b/activesupport/test/cache/stores/file_store_test.rb
@@ -118,6 +118,7 @@ class FileStoreTest < ActiveSupport::TestCase
assert_not @cache.exist?("foo")
assert @cache.exist?("baz")
assert @cache.exist?("quux")
+ assert_equal 2, Dir.glob(File.join(cache_dir, "**")).size
end
end
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 3902e41a60..5f894db46f 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -193,13 +193,6 @@ module CallbacksTest
before_save Proc.new { |r| r.history << "b00m" }, if: :no
before_save Proc.new { |r| r.history << [:before_save, :symbol] }, unless: :no
before_save Proc.new { |r| r.history << "b00m" }, unless: :yes
- # string
- ActiveSupport::Deprecation.silence do
- before_save Proc.new { |r| r.history << [:before_save, :string] }, if: "yes"
- before_save Proc.new { |r| r.history << "b00m" }, if: "no"
- before_save Proc.new { |r| r.history << [:before_save, :string] }, unless: "no"
- before_save Proc.new { |r| r.history << "b00m" }, unless: "yes"
- end
# Combined if and unless
before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, if: :yes, unless: :no
before_save Proc.new { |r| r.history << "b00m" }, if: :yes, unless: :yes
@@ -592,8 +585,6 @@ module CallbacksTest
[:before_save, :proc],
[:before_save, :symbol],
[:before_save, :symbol],
- [:before_save, :string],
- [:before_save, :string],
[:before_save, :combined_symbol],
], person.history
end
@@ -1182,14 +1173,15 @@ module CallbacksTest
end
end
- class DeprecatedWarningTest < ActiveSupport::TestCase
- def test_deprecate_string_conditional_options
+ class NotSupportedStringConditionalTest < ActiveSupport::TestCase
+ def test_string_conditional_options
klass = Class.new(Record)
- assert_deprecated { klass.before_save :tweedle, if: "true" }
- assert_deprecated { klass.after_save :tweedle, unless: "false" }
- assert_deprecated { klass.skip_callback :save, :before, :tweedle, if: "true" }
- assert_deprecated { klass.skip_callback :save, :after, :tweedle, unless: "false" }
+ assert_raises(ArgumentError) { klass.before_save :tweedle, if: ["true"] }
+ assert_raises(ArgumentError) { klass.before_save :tweedle, if: "true" }
+ assert_raises(ArgumentError) { klass.after_save :tweedle, unless: "false" }
+ assert_raises(ArgumentError) { klass.skip_callback :save, :before, :tweedle, if: "true" }
+ assert_raises(ArgumentError) { klass.skip_callback :save, :after, :tweedle, unless: "false" }
end
end
diff --git a/activesupport/test/core_ext/module/reachable_test.rb b/activesupport/test/core_ext/module/reachable_test.rb
index a69fc6839e..097a72fa5b 100644
--- a/activesupport/test/core_ext/module/reachable_test.rb
+++ b/activesupport/test/core_ext/module/reachable_test.rb
@@ -5,13 +5,17 @@ require "active_support/core_ext/module/reachable"
class AnonymousTest < ActiveSupport::TestCase
test "an anonymous class or module is not reachable" do
- assert !Module.new.reachable?
- assert !Class.new.reachable?
+ assert_deprecated do
+ assert !Module.new.reachable?
+ assert !Class.new.reachable?
+ end
end
test "ordinary named classes or modules are reachable" do
- assert Kernel.reachable?
- assert Object.reachable?
+ assert_deprecated do
+ assert Kernel.reachable?
+ assert Object.reachable?
+ end
end
test "a named class or module whose constant has gone is not reachable" do
@@ -21,8 +25,10 @@ class AnonymousTest < ActiveSupport::TestCase
self.class.send(:remove_const, :C)
self.class.send(:remove_const, :M)
- assert !c.reachable?
- assert !m.reachable?
+ assert_deprecated do
+ assert !c.reachable?
+ assert !m.reachable?
+ end
end
test "a named class or module whose constants store different objects are not reachable" do
@@ -35,9 +41,11 @@ class AnonymousTest < ActiveSupport::TestCase
eval "class C; end"
eval "module M; end"
- assert C.reachable?
- assert M.reachable?
- assert !c.reachable?
- assert !m.reachable?
+ assert_deprecated do
+ assert C.reachable?
+ assert M.reachable?
+ assert !c.reachable?
+ assert !m.reachable?
+ end
end
end
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index a96e3d62e8..0467123e55 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -16,6 +16,11 @@ class RangeTest < ActiveSupport::TestCase
assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db)
end
+ def test_to_s_with_alphabets
+ alphabet_range = ("a".."z")
+ assert_equal "BETWEEN 'a' AND 'z'", alphabet_range.to_s(:db)
+ end
+
def test_to_s_with_numeric
number_range = (1..100)
assert_equal "BETWEEN '1' AND '100'", number_range.to_s(:db)
diff --git a/activesupport/test/encrypted_configuration_test.rb b/activesupport/test/encrypted_configuration_test.rb
new file mode 100644
index 0000000000..471faa8c12
--- /dev/null
+++ b/activesupport/test/encrypted_configuration_test.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/encrypted_configuration"
+
+class EncryptedConfigurationTest < ActiveSupport::TestCase
+ setup do
+ @credentials_config_path = File.join(Dir.tmpdir, "credentials.yml.enc")
+
+ @credentials_key_path = File.join(Dir.tmpdir, "master.key")
+ File.write(@credentials_key_path, ActiveSupport::EncryptedConfiguration.generate_key)
+
+ @credentials = ActiveSupport::EncryptedConfiguration.new \
+ config_path: @credentials_config_path, key_path: @credentials_key_path, env_key: "RAILS_MASTER_KEY"
+ end
+
+ teardown do
+ FileUtils.rm_rf @credentials_config_path
+ FileUtils.rm_rf @credentials_key_path
+ end
+
+ test "reading configuration by env key" do
+ FileUtils.rm_rf @credentials_key_path
+
+ begin
+ ENV["RAILS_MASTER_KEY"] = ActiveSupport::EncryptedConfiguration.generate_key
+ @credentials.write({ something: { good: true, bad: false } }.to_yaml)
+
+ assert @credentials[:something][:good]
+ assert_not @credentials.dig(:something, :bad)
+ assert_nil @credentials.fetch(:nothing, nil)
+ ensure
+ ENV["RAILS_MASTER_KEY"] = nil
+ end
+ end
+
+ test "reading configuration by key file" do
+ @credentials.write({ something: { good: true } }.to_yaml)
+
+ assert @credentials.something[:good]
+ end
+
+ test "change configuration by key file" do
+ @credentials.write({ something: { good: true } }.to_yaml)
+ @credentials.change do |config_file|
+ config = YAML.load(config_file.read)
+ config_file.write config.merge(new: "things").to_yaml
+ end
+
+ assert @credentials.something[:good]
+ assert_equal "things", @credentials[:new]
+ end
+
+ test "raises key error when accessing config via bang method" do
+ assert_raise(KeyError) { @credentials.something! }
+ end
+end
diff --git a/activesupport/test/encrypted_file_test.rb b/activesupport/test/encrypted_file_test.rb
new file mode 100644
index 0000000000..7259726d08
--- /dev/null
+++ b/activesupport/test/encrypted_file_test.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/encrypted_file"
+
+class EncryptedFileTest < ActiveSupport::TestCase
+ setup do
+ @content = "One little fox jumped over the hedge"
+
+ @content_path = File.join(Dir.tmpdir, "content.txt.enc")
+
+ @key_path = File.join(Dir.tmpdir, "content.txt.key")
+ File.write(@key_path, ActiveSupport::EncryptedFile.generate_key)
+
+ @encrypted_file = ActiveSupport::EncryptedFile.new \
+ content_path: @content_path, key_path: @key_path, env_key: "CONTENT_KEY"
+ end
+
+ teardown do
+ FileUtils.rm_rf @content_path
+ FileUtils.rm_rf @key_path
+ end
+
+ test "reading content by env key" do
+ FileUtils.rm_rf @key_path
+
+ begin
+ ENV["CONTENT_KEY"] = ActiveSupport::EncryptedFile.generate_key
+ @encrypted_file.write @content
+
+ assert_equal @content, @encrypted_file.read
+ ensure
+ ENV["CONTENT_KEY"] = nil
+ end
+ end
+
+ test "reading content by key file" do
+ @encrypted_file.write(@content)
+ assert_equal @content, @encrypted_file.read
+ end
+
+ test "change content by key file" do
+ @encrypted_file.write(@content)
+ @encrypted_file.change do |file|
+ file.write(file.read + " and went by the lake")
+ end
+
+ assert_equal "#{@content} and went by the lake", @encrypted_file.read
+ end
+end
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
index b3788ee65c..41d653fa59 100644
--- a/activesupport/test/hash_with_indifferent_access_test.rb
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -399,6 +399,36 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
end
+ def test_indifferent_transform_keys
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).transform_keys { |k| k * 2 }
+
+ assert_equal({ "aa" => 1, "bb" => 2 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_transform_keys_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.transform_keys! { |k| k * 2 }
+
+ assert_equal({ "aa" => 1, "bb" => 2 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_transform_values
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).transform_values { |v| v * 2 }
+
+ assert_equal({ "a" => 2, "b" => 4 }, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_transform_values_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.transform_values! { |v| v * 2 }
+
+ assert_equal({ "a" => 2, "b" => 4 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
def test_indifferent_compact
hash_contain_nil_value = @strings.merge("z" => nil)
hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index eafa2e1712..96ad8dfbdb 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -454,6 +454,10 @@ EXPECTED
assert_equal '{"number":null}', NaNNumber.new.to_json
end
+ def test_to_json_works_on_io_objects
+ assert_equal STDOUT.to_s.to_json, STDOUT.to_json
+ end
+
private
def object_keys(json_object)
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 1fbe655642..9edf07f762 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -115,6 +115,72 @@ class MessageEncryptorTest < ActiveSupport::TestCase
assert_equal "Ruby on Rails", encryptor.decrypt_and_verify(encrypted_message)
end
+ def test_rotating_secret
+ old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm").encrypt_and_sign("old")
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
+ encryptor.rotate secrets[:old]
+
+ assert_equal "old", encryptor.decrypt_and_verify(old_message)
+ end
+
+ def test_rotating_serializer
+ old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm", serializer: JSON).
+ encrypt_and_sign(ahoy: :hoy)
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm", serializer: JSON)
+ encryptor.rotate secrets[:old]
+
+ assert_equal({ "ahoy" => "hoy" }, encryptor.decrypt_and_verify(old_message))
+ end
+
+ def test_rotating_aes_cbc_secrets
+ old_encryptor = ActiveSupport::MessageEncryptor.new(secrets[:old], "old sign", cipher: "aes-256-cbc")
+ old_message = old_encryptor.encrypt_and_sign("old")
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret)
+ encryptor.rotate secrets[:old], "old sign", cipher: "aes-256-cbc"
+
+ assert_equal "old", encryptor.decrypt_and_verify(old_message)
+ end
+
+ def test_multiple_rotations
+ older_message = ActiveSupport::MessageEncryptor.new(secrets[:older], "older sign").encrypt_and_sign("older")
+ old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], "old sign").encrypt_and_sign("old")
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret)
+ encryptor.rotate secrets[:old], "old sign"
+ encryptor.rotate secrets[:older], "older sign"
+
+ assert_equal "new", encryptor.decrypt_and_verify(encryptor.encrypt_and_sign("new"))
+ assert_equal "old", encryptor.decrypt_and_verify(old_message)
+ assert_equal "older", encryptor.decrypt_and_verify(older_message)
+ end
+
+ def test_on_rotation_is_called_and_returns_modified_messages
+ older_message = ActiveSupport::MessageEncryptor.new(secrets[:older], "older sign").encrypt_and_sign(encoded: "message")
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret)
+ encryptor.rotate secrets[:old]
+ encryptor.rotate secrets[:older], "older sign"
+
+ rotated = false
+ message = encryptor.decrypt_and_verify(older_message, on_rotation: proc { rotated = true })
+
+ assert_equal({ encoded: "message" }, message)
+ assert rotated
+ end
+
+ def test_with_rotated_metadata
+ old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm").
+ encrypt_and_sign("metadata", purpose: :rotation)
+
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm")
+ encryptor.rotate secrets[:old]
+
+ assert_equal "metadata", encryptor.decrypt_and_verify(old_message, purpose: :rotation)
+ end
+
private
def assert_aead_not_decrypted(encryptor, value)
assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do
@@ -134,6 +200,10 @@ class MessageEncryptorTest < ActiveSupport::TestCase
end
end
+ def secrets
+ @secrets ||= Hash.new { |h, k| h[k] = SecureRandom.random_bytes(32) }
+ end
+
def munge(base64_string)
bits = ::Base64.strict_decode64(base64_string)
bits.reverse!
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index fbeafca203..05d5c1cbc3 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -20,6 +20,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
def setup
@verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!")
@data = { some: "data", now: Time.utc(2010) }
+ @secret = SecureRandom.random_bytes(32)
end
def test_valid_message
@@ -90,6 +91,51 @@ class MessageVerifierTest < ActiveSupport::TestCase
signed_message = "BAh7BzoJc29tZUkiCWRhdGEGOgZFVDoIbm93SXU6CVRpbWUNIIAbgAAAAAAHOgtvZmZzZXRpADoJem9uZUkiCFVUQwY7BkY=--d03c52c91dfe4ccc5159417c660461bcce005e96"
assert_equal @data, @verifier.verify(signed_message)
end
+
+ def test_rotating_secret
+ old_message = ActiveSupport::MessageVerifier.new("old", digest: "SHA1").generate("old")
+
+ verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA1")
+ verifier.rotate "old"
+
+ assert_equal "old", verifier.verified(old_message)
+ end
+
+ def test_multiple_rotations
+ old_message = ActiveSupport::MessageVerifier.new("old", digest: "SHA256").generate("old")
+ older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate("older")
+
+ verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512")
+ verifier.rotate "old", digest: "SHA256"
+ verifier.rotate "older", digest: "SHA1"
+
+ assert_equal "new", verifier.verified(verifier.generate("new"))
+ assert_equal "old", verifier.verified(old_message)
+ assert_equal "older", verifier.verified(older_message)
+ end
+
+ def test_on_rotation_is_called_and_verified_returns_message
+ older_message = ActiveSupport::MessageVerifier.new("older", digest: "SHA1").generate(encoded: "message")
+
+ verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512")
+ verifier.rotate "old", digest: "SHA256"
+ verifier.rotate "older", digest: "SHA1"
+
+ rotated = false
+ message = verifier.verified(older_message, on_rotation: proc { rotated = true })
+
+ assert_equal({ encoded: "message" }, message)
+ assert rotated
+ end
+
+ def test_rotations_with_metadata
+ old_message = ActiveSupport::MessageVerifier.new("old").generate("old", purpose: :rotation)
+
+ verifier = ActiveSupport::MessageVerifier.new(@secret)
+ verifier.rotate "old"
+
+ assert_equal "old", verifier.verified(old_message, purpose: :rotation)
+ end
end
class MessageVerifierMetadataTest < ActiveSupport::TestCase
diff --git a/activesupport/test/messages/rotation_configuration_test.rb b/activesupport/test/messages/rotation_configuration_test.rb
new file mode 100644
index 0000000000..2f6824ed21
--- /dev/null
+++ b/activesupport/test/messages/rotation_configuration_test.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/messages/rotation_configuration"
+
+class MessagesRotationConfiguration < ActiveSupport::TestCase
+ def setup
+ @config = ActiveSupport::Messages::RotationConfiguration.new
+ end
+
+ def test_signed_configurations
+ @config.rotate :signed, "older secret", salt: "salt", digest: "SHA1"
+ @config.rotate :signed, "old secret", salt: "salt", digest: "SHA256"
+
+ assert_equal [
+ [ "older secret", salt: "salt", digest: "SHA1" ],
+ [ "old secret", salt: "salt", digest: "SHA256" ] ], @config.signed
+ end
+
+ def test_encrypted_configurations
+ @config.rotate :encrypted, "old raw key", cipher: "aes-256-gcm"
+
+ assert_equal [ [ "old raw key", cipher: "aes-256-gcm" ] ], @config.encrypted
+ end
+end
diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index 7f2e774c02..2c67bb02ac 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -102,4 +102,17 @@ class OrderedOptionsTest < ActiveSupport::TestCase
end
assert_raises(KeyError) { a.non_existing_key! }
end
+
+ def test_inheritable_options_with_bang
+ a = ActiveSupport::InheritableOptions.new(foo: :bar)
+
+ assert_nothing_raised { a.foo! }
+ assert_equal a.foo, a.foo!
+
+ assert_raises(KeyError) do
+ a.foo = nil
+ a.foo!
+ end
+ assert_raises(KeyError) { a.non_existing_key! }
+ end
end
diff --git a/ci/phantomjs.js b/ci/phantomjs.js
deleted file mode 100644
index 7a33fb14a3..0000000000
--- a/ci/phantomjs.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * PhantomJS Runner QUnit Plugin 1.2.0
- *
- * PhantomJS binaries: http://phantomjs.org/download.html
- * Requires PhantomJS 1.6+ (1.7+ recommended)
- *
- * Run with:
- * phantomjs runner.js [url-of-your-qunit-testsuite]
- *
- * e.g.
- * phantomjs runner.js http://localhost/qunit/test/index.html
- */
-
-/*global phantom:false, require:false, console:false, window:false, QUnit:false */
-
-(function() {
- 'use strict';
-
- var url, page, timeout,
- args = require('system').args;
-
- // arg[0]: scriptName, args[1...]: arguments
- if (args.length < 2 || args.length > 3) {
- console.error('Usage:\n phantomjs runner.js [url-of-your-qunit-testsuite] [timeout-in-seconds]');
- phantom.exit(1);
- }
-
- url = args[1];
- page = require('webpage').create();
- if (args[2] !== undefined) {
- timeout = parseInt(args[2], 10);
- }
-
- // Route `console.log()` calls from within the Page context to the main Phantom context (i.e. current `this`)
- page.onConsoleMessage = function(msg) {
- console.log(msg);
- };
-
- page.onInitialized = function() {
- page.evaluate(addLogging);
- };
-
- page.onCallback = function(message) {
- var result,
- failed;
-
- if (message) {
- if (message.name === 'QUnit.done') {
- result = message.data;
- failed = !result || !result.total || result.failed;
-
- if (!result.total) {
- console.error('No tests were executed. Are you loading tests asynchronously?');
- }
-
- phantom.exit(failed ? 1 : 0);
- }
- }
- };
-
- page.open(url, function(status) {
- if (status !== 'success') {
- console.error('Unable to access network: ' + status);
- phantom.exit(1);
- } else {
- // Cannot do this verification with the 'DOMContentLoaded' handler because it
- // will be too late to attach it if a page does not have any script tags.
- var qunitMissing = page.evaluate(function() { return (typeof QUnit === 'undefined' || !QUnit); });
- if (qunitMissing) {
- console.error('The `QUnit` object is not present on this page.');
- phantom.exit(1);
- }
-
- // Set a timeout on the test running, otherwise tests with async problems will hang forever
- if (typeof timeout === 'number') {
- setTimeout(function() {
- console.error('The specified timeout of ' + timeout + ' seconds has expired. Aborting...');
- phantom.exit(1);
- }, timeout * 1000);
- }
-
- // Do nothing... the callback mechanism will handle everything!
- }
- });
-
- function addLogging() {
- window.document.addEventListener('DOMContentLoaded', function() {
- var currentTestAssertions = [];
-
- QUnit.log(function(details) {
- var response;
-
- // Ignore passing assertions
- if (details.result) {
- return;
- }
-
- response = details.message || '';
-
- if (typeof details.expected !== 'undefined') {
- if (response) {
- response += ', ';
- }
-
- response += 'expected: ' + details.expected + ', but was: ' + details.actual;
- }
-
- if (details.source) {
- response += "\n" + details.source;
- }
-
- currentTestAssertions.push('Failed assertion: ' + response);
- });
-
- QUnit.testDone(function(result) {
- var i,
- len,
- name = '';
-
- if (result.module) {
- name += result.module + ': ';
- }
- name += result.name;
-
- if (result.failed) {
- console.log('\n' + 'Test failed: ' + name);
-
- for (i = 0, len = currentTestAssertions.length; i < len; i++) {
- console.log(' ' + currentTestAssertions[i]);
- }
- }
-
- currentTestAssertions.length = 0;
- });
-
- QUnit.done(function(result) {
- console.log('\n' + 'Took ' + result.runtime + 'ms to run ' + result.total + ' tests. ' + result.passed + ' passed, ' + result.failed + ' failed.');
-
- if (typeof window.callPhantom === 'function') {
- window.callPhantom({
- 'name': 'QUnit.done',
- 'data': result
- });
- }
- });
- }, false);
- }
-})();
-
diff --git a/ci/qunit-selenium-runner.rb b/ci/qunit-selenium-runner.rb
new file mode 100644
index 0000000000..9b856e2a41
--- /dev/null
+++ b/ci/qunit-selenium-runner.rb
@@ -0,0 +1,13 @@
+require 'qunit/selenium/test_runner'
+require 'chromedriver/helper'
+
+driver_options = Selenium::WebDriver::Chrome::Options.new
+driver_options.add_argument('--headless')
+driver_options.add_argument('--disable-gpu')
+
+driver = ::Selenium::WebDriver.for(:chrome, options: driver_options)
+result = QUnit::Selenium::TestRunner.new(driver).open(ARGV[0], timeout: 60)
+driver.quit
+
+puts "Time: #{result.duration} seconds, Total: #{result.assertions[:total]}, Passed: #{result.assertions[:passed]}, Failed: #{result.assertions[:failed]}"
+exit(result.tests[:failed] > 0 ? 1 : 0)
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 4d8d8db3e5..557b1d7bef 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
# Activate the gem you are reporting the issue against.
gem "rails", "5.1.0"
end
@@ -19,7 +22,6 @@ require "action_controller/railtie"
class TestApp < Rails::Application
config.root = __dir__
config.session_store :cookie_store, key: "cookie_store_key"
- secrets.secret_token = "secret_token"
secrets.secret_key_base = "secret_key_base"
config.logger = Logger.new($stdout)
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 1f862e07da..cf76de80d2 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
end
@@ -17,7 +20,6 @@ require "action_controller/railtie"
class TestApp < Rails::Application
config.root = __dir__
- secrets.secret_token = "secret_token"
secrets.secret_key_base = "secret_key_base"
config.logger = Logger.new($stdout)
diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb
index af777a86ef..013d1f8602 100644
--- a/guides/bug_report_templates/active_job_gem.rb
+++ b/guides/bug_report_templates/active_job_gem.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
# Activate the gem you are reporting the issue against.
gem "activejob", "5.1.0"
end
diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb
index 39fb3f60a6..ce480cbb52 100644
--- a/guides/bug_report_templates/active_job_master.rb
+++ b/guides/bug_report_templates/active_job_master.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
end
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index 168e2dcc66..921917fbe9 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
# Activate the gem you are reporting the issue against.
gem "activerecord", "5.1.0"
gem "sqlite3"
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index cbd2cff2b8..78411e2d57 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
gem "sqlite3"
diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb
index b931ed0beb..9002b8f428 100644
--- a/guides/bug_report_templates/active_record_migrations_gem.rb
+++ b/guides/bug_report_templates/active_record_migrations_gem.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
# Activate the gem you are reporting the issue against.
gem "activerecord", "5.1.0"
gem "sqlite3"
@@ -34,7 +37,7 @@ end
class Payment < ActiveRecord::Base
end
-class ChangeAmountToAddScale < ActiveRecord::Migration[5.0]
+class ChangeAmountToAddScale < ActiveRecord::Migration[5.1]
def change
reversible do |dir|
dir.up do
diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb
index 2c009c0563..fce8d1d848 100644
--- a/guides/bug_report_templates/active_record_migrations_master.rb
+++ b/guides/bug_report_templates/active_record_migrations_master.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
gem "sqlite3"
@@ -34,7 +37,7 @@ end
class Payment < ActiveRecord::Base
end
-class ChangeAmountToAddScale < ActiveRecord::Migration[5.0]
+class ChangeAmountToAddScale < ActiveRecord::Migration[5.2]
def change
reversible do |dir|
dir.up do
diff --git a/guides/bug_report_templates/benchmark.rb b/guides/bug_report_templates/benchmark.rb
index d0f5a634bc..fb51273e3e 100644
--- a/guides/bug_report_templates/benchmark.rb
+++ b/guides/bug_report_templates/benchmark.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
gem "benchmark-ips"
diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb
index c990bda005..60e8322c2a 100644
--- a/guides/bug_report_templates/generic_gem.rb
+++ b/guides/bug_report_templates/generic_gem.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
# Activate the gem you are reporting the issue against.
gem "activesupport", "5.1.0"
end
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
index 1a9b99b624..384c8b1833 100644
--- a/guides/bug_report_templates/generic_master.rb
+++ b/guides/bug_report_templates/generic_master.rb
@@ -9,6 +9,9 @@ end
gemfile(true) do
source "https://rubygems.org"
+
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
+
gem "rails", github: "rails/rails"
gem "arel", github: "rails/arel"
end
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 3f5a3c7ade..1020f4a8e7 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -231,7 +231,7 @@ Rails chooses between file, template, and action depending on whether there is a
### Application Controller Renamed
-If you're one of the people who has always been bothered by the special-case naming of `application.rb`, rejoice! It's been reworked to be application_controller.rb in Rails 2.3. In addition, there's a new rake task, `rake rails:update:application_controller` to do this automatically for you - and it will be run as part of the normal `rake rails:update` process.
+If you're one of the people who has always been bothered by the special-case naming of `application.rb`, rejoice! It's been reworked to be `application_controller.rb` in Rails 2.3. In addition, there's a new rake task, `rake rails:update:application_controller` to do this automatically for you - and it will be run as part of the normal `rake rails:update` process.
* More Information:
* [The Death of Application.rb](http://afreshcup.com/2008/11/17/rails-2x-the-death-of-applicationrb/)
@@ -304,7 +304,7 @@ Rails now keeps a per-request local cache of read from the remote cache stores,
Rails can now provide localized views, depending on the locale that you have set. For example, suppose you have a `Posts` controller with a `show` action. By default, this will render `app/views/posts/show.html.erb`. But if you set `I18n.locale = :da`, it will render `app/views/posts/show.da.html.erb`. If the localized template isn't present, the undecorated version will be used. Rails also includes `I18n#available_locales` and `I18n::SimpleBackend#available_locales`, which return an array of the translations that are available in the current Rails project.
-In addition, you can use the same scheme to localize the rescue files in the `public` directory: `public/500.da.html` or `public/404.en.html` work, for example.
+In addition, you can use the same scheme to localize the rescue files in the public directory: `public/500.da.html` or `public/404.en.html` work, for example.
### Partial Scoping for Translations
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index 6570b19f97..f6571544f9 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -30,7 +30,7 @@ TIP: Note that Ruby 1.8.7 p248 and p249 have marshalling bugs that crash Rails.
### What to update in your apps
-* Update your Gemfile to depend on
+* Update your `Gemfile` to depend on
* `rails = 3.2.0`
* `sass-rails ~> 3.2.3`
* `coffee-rails ~> 3.2.1`
diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md
index 6f1b75a42b..0921cd1979 100644
--- a/guides/source/4_0_release_notes.md
+++ b/guides/source/4_0_release_notes.md
@@ -66,7 +66,7 @@ Major Features
* **ActiveRecord session store** ([commit](https://github.com/rails/rails/commit/0ffe19056c8e8b2f9ae9d487b896cad2ce9387ad)) - The ActiveRecord session store is extracted to a separate gem. Storing sessions in SQL is costly. Instead, use cookie sessions, memcache sessions, or a custom session store.
* **ActiveModel mass assignment protection** ([commit](https://github.com/rails/rails/commit/f8c9a4d3e88181cee644f91e1342bfe896ca64c6)) - Rails 3 mass assignment protection is deprecated. Instead, use strong parameters.
* **ActiveResource** ([commit](https://github.com/rails/rails/commit/f1637bf2bb00490203503fbd943b73406e043d1d)) - ActiveResource is extracted to a separate gem. ActiveResource was not widely used.
-* **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a Gemfile to manage installed gems.
+* **vendor/plugins removed** ([commit](https://github.com/rails/rails/commit/853de2bd9ac572735fa6cf59fcf827e485a231c3)) - Use a `Gemfile` to manage installed gems.
### ActionPack
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index a30bfc458a..036a310ac8 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -179,7 +179,7 @@ change your code to use the explicit form (`render file: "foo/bar"`) instead.
`respond_with` and the corresponding class-level `respond_to` have been moved
to the [responders](https://github.com/plataformatec/responders) gem. Add
-`gem 'responders', '~> 2.0'` to your Gemfile to use it:
+`gem 'responders', '~> 2.0'` to your `Gemfile` to use it:
```ruby
# app/controllers/users_controller.rb
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
index 3805fd2a63..656838c6b8 100644
--- a/guides/source/5_0_release_notes.md
+++ b/guides/source/5_0_release_notes.md
@@ -775,7 +775,7 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Added prepared statements support to `mysql2` adapter, for mysql2 0.4.4+,
Previously this was only supported on the deprecated `mysql` legacy adapter.
- To enable, set `prepared_statements: true` in config/database.yml.
+ To enable, set `prepared_statements: true` in `config/database.yml`.
([Pull Request](https://github.com/rails/rails/pull/23461))
* Added ability to call `ActionRecord::Relation#update` on relation objects
diff --git a/guides/source/5_1_release_notes.md b/guides/source/5_1_release_notes.md
index fa92b9e5f8..80c9da6446 100644
--- a/guides/source/5_1_release_notes.md
+++ b/guides/source/5_1_release_notes.md
@@ -602,7 +602,7 @@ Please refer to the [Changelog][active-support] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/28157))
* Deprecated passing string to `:if` and `:unless` conditional options on `set_callback` and `skip_callback`.
- ([Commit](https://github.com/rails/rails/commit/0952552)
+ ([Commit](https://github.com/rails/rails/commit/0952552))
### Notable changes
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index 8afec00018..6959f992aa 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -16,6 +16,7 @@
<% end %>
<p>
The guides for earlier releases:
+<a href="http://guides.rubyonrails.org/v5.1/">Rails 5.1</a>,
<a href="http://guides.rubyonrails.org/v5.0/">Rails 5.0</a>,
<a href="http://guides.rubyonrails.org/v4.2/">Rails 4.2</a>,
<a href="http://guides.rubyonrails.org/v4.1/">Rails 4.1</a>,
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index 31151e0329..6ac6792e26 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -555,9 +555,8 @@ The async adapter is intended for development/testing and should not be used in
##### Redis Adapter
-Action Cable contains two Redis adapters: "normal" Redis and Evented Redis. Both
-of the adapters require users to provide a URL pointing to the Redis server.
-Additionally, a channel_prefix may be provided to avoid channel name collisions
+The Redis adapter requires users to provide a URL pointing to the Redis server.
+Additionally, a `channel_prefix` may be provided to avoid channel name collisions
when using the same Redis server for multiple applications. See the [Redis PubSub documentation](https://redis.io/topics/pubsub#database-amp-scoping) for more details.
##### PostgreSQL Adapter
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index b3b5f19b61..d53c4dedf9 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -397,34 +397,18 @@ You can also pass a `:domain` key and specify the domain name for the cookie:
Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"
```
-Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/secrets.yml`
+Rails sets up (for the CookieStore) a secret key used for signing the session data in `config/credentials.yml.enc`. This can be changed with `bin/rails credentials:edit`.
```ruby
-# Be sure to restart your server when you modify this file.
-
-# Your secret key is used for verifying the integrity of signed cookies.
-# If you change this key, all old signed cookies will become invalid!
-
-# Make sure the secret is at least 30 characters and all random,
-# no regular words or you'll be exposed to dictionary attacks.
-# You can use `rails secret` to generate a secure secret key.
-
-# Make sure the secrets in this file are kept private
-# if you're sharing your code publicly.
-
-development:
- secret_key_base: a75d...
-
-test:
- secret_key_base: 492f...
+# aws:
+# access_key_id: 123
+# secret_access_key: 345
-# Do not keep production secrets in the repository,
-# instead read values from the environment.
-production:
- secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
+# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
+secret_key_base: 492f...
```
-NOTE: Changing the secret when using the `CookieStore` will invalidate all existing sessions.
+NOTE: Changing the secret_key_base when using the `CookieStore` will invalidate all existing sessions.
### Accessing the Session
@@ -800,9 +784,9 @@ The way this is done is to add a non-guessable token which is only known to your
If you generate a form like this:
```erb
-<%= form_for @user do |f| %>
- <%= f.text_field :username %>
- <%= f.text_field :password %>
+<%= form_with model: @user, local: true do |form| %>
+ <%= form.text_field :username %>
+ <%= form.text_field :password %>
<% end %>
```
@@ -1132,7 +1116,7 @@ Rails default exception handling displays a "500 Server Error" message for all e
### The Default 500 and 404 Templates
-By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them.
+By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the public folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them.
### `rescue_from`
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 96ef9c4450..cb07781d1c 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -92,8 +92,8 @@ registered email address:
class UserMailer < ApplicationMailer
default from: 'notifications@example.com'
- def welcome_email(user)
- @user = user
+ def welcome_email
+ @user = params[:user]
@url = 'http://example.com/login'
mail(to: @user.email, subject: 'Welcome to My Awesome Site')
end
@@ -176,7 +176,7 @@ $ bin/rails db:migrate
Now that we have a user model to play with, we will just edit the
`app/controllers/users_controller.rb` make it instruct the `UserMailer` to deliver
an email to the newly created user by editing the create action and inserting a
-call to `UserMailer.welcome_email` right after the user is successfully saved.
+call to `UserMailer.with(user: @user).welcome_email` right after the user is successfully saved.
Action Mailer is nicely integrated with Active Job so you can send emails outside
of the request-response cycle, so the user doesn't have to wait on it:
@@ -191,7 +191,7 @@ class UsersController < ApplicationController
respond_to do |format|
if @user.save
# Tell the UserMailer to send a welcome email after save
- UserMailer.welcome_email(@user).deliver_later
+ UserMailer.with(user: @user).welcome_email.deliver_later
format.html { redirect_to(@user, notice: 'User was successfully created.') }
format.json { render json: @user, status: :created, location: @user }
@@ -220,12 +220,17 @@ If you want to send emails right away (from a cronjob for example) just call
class SendWeeklySummary
def run
User.find_each do |user|
- UserMailer.weekly_summary(user).deliver_now
+ UserMailer.with(user: user).weekly_summary.deliver_now
end
end
end
```
+Any key value pair passed to `with` just becomes the `params` for the mailer
+action. So `with(user: @user, account: @user.account)` makes `params[:user]` and
+`params[:account]` available in the mailer action. Just like controllers have
+params.
+
The method `welcome_email` returns an `ActionMailer::MessageDelivery` object which
can then just be told `deliver_now` or `deliver_later` to send itself out. The
`ActionMailer::MessageDelivery` object is just a wrapper around a `Mail::Message`. If
@@ -331,7 +336,7 @@ with the addresses separated by commas.
```ruby
class AdminMailer < ApplicationMailer
- default to: Proc.new { Admin.pluck(:email) },
+ default to: -> { Admin.pluck(:email) },
from: 'notification@example.com'
def new_registration(user)
@@ -351,8 +356,8 @@ address when they receive the email. The trick to doing that is to format the
email address in the format `"Full Name" <email>`.
```ruby
-def welcome_email(user)
- @user = user
+def welcome_email
+ @user = params[:user]
email_with_name = %("#{@user.name}" <#{@user.email}>)
mail(to: email_with_name, subject: 'Welcome to My Awesome Site')
end
@@ -372,8 +377,8 @@ To change the default mailer view for your action you do something like:
class UserMailer < ApplicationMailer
default from: 'notifications@example.com'
- def welcome_email(user)
- @user = user
+ def welcome_email
+ @user = params[:user]
@url = 'http://example.com/login'
mail(to: @user.email,
subject: 'Welcome to My Awesome Site',
@@ -394,8 +399,8 @@ templates or even render inline or text without using a template file:
class UserMailer < ApplicationMailer
default from: 'notifications@example.com'
- def welcome_email(user)
- @user = user
+ def welcome_email
+ @user = params[:user]
@url = 'http://example.com/login'
mail(to: @user.email,
subject: 'Welcome to My Awesome Site') do |format|
@@ -453,8 +458,8 @@ the format block to specify different layouts for different formats:
```ruby
class UserMailer < ApplicationMailer
- def welcome_email(user)
- mail(to: user.email) do |format|
+ def welcome_email
+ mail(to: params[:user].email) do |format|
format.html { render layout: 'my_layout' }
format.text
end
@@ -477,7 +482,7 @@ special URL that renders them. In the above example, the preview class for
```ruby
class UserMailerPreview < ActionMailer::Preview
def welcome_email
- UserMailer.welcome_email(User.first)
+ UserMailer.with(user: User.first).welcome_email
end
end
```
@@ -594,12 +599,12 @@ mailer action.
```ruby
class UserMailer < ApplicationMailer
- def welcome_email(user, company)
- @user = user
+ def welcome_email
+ @user = params[:user]
@url = user_url(@user)
- delivery_options = { user_name: company.smtp_user,
- password: company.smtp_password,
- address: company.smtp_host }
+ delivery_options = { user_name: params[:company].smtp_user,
+ password: params[:company].smtp_password,
+ address: params[:company].smtp_host }
mail(to: @user.email,
subject: "Please see the Terms and Conditions attached",
delivery_method_options: delivery_options)
@@ -616,9 +621,9 @@ will default to `text/plain` otherwise.
```ruby
class UserMailer < ApplicationMailer
- def welcome_email(user, email_body)
- mail(to: user.email,
- body: email_body,
+ def welcome_email
+ mail(to: params[:user].email,
+ body: params[:email_body],
content_type: "text/html",
subject: "Already rendered!")
end
@@ -677,24 +682,43 @@ Action Mailer allows for you to specify a `before_action`, `after_action` and
* You could use a `before_action` to populate the mail object with defaults,
delivery_method_options or insert default headers and attachments.
+```ruby
+class InvitationsMailer < ApplicationMailer
+ before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
+ before_action { @account = params[:inviter].account }
+
+ default to: -> { @invitee.email_address },
+ from: -> { common_address(@inviter) },
+ reply_to: -> { @inviter.email_address_with_name }
+
+ def account_invitation
+ mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
+ end
+
+ def project_invitation
+ @project = params[:project]
+ @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
+
+ mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
+ end
+end
+```
+
* You could use an `after_action` to do similar setup as a `before_action` but
using instance variables set in your mailer action.
```ruby
class UserMailer < ApplicationMailer
+ before_action { @business, @user = params[:business], params[:user] }
+
after_action :set_delivery_options,
:prevent_delivery_to_guests,
:set_business_headers
- def feedback_message(business, user)
- @business = business
- @user = user
- mail
+ def feedback_message
end
- def campaign_message(business, user)
- @business = business
- @user = user
+ def campaign_message
end
private
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index a57623428f..c1e02745de 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -490,7 +490,7 @@ stylesheet_link_tag "application" # => <link href="/assets/application.css" medi
#### stylesheet_path
-Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by stylesheet_link_tag to build the stylesheet path.
+Computes the path to a stylesheet asset in the `app/assets/stylesheets` directory. If the source filename has no extension, `.css` will be appended. Full paths from the document root will be passed through. Used internally by `stylesheet_link_tag` to build the stylesheet path.
```ruby
stylesheet_path "application" # => /assets/application.css
@@ -1160,7 +1160,7 @@ Returns a string of option tags for pretty much any time zone in the world.
Returns select and option tags for the given object and method, using `time_zone_options_for_select` to generate the list of option tags.
```ruby
-time_zone_select( "user", "time_zone")
+time_zone_select("user", "time_zone")
```
#### date_field
diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md
index 9fc95954bc..914ef2c327 100644
--- a/guides/source/active_job_basics.md
+++ b/guides/source/active_job_basics.md
@@ -162,6 +162,7 @@ Here is a noncomprehensive list of documentation:
- [Sidekiq](https://github.com/mperham/sidekiq/wiki/Active-Job)
- [Resque](https://github.com/resque/resque/wiki/ActiveJob)
+- [Sneakers](https://github.com/jondot/sneakers/wiki/How-To:-Rails-Background-Jobs-with-ActiveJob)
- [Sucker Punch](https://github.com/brandonhilkert/sucker_punch#active-job)
- [Queue Classic](https://github.com/QueueClassic/queue_classic#active-job)
@@ -388,6 +389,25 @@ class GuestsCleanupJob < ApplicationJob
end
```
+### Retrying or Discarding failed jobs
+
+It's also possible to retry or discard a job if an exception is raised during execution.
+For example:
+
+```ruby
+class RemoteServiceJob < ApplicationJob
+ retry_on CustomAppException # defaults to 3s wait, 5 attempts
+
+ discard_on ActiveJob::DeserializationError
+
+ def perform(*args)
+ # Might raise CustomAppException or ActiveJob::DeserializationError
+ end
+end
+```
+
+To get more details see the API Documentation for [ActiveJob::Exceptions](http://api.rubyonrails.org/classes/ActiveJob/Exceptions/ClassMethods.html).
+
### Deserialization
GlobalID allows serializing full Active Record objects passed to `#perform`.
diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md
index b8f076a27b..ee0472621b 100644
--- a/guides/source/active_model_basics.md
+++ b/guides/source/active_model_basics.md
@@ -464,7 +464,7 @@ a `password` accessor with certain validations on it.
#### Requirements
`ActiveModel::SecurePassword` depends on [`bcrypt`](https://github.com/codahale/bcrypt-ruby 'BCrypt'),
-so include this gem in your Gemfile to use `ActiveModel::SecurePassword` correctly.
+so include this gem in your `Gemfile` to use `ActiveModel::SecurePassword` correctly.
In order to make this work, the model must have an accessor named `password_digest`.
The `has_secure_password` will add the following validations on the `password` accessor:
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index 11aefcb05f..069a624984 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -304,6 +304,17 @@ user = User.find_by(name: 'David')
user.destroy
```
+If you'd like to delete several records in bulk, you may use `destroy_all`
+method:
+
+```ruby
+# find and delete all users named David
+User.where(name: 'David').destroy_all
+
+# delete all users
+User.destroy_all
+```
+
Validations
-----------
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 678b80516f..3786343fc3 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -414,7 +414,7 @@ end
`find_in_batches` works on model classes, as seen above, and also on relations:
```ruby
-Invoice.pending.find_in_batches do |invoice|
+Invoice.pending.find_in_batches do |invoices|
pending_invoices_export.add_invoices(invoices)
end
```
@@ -1712,10 +1712,10 @@ Client.find_by_sql("SELECT * FROM clients
### `select_all`
-`find_by_sql` has a close relative called `connection#select_all`. `select_all` will retrieve objects from the database using custom SQL just like `find_by_sql` but will not instantiate them. Instead, you will get an array of hashes where each hash indicates a record.
+`find_by_sql` has a close relative called `connection#select_all`. `select_all` will retrieve objects from the database using custom SQL just like `find_by_sql` but will not instantiate them. This method will return an instance of `ActiveRecord::Result` class and calling `to_hash` on this object would return you an array of hashes where each hash indicates a record.
```ruby
-Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'")
+Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'").to_hash
# => [
# {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"},
# {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"}
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index b43d13b804..54d3dec1c2 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -634,7 +634,7 @@ NOTE: Defined in `active_support/core_ext/module/introspection.rb`.
#### `parent_name`
-The `parent_name` method on a nested named module returns the fully-qualified name of the module that contains its corresponding constant:
+The `parent_name` method on a nested named module returns the fully qualified name of the module that contains its corresponding constant:
```ruby
module X
@@ -674,44 +674,6 @@ M.parents # => [X::Y, X, Object]
NOTE: Defined in `active_support/core_ext/module/introspection.rb`.
-### Reachable
-
-A named module is reachable if it is stored in its corresponding constant. It means you can reach the module object via the constant.
-
-That is what ordinarily happens, if a module is called "M", the `M` constant exists and holds it:
-
-```ruby
-module M
-end
-
-M.reachable? # => true
-```
-
-But since constants and modules are indeed kind of decoupled, module objects can become unreachable:
-
-```ruby
-module M
-end
-
-orphan = Object.send(:remove_const, :M)
-
-# The module object is orphan now but it still has a name.
-orphan.name # => "M"
-
-# You cannot reach it via the constant M because it does not even exist.
-orphan.reachable? # => false
-
-# Let's define a module called "M" again.
-module M
-end
-
-# The constant M exists now again, and it stores a module
-# object called "M", but it is a new instance.
-orphan.reachable? # => false
-```
-
-NOTE: Defined in `active_support/core_ext/module/reachable.rb`.
-
### Anonymous
A module may or may not have a name:
@@ -745,7 +707,6 @@ end
m = Object.send(:remove_const, :M)
-m.reachable? # => false
m.anonymous? # => false
```
@@ -1791,7 +1752,7 @@ The methods `to_date`, `to_time`, and `to_datetime` are basically convenience wr
"2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200
```
-Default is `:utc`.
+Default is `:local`.
Please refer to the documentation of `Date._parse` for further details.
@@ -1835,7 +1796,7 @@ NOTE: Defined in `active_support/core_ext/numeric/bytes.rb`.
### Time
-Enables the use of time calculations and declarations, like `45.minutes + 2.hours + 4.years`.
+Enables the use of time calculations and declarations, like `45.minutes + 2.hours + 4.weeks`.
These methods use Time#advance for precise date calculations when using from_now, ago, etc.
as well as adding or subtracting their results from a Time object. For example:
@@ -1844,13 +1805,15 @@ as well as adding or subtracting their results from a Time object. For example:
# equivalent to Time.current.advance(months: 1)
1.month.from_now
-# equivalent to Time.current.advance(years: 2)
-2.years.from_now
+# equivalent to Time.current.advance(weeks: 2)
+2.weeks.from_now
-# equivalent to Time.current.advance(months: 4, years: 5)
-(4.months + 5.years).from_now
+# equivalent to Time.current.advance(months: 4, weeks: 5)
+(4.months + 5.weeks).from_now
```
+WARNING. For other durations please refer to the time extensions to `Integer`.
+
NOTE: Defined in `active_support/core_ext/numeric/time.rb`.
### Formatting
@@ -1986,6 +1949,28 @@ The method `ordinalize` returns the ordinal string corresponding to the receiver
NOTE: Defined in `active_support/core_ext/integer/inflections.rb`.
+### Time
+
+Enables the use of time calculations and declarations, like `4.months + 5.years`.
+
+These methods use Time#advance for precise date calculations when using from_now, ago, etc.
+as well as adding or subtracting their results from a Time object. For example:
+
+```ruby
+# equivalent to Time.current.advance(months: 1)
+1.month.from_now
+
+# equivalent to Time.current.advance(years: 2)
+2.years.from_now
+
+# equivalent to Time.current.advance(months: 4, years: 5)
+(4.months + 5.years).from_now
+```
+
+WARNING. For other durations please refer to the time extensions to `Numeric`.
+
+NOTE: Defined in `active_support/core_ext/integer/time.rb`.
+
Extensions to `BigDecimal`
--------------------------
### `to_s`
@@ -3708,9 +3693,9 @@ Extensions to `NameError`
Active Support adds `missing_name?` to `NameError`, which tests whether the exception was raised because of the name passed as argument.
-The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully-qualified constant name.
+The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully qualified constant name.
-TIP: A symbol can represent a fully-qualified constant name as in `:"ActiveRecord::Base"`, so the behavior for symbols is defined for convenience, not because it has to be that way technically.
+TIP: A symbol can represent a fully qualified constant name as in `:"ActiveRecord::Base"`, so the behavior for symbols is defined for convenience, not because it has to be that way technically.
For example, when an action of `ArticlesController` is called Rails tries optimistically to use `ArticlesHelper`. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that `articles_helper.rb` raises a `NameError` due to an actual unknown constant. That should be reraised. The method `missing_name?` provides a way to distinguish both cases:
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 03c9183eb3..25f78fd940 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -197,6 +197,12 @@ INFO. Additional keys may be added by the caller.
}
```
+### unpermitted_parameters.action_controller
+
+| Key | Value |
+| ------- | ---------------- |
+| `:keys` | Unpermitted keys |
+
Action View
-----------
@@ -304,7 +310,7 @@ Action Mailer
mailer: "Notification",
message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
subject: "Rails Guides",
- to: ["users@rails.com", "ddh@rails.com"],
+ to: ["users@rails.com", "dhh@rails.com"],
from: ["me@rails.com"],
date: Sat, 10 Mar 2012 14:18:09 +0100,
mail: "..." # omitted for brevity
@@ -330,13 +336,29 @@ Action Mailer
mailer: "Notification",
message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
subject: "Rails Guides",
- to: ["users@rails.com", "ddh@rails.com"],
+ to: ["users@rails.com", "dhh@rails.com"],
from: ["me@rails.com"],
date: Sat, 10 Mar 2012 14:18:09 +0100,
mail: "..." # omitted for brevity
}
```
+### process.action_mailer
+
+| Key | Value |
+| ------------- | ------------------------ |
+| `:mailer` | Name of the mailer class |
+| `:action` | The action |
+| `:args` | The arguments |
+
+```ruby
+{
+ mailer: "Notification",
+ action: "welcome_email",
+ args: []
+}
+```
+
Active Support
--------------
@@ -450,6 +472,92 @@ Active Job
| `:adapter` | QueueAdapter object processing the job |
| `:job` | Job object |
+Action Cable
+------------
+
+### perform_action.action_cable
+
+| Key | Value |
+| ---------------- | ------------------------- |
+| `:channel_class` | Name of the channel class |
+| `:action` | The action |
+| `:data` | A hash of data |
+
+### transmit.action_cable
+
+| Key | Value |
+| ---------------- | ------------------------- |
+| `:channel_class` | Name of the channel class |
+| `:data` | A hash of data |
+| `:via` | Via |
+
+### transmit_subscription_confirmation.action_cable
+
+| Key | Value |
+| ---------------- | ------------------------- |
+| `:channel_class` | Name of the channel class |
+
+### transmit_subscription_rejection.action_cable
+
+| Key | Value |
+| ---------------- | ------------------------- |
+| `:channel_class` | Name of the channel class |
+
+### broadcast.action_cable
+
+| Key | Value |
+| --------------- | -------------------- |
+| `:broadcasting` | A named broadcasting |
+| `:message` | A hash of message |
+| `:coder` | The coder |
+
+Active Storage
+--------------
+
+### service_upload.active_storage
+
+| Key | Value |
+| ------------ | ---------------------------- |
+| `:key` | Secure token |
+| `:service` | Name of the service |
+| `:checksum` | Checksum to ensure integrity |
+
+### service_streaming_download.active_storage
+
+| Key | Value |
+| ------------ | ------------------- |
+| `:key` | Secure token |
+| `:service` | Name of the service |
+
+### service_download.active_storage
+
+| Key | Value |
+| ------------ | ------------------- |
+| `:key` | Secure token |
+| `:service` | Name of the service |
+
+### service_delete.active_storage
+
+| Key | Value |
+| ------------ | ------------------- |
+| `:key` | Secure token |
+| `:service` | Name of the service |
+
+### service_exist.active_storage
+
+| Key | Value |
+| ------------ | --------------------------- |
+| `:key` | Secure token |
+| `:service` | Name of the service |
+| `:exist` | File or blob exists or not |
+
+### service_url.active_storage
+
+| Key | Value |
+| ------------ | ------------------- |
+| `:key` | Secure token |
+| `:service` | Name of the service |
+| `:url` | Generated url |
Railties
--------
@@ -549,4 +657,4 @@ end
```
You should follow Rails conventions when defining your own events. The format is: `event.library`.
-If you application is sending Tweets, you should create an event named `tweet.twitter`.
+If your application is sending Tweets, you should create an event named `tweet.twitter`.
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index da1b7b25ef..43a7de88b0 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -216,7 +216,6 @@ An API application comes with the following middleware by default:
- `Rack::Head`
- `Rack::ConditionalGet`
- `Rack::ETag`
-- `MyApi::Application::Routes`
See the [internal middleware](rails_on_rack.html#internal-middleware-stack)
section of the Rack guide for further information on them.
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 17ab9c7600..805b0f0d62 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -65,7 +65,7 @@ config.assets.js_compressor = :uglifier
```
NOTE: The `sass-rails` gem is automatically used for CSS compression if included
-in the Gemfile and no `config.assets.css_compressor` option is set.
+in the `Gemfile` and no `config.assets.css_compressor` option is set.
### Main Features
@@ -154,7 +154,7 @@ environments. You can enable or disable it in your configuration through the
More reading:
-* [Optimize caching](http://code.google.com/speed/page-speed/docs/caching.html)
+* [Optimize caching](https://developers.google.com/speed/docs/insights/LeverageBrowserCaching)
* [Revving Filenames: don't use querystring](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/)
@@ -181,7 +181,7 @@ When you generate a scaffold or a controller, Rails also generates a JavaScript
file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a
Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`)
for that controller. Additionally, when generating a scaffold, Rails generates
-the file scaffolds.css (or scaffolds.scss if `sass-rails` is in the
+the file `scaffolds.css` (or `scaffolds.scss` if `sass-rails` is in the
`Gemfile`.)
For example, if you generate a `ProjectsController`, Rails will also add a new
@@ -202,7 +202,7 @@ will result in your assets being included more than once.
WARNING: When using asset precompilation, you will need to ensure that your
controller assets will be precompiled when loading them on a per page basis. By
-default .coffee and .scss files will not be precompiled on their own. See
+default `.coffee` and `.scss` files will not be precompiled on their own. See
[Precompiling Assets](#precompiling-assets) for more information on how
precompiling works.
@@ -726,7 +726,7 @@ include, you can add them to the `precompile` array in `config/initializers/asse
Rails.application.config.assets.precompile += %w( admin.js admin.css )
```
-NOTE. Always specify an expected compiled filename that ends with .js or .css,
+NOTE. Always specify an expected compiled filename that ends with `.js` or `.css`,
even if you want to add Sass or CoffeeScript files to the precompile array.
The task also generates a `.sprockets-manifest-md5hash.json` (where `md5hash` is
@@ -1090,7 +1090,7 @@ Possible options for JavaScript compression are `:closure`, `:uglifier` and
`:yui`. These require the use of the `closure-compiler`, `uglifier` or
`yui-compressor` gems, respectively.
-The default Gemfile includes [uglifier](https://github.com/lautis/uglifier).
+The default `Gemfile` includes [uglifier](https://github.com/lautis/uglifier).
This gem wraps [UglifyJS](https://github.com/mishoo/UglifyJS) (written for
NodeJS) in Ruby. It compresses your code by removing white space and comments,
shortening local variable names, and performing other micro-optimizations such
@@ -1219,7 +1219,7 @@ Sprockets uses Processors, Transformers, Compressors, and Exporters to extend
Sprockets functionality. Have a look at
[Extending Sprockets](https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md)
to learn more. Here we registered a preprocessor to add a comment to the end
-of text/css (.css) files.
+of text/css (`.css`) files.
```ruby
module AddComment
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index b5bd24d027..9616647f15 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -906,7 +906,7 @@ The `belongs_to` association supports these options:
##### `:autosave`
-If you set the `:autosave` option to `true`, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.
+If you set the `:autosave` option to `true`, Rails will save any loaded association members and destroy members that are marked for destruction whenever you save the parent object. Setting `:autosave` to `false` is not the same as not setting the `:autosave` option. If the `:autosave` option is not present, then new associated objects will be saved, but updated associated objects will not be saved.
##### `:class_name`
@@ -1257,7 +1257,7 @@ Setting the `:as` option indicates that this is a polymorphic association. Polym
##### `:autosave`
-If you set the `:autosave` option to `true`, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.
+If you set the `:autosave` option to `true`, Rails will save any loaded association members and destroy members that are marked for destruction whenever you save the parent object. Setting `:autosave` to `false` is not the same as not setting the `:autosave` option. If the `:autosave` option is not present, then new associated objects will be saved, but updated associated objects will not be saved.
##### `:class_name`
@@ -1653,7 +1653,7 @@ Setting the `:as` option indicates that this is a polymorphic association, as di
##### `:autosave`
-If you set the `:autosave` option to `true`, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.
+If you set the `:autosave` option to `true`, Rails will save any loaded association members and destroy members that are marked for destruction whenever you save the parent object. Setting `:autosave` to `false` is not the same as not setting the `:autosave` option. If the `:autosave` option is not present, then new associated objects will be saved, but updated associated objects will not be saved.
##### `:class_name`
@@ -2176,7 +2176,7 @@ end
##### `:autosave`
-If you set the `:autosave` option to `true`, Rails will save any loaded members and destroy members that are marked for destruction whenever you save the parent object.
+If you set the `:autosave` option to `true`, Rails will save any loaded association members and destroy members that are marked for destruction whenever you save the parent object. Setting `:autosave` to `false` is not the same as not setting the `:autosave` option. If the `:autosave` option is not present, then new associated objects will be saved, but updated associated objects will not be saved.
##### `:class_name`
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index c62194faf4..ede0324a51 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -954,7 +954,7 @@ to work on some subclass, things get interesting.
While working with `Polygon` you do not need to be aware of all its descendants,
because anything in the table is by definition a polygon, but when working with
subclasses Active Record needs to be able to enumerate the types it is looking
-for. Let’s see an example.
+for. Let's see an example.
`Rectangle.all` only loads rectangles by adding a type constraint to the query:
@@ -963,7 +963,7 @@ SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle")
```
-Let’s introduce now a subclass of `Rectangle`:
+Let's introduce now a subclass of `Rectangle`:
```ruby
# app/models/square.rb
@@ -978,7 +978,7 @@ SELECT "polygons".* FROM "polygons"
WHERE "polygons"."type" IN ("Rectangle", "Square")
```
-But there’s a caveat here: How does Active Record know that the class `Square`
+But there's a caveat here: How does Active Record know that the class `Square`
exists at all?
Even if the file `app/models/square.rb` exists and defines the `Square` class,
@@ -1049,7 +1049,7 @@ end
The purpose of this setup would be that the application uses the class that
corresponds to the environment via `AUTH_SERVICE`. In development mode
-`MockedAuthService` gets autoloaded when the initializer runs. Let’s suppose
+`MockedAuthService` gets autoloaded when the initializer runs. Let's suppose
we do some requests, change its implementation, and hit the application again.
To our surprise the changes are not reflected. Why?
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 910a531068..96650b5be9 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -181,7 +181,7 @@ cache.
### Shared Partial Caching
-It is possible to share partials and associated caching between files with different mime types. For example shared partial caching allows template writers to share a partial between HTML and Javascript files. When templates are collected in the template resolver file paths they only include the template language extension and not the mime type. Because of this templates can be used for multiple mime types. Both HTML and JavaScript requests will respond to the following code:
+It is possible to share partials and associated caching between files with different mime types. For example shared partial caching allows template writers to share a partial between HTML and JavaScript files. When templates are collected in the template resolver file paths they only include the template language extension and not the mime type. Because of this templates can be used for multiple mime types. Both HTML and JavaScript requests will respond to the following code:
```ruby
render(partial: 'hotels/hotel', collection: @hotels, cached: true)
@@ -195,7 +195,7 @@ Another option is to include the full filename of the partial to render.
render(partial: 'hotels/hotel.html.erb', collection: @hotels, cached: true)
```
-Will load a file named `hotels/hotel.html.erb` in any file mime type, for example you could include this partial in a Javascript file.
+Will load a file named `hotels/hotel.html.erb` in any file mime type, for example you could include this partial in a JavaScript file.
### Managing dependencies
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 2cd8e02a77..88c559921c 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -659,6 +659,6 @@ development:
...
```
-It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database.
+It also generated some lines in our `database.yml` configuration corresponding to our choice of PostgreSQL for database.
NOTE. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the `rails new` command to generate the basis of your app.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index d4e1d7b5dd..7a32607eb7 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -138,7 +138,7 @@ defaults to `:debug` for all environments. The available log levels are: `:debug
* `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to `true`. If `config.cache_classes` is `true`, this option is ignored.
-* `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`.
+* `secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get a random generated key in test and development environments, other environments should set one in `config/credentials.yml.enc`.
* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to `true`, but in the production environment it is set to `false` because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to `true.` Otherwise, you won't be able to use page caching and request for files that exist under the public directory.
@@ -391,7 +391,7 @@ by setting up a Rake task which runs
```
for all models and all boolean columns, after which the flag must be set to true
-by adding the following to your application.rb file:
+by adding the following to your `application.rb` file:
```ruby
Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
@@ -487,6 +487,15 @@ Defaults to `'signed cookie'`.
authenticated encrypted cookie salt. Defaults to `'authenticated encrypted
cookie'`.
+* `config.action_dispatch.encrypted_cookie_cipher` sets the cipher to be
+ used for encrypted cookies. This defaults to `"aes-256-gcm"`.
+
+* `config.action_dispatch.signed_cookie_digest` sets the digest to be
+ used for signed cookies. This defaults to `"SHA1"`.
+
+* `config.action_dispatch.cookies_rotations` allows rotating
+ secrets, ciphers, and digests for encrypted and signed cookies.
+
* `config.action_dispatch.perform_deep_munge` configures whether `deep_munge`
method should be performed on the parameters. See [Security Guide](security.html#unsafe-query-generation)
for more information. It defaults to `true`.
@@ -563,7 +572,7 @@ Defaults to `'signed cookie'`.
error should be raised for missing translations.
* `config.action_view.automatically_disable_submit_tag` determines whether
- submit_tag should automatically disable on click, this defaults to `true`.
+ `submit_tag` should automatically disable on click, this defaults to `true`.
* `config.action_view.debug_missing_translation` determines whether to wrap the missing translations key in a `<span>` tag or not. This defaults to `true`.
@@ -1308,7 +1317,7 @@ know which pages it is allowed to index.
Rails creates this file for you inside the `/public` folder. By default, it allows
search engines to index all pages of your application. If you want to block
-indexing on all pages of you application, use this:
+indexing on all pages of your application, use this:
```
User-agent: *
diff --git a/guides/source/engines.md b/guides/source/engines.md
index c7331b6ca4..a9b841e3bf 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -63,7 +63,7 @@ authentication for its parent applications, or
[Thredded](https://github.com/thredded/thredded), an engine that provides forum
functionality. There's also [Spree](https://github.com/spree/spree) which
provides an e-commerce platform, and
-[RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine.
+[Refinery CMS](https://github.com/refinery/refinerycms), a CMS engine.
Finally, engines would not have been possible without the work of James Adam,
Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever
@@ -537,12 +537,12 @@ directory at `app/views/blorgh/comments` and in it a new file called
```html+erb
<h3>New comment</h3>
-<%= form_for [@article, @article.comments.build] do |f| %>
+<%= form_with(model: [@article, @article.comments.build], local: true) do |form| %>
<p>
- <%= f.label :text %><br>
- <%= f.text_area :text %>
+ <%= form.label :text %><br>
+ <%= form.text_area :text %>
</p>
- <%= f.submit %>
+ <%= form.submit %>
<% end %>
```
@@ -653,7 +653,7 @@ there isn't an application handy to test this out in, generate one using the
$ rails new unicorn
```
-Usually, specifying the engine inside the Gemfile would be done by specifying it
+Usually, specifying the engine inside the `Gemfile` would be done by specifying it
as a normal, everyday gem.
```ruby
@@ -783,8 +783,8 @@ added above the `title` field with this code:
```html+erb
<div class="field">
- <%= f.label :author_name %><br>
- <%= f.text_field :author_name %>
+ <%= form.label :author_name %><br>
+ <%= form.text_field :author_name %>
</div>
```
@@ -1322,7 +1322,7 @@ engine.
Assets within an engine work in an identical way to a full application. Because
the engine class inherits from `Rails::Engine`, the application will know to
-look up assets in the engine's 'app/assets' and 'lib/assets' directories.
+look up assets in the engine's `app/assets` and `lib/assets` directories.
Like all of the other components of an engine, the assets should be namespaced.
This means that if you have an asset called `style.css`, it should be placed at
@@ -1361,7 +1361,7 @@ that only exists for your engine. In this case, the host application doesn't
need to require `admin.css` or `admin.js`. Only the gem's admin layout needs
these assets. It doesn't make sense for the host app to include
`"blorgh/admin.css"` in its stylesheets. In this situation, you should
-explicitly define these assets for precompilation. This tells sprockets to add
+explicitly define these assets for precompilation. This tells Sprockets to add
your engine assets when `bin/rails assets:precompile` is triggered.
You can define assets for precompilation in `engine.rb`:
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index f46f1648b3..4ce67df93a 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -274,10 +274,12 @@ There are a few things to note here:
The resulting HTML is:
```html
-<form accept-charset="UTF-8" action="/articles" method="post" class="nifty_form">
- <input id="article_title" name="article[title]" type="text" />
- <textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea>
- <input name="commit" type="submit" value="Create" />
+<form class="nifty_form" id="new_article" action="/articles" accept-charset="UTF-8" method="post">
+ <input name="utf8" type="hidden" value="&#x2713;" />
+ <input type="hidden" name="authenticity_token" value="NRkFyRWxdYNfUg7vYxLOp2SLf93lvnl+QwDWorR42Dp6yZXPhHEb6arhDOIWcqGit8jfnrPwL781/xlrzj63TA==" />
+ <input type="text" name="article[title]" id="article_title" />
+ <textarea name="article[body]" id="article_body" cols="60" rows="12"></textarea>
+ <input type="submit" name="commit" value="Create" data-disable-with="Create" />
</form>
```
@@ -299,9 +301,11 @@ You can create a similar binding without actually creating `<form>` tags with th
which produces the following output:
```html
-<form accept-charset="UTF-8" action="/people" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" type="text" />
- <input id="contact_detail_phone_number" name="contact_detail[phone_number]" type="text" />
+<form class="new_person" id="new_person" action="/people" accept-charset="UTF-8" method="post">
+ <input name="utf8" type="hidden" value="&#x2713;" />
+ <input type="hidden" name="authenticity_token" value="bL13x72pldyDD8bgtkjKQakJCpd4A8JdXGbfksxBDHdf1uC0kCMqe2tvVdUYfidJt0fj3ihC4NxiVHv8GVYxJA==" />
+ <input type="text" name="person[name]" id="person_name" />
+ <input type="text" name="contact_detail[phone_number]" id="contact_detail_phone_number" />
</form>
```
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 1e5c6fe3d0..b007baea87 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -172,18 +172,20 @@ of the files and folders that Rails created by default:
|app/|Contains the controllers, models, views, helpers, mailers, channels, jobs and assets for your application. You'll focus on this folder for the remainder of this guide.|
|bin/|Contains the rails script that starts your app and can contain other scripts you use to setup, update, deploy or run your application.|
|config/|Configure your application's routes, database, and more. This is covered in more detail in [Configuring Rails Applications](configuring.html).|
-|config.ru|Rack configuration for Rack based servers used to start the application.|
+|config.ru|Rack configuration for Rack based servers used to start the application. For more information about Rack, see the [Rack website](https://rack.github.io/).|
|db/|Contains your current database schema, as well as the database migrations.|
|Gemfile<br>Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the [Bundler website](https://bundler.io).|
|lib/|Extended modules for your application.|
|log/|Application log files.|
+|package.json|This file allows you to specify what npm dependencies are needed for your Rails application. This file is used by Yarn. For more information about Yarn, see the [Yarn website](https://yarnpkg.com/lang/en/).|
|public/|The only folder seen by the world as-is. Contains static files and compiled assets.|
-|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the `lib/tasks` directory of your application.|
+|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing `Rakefile`, you should add your own tasks by adding files to the `lib/tasks` directory of your application.|
|README.md|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
|test/|Unit tests, fixtures, and other test apparatus. These are covered in [Testing Rails Applications](testing.html).|
|tmp/|Temporary files (like cache and pid files).|
|vendor/|A place for all third-party code. In a typical Rails application this includes vendored gems.|
|.gitignore|This file tells git which files (or patterns) it should ignore. See [GitHub - Ignoring files](https://help.github.com/articles/ignoring-files) for more info about ignoring files.
+|.ruby-version|This file contains the default Ruby version.|
Hello, Rails!
-------------
@@ -370,16 +372,17 @@ singular form `article` and makes meaningful use of the distinction.
```bash
$ bin/rails routes
- Prefix Verb URI Pattern Controller#Action
- articles GET /articles(.:format) articles#index
- POST /articles(.:format) articles#create
- new_article GET /articles/new(.:format) articles#new
-edit_article GET /articles/:id/edit(.:format) articles#edit
- article GET /articles/:id(.:format) articles#show
- PATCH /articles/:id(.:format) articles#update
- PUT /articles/:id(.:format) articles#update
- DELETE /articles/:id(.:format) articles#destroy
- root GET / welcome#index
+ Prefix Verb URI Pattern Controller#Action
+welcome_index GET /welcome/index(.:format) welcome#index
+ articles GET /articles(.:format) articles#index
+ POST /articles(.:format) articles#create
+ new_article GET /articles/new(.:format) articles#new
+ edit_article GET /articles/:id/edit(.:format) articles#edit
+ article GET /articles/:id(.:format) articles#show
+ PATCH /articles/:id(.:format) articles#update
+ PUT /articles/:id(.:format) articles#update
+ DELETE /articles/:id(.:format) articles#destroy
+ root GET / welcome#index
```
In the next section, you will add the ability to create new articles in your
@@ -565,15 +568,16 @@ To see what Rails will do with this, we look back at the output of
```bash
$ bin/rails routes
Prefix Verb URI Pattern Controller#Action
- articles GET /articles(.:format) articles#index
- POST /articles(.:format) articles#create
- new_article GET /articles/new(.:format) articles#new
-edit_article GET /articles/:id/edit(.:format) articles#edit
- article GET /articles/:id(.:format) articles#show
- PATCH /articles/:id(.:format) articles#update
- PUT /articles/:id(.:format) articles#update
- DELETE /articles/:id(.:format) articles#destroy
- root GET / welcome#index
+welcome_index GET /welcome/index(.:format) welcome#index
+ articles GET /articles(.:format) articles#index
+ POST /articles(.:format) articles#create
+ new_article GET /articles/new(.:format) articles#new
+ edit_article GET /articles/:id/edit(.:format) articles#edit
+ article GET /articles/:id(.:format) articles#show
+ PATCH /articles/:id(.:format) articles#update
+ PUT /articles/:id(.:format) articles#update
+ DELETE /articles/:id(.:format) articles#destroy
+ root GET / welcome#index
```
The `articles_path` helper tells Rails to point the form to the URI Pattern
@@ -592,7 +596,7 @@ familiar error:
You now need to create the `create` action within the `ArticlesController` for
this to work.
-NOTE: by default `form_with` submits forms using Ajax thereby skipping full page
+NOTE: By default `form_with` submits forms using Ajax thereby skipping full page
redirects. To make this guide easier to get into we've disabled that with
`local: true` for now.
@@ -1163,7 +1167,7 @@ it look as follows:
```html+erb
<h1>Edit article</h1>
-<%= form_with(model: @article) do |form| %>
+<%= form_with(model: @article, local: true) do |form| %>
<% if @article.errors.any? %>
<div id="error_explanation">
@@ -1766,7 +1770,7 @@ add that to the `app/views/articles/show.html.erb`.
<% end %>
<h2>Add a comment:</h2>
-<%= form_with(model: [ @article, @article.comments.build ]) do |form| %>
+<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
@@ -1832,7 +1836,7 @@ following:
<%= render @article.comments %>
<h2>Add a comment:</h2>
-<%= form_with(model: [ @article, @article.comments.build ]) do |form| %>
+<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
@@ -1862,7 +1866,7 @@ Let us also move that new comment section out to its own partial. Again, you
create a file `app/views/comments/_form.html.erb` containing:
```html+erb
-<%= form_with(model: [ @article, @article.comments.build ]) do |form| %>
+<%= form_with(model: [ @article, @article.comments.build ], local: true) do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index cb24822f86..e6aa6181cc 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -1171,7 +1171,7 @@ Conclusion
At this point you should have a good overview about how I18n support in Ruby on Rails works and are ready to start translating your project.
-If you want to discuss certain portions or have questions, please sign up to the [rails-i18n mailing list](http://groups.google.com/group/rails-i18n).
+If you want to discuss certain portions or have questions, please sign up to the [rails-i18n mailing list](https://groups.google.com/forum/#!forum/rails-i18n).
Contributing to Rails I18n
@@ -1179,7 +1179,7 @@ Contributing to Rails I18n
I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in gems and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core.
-Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](http://groups.google.com/group/rails-i18n)!)
+Thus we encourage everybody to experiment with new ideas and features in gems or other libraries and make them available to the community. (Don't forget to announce your work on our [mailing list](https://groups.google.com/forum/#!forum/rails-i18n)!)
If you find your own locale (language) missing from our [example translations data](https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale) repository for Ruby on Rails, please [_fork_](https://github.com/guides/fork-a-project-and-submit-your-modifications) the repository, add your data and send a [pull request](https://help.github.com/articles/about-pull-requests/).
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 0cfabe2a66..1541ea38cd 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -116,8 +116,6 @@ A standard Rails application depends on several gems, specifically:
* mail
* mime-types
* rack
-* rack-cache
-* rack-mount
* rack-test
* rails
* railties
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 76b325d0bf..b9b327252f 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -71,23 +71,25 @@ If we want to display the properties of all the books in our view, we can do so
<h1>Listing Books</h1>
<table>
- <tr>
- <th>Title</th>
- <th>Summary</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
-
-<% @books.each do |book| %>
- <tr>
- <td><%= book.title %></td>
- <td><%= book.content %></td>
- <td><%= link_to "Show", book %></td>
- <td><%= link_to "Edit", edit_book_path(book) %></td>
- <td><%= link_to "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %></td>
- </tr>
-<% end %>
+ <thead>
+ <tr>
+ <th>Title</th>
+ <th>Content</th>
+ <th colspan="3"></th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% @books.each do |book| %>
+ <tr>
+ <td><%= book.title %></td>
+ <td><%= book.content %></td>
+ <td><%= link_to "Show", book %></td>
+ <td><%= link_to "Edit", edit_book_path(book) %></td>
+ <td><%= link_to "Destroy", book, method: :delete, data: { confirm: "Are you sure?" } %></td>
+ </tr>
+ <% end %>
+ </tbody>
</table>
<br>
@@ -230,14 +232,14 @@ You can send an HTML string back to the browser by using the `:html` option to
`render`:
```ruby
-render html: "<strong>Not Found</strong>".html_safe
+render html: helpers.tag.strong('Not Found')
```
TIP: This is useful when you're rendering a small snippet of HTML code.
However, you might want to consider moving it to a template file if the markup
is complex.
-NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method.
+NOTE: When using `html:` option, HTML entities will be escaped if the string is not composed with `html_safe`-aware APIs.
#### Rendering JSON
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index 0f0cde7634..15073af6be 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -237,7 +237,7 @@ Finished in 0.004812s, 831.2949 runs/s, 415.6475 assertions/s.
This tells us that we don't have the necessary models (Hickwall and Wickwall) that we are trying to test.
We can easily generate these models in our "dummy" Rails application by running the following commands from the
-test/dummy directory:
+`test/dummy` directory:
```bash
$ cd test/dummy
@@ -359,7 +359,7 @@ When you run `bin/test`, you should see the tests all pass:
### Add an Instance Method
-This plugin will add a method named 'squawk' to any Active Record object that calls 'acts_as_yaffle'. The 'squawk'
+This plugin will add a method named 'squawk' to any Active Record object that calls `acts_as_yaffle`. The 'squawk'
method will simply set the value of one of the fields in the database.
To start out, write a failing test that shows the behavior you'd like:
@@ -392,7 +392,7 @@ end
```
Run the test to make sure the last two tests fail with an error that contains "NoMethodError: undefined method `squawk'",
-then update 'acts_as_yaffle.rb' to look like this:
+then update `acts_as_yaffle.rb` to look like this:
```ruby
# yaffle/lib/yaffle/acts_as_yaffle.rb
@@ -446,7 +446,7 @@ Publishing Your Gem
-------------------
Gem plugins currently in development can easily be shared from any Git repository. To share the Yaffle gem with others, simply
-commit the code to a Git repository (like GitHub) and add a line to the Gemfile of the application in question:
+commit the code to a Git repository (like GitHub) and add a line to the `Gemfile` of the application in question:
```ruby
gem "yaffle", git: "https://github.com/rails/yaffle.git"
diff --git a/guides/source/security.md b/guides/source/security.md
index d0d7e12b0a..cfa777d433 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -85,46 +85,117 @@ This will also be a good idea, if you modify the structure of an object and old
* _Critical data should not be stored in session_. If the user clears their cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data.
-### Session Storage
+### Encrypted Session Storage
NOTE: _Rails provides several storage mechanisms for the session hashes. The most important is `ActionDispatch::Session::CookieStore`._
-Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session ID. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it:
+The `CookieStore` saves the session hash directly in a cookie on the
+client-side. The server retrieves the session hash from the cookie and
+eliminates the need for a session ID. That will greatly increase the
+speed of the application, but it is a controversial storage option and
+you have to think about the security implications and storage
+limitations of it:
+
+* Cookies imply a strict size limit of 4kB. This is fine as you should
+ not store large amounts of data in a session anyway, as described
+ before. Storing the current user's database id in a session is common
+ practice.
+
+* Session cookies do not invalidate themselves and can be maliciously
+ reused. It may be a good idea to have your application invalidate old
+ session cookies using a stored timestamp.
+
+The `CookieStore` uses the
+[encrypted](http://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-encrypted)
+cookie jar to provide a secure, encrypted location to store session
+data. Cookie-based sessions thus provide both integrity as well as
+confidentiality to their contents. The encryption key, as well as the
+verification key used for
+[signed](http://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-signed)
+cookies, is derived from the `secret_key_base` configuration value.
+
+As of Rails 5.2 encrypted cookies and sessions are protected using AES
+GCM encryption. This form of encryption is a type of Authenticated
+Encryption and couples authentication and encryption in single step
+while also producing shorter ciphertexts as compared to other
+algorithms previously used. The key for cookies encrypted with AES GCM
+are derived using a salt value defined by the
+`config.action_dispatch.authenticated_encrypted_cookie_salt`
+configuration value.
+
+Prior to this version, encrypted cookies were secured using AES in CBC
+mode with HMAC using SHA1 for authentication. The keys for this type of
+encryption and for HMAC verification were derived via the salts defined
+by `config.action_dispatch.encrypted_cookie_salt` and
+`config.action_dispatch.encrypted_signed_cookie_salt` respectively.
+
+Prior to Rails version 4 in both versions 2 and 3, session cookies were
+protected using only HMAC verification. As such, these session cookies
+only provided integrity to their content because the actual session data
+was stored in plaintext encoded as base64. This is how `signed` cookies
+work in the current version of Rails. These kinds of cookies are still
+useful for protecting the integrity of certain client-stored data and
+information.
+
+__Do not use a trivial secret for the `secret_key_base`, i.e. a word
+from a dictionary, or one which is shorter than 30 characters! Instead
+use `rails secret` to generate secret keys!__
+
+It is also important to use different salt values for encrypted and
+signed cookies. Using the same value for different salt configuration
+values may lead to the same derived key being used for different
+security features which in turn may weaken the strength of the key.
+
+In test and development applications get a `secret_key_base` derived from the app name. Other environments must use a random key present in `config/credentials.yml.enc`, shown here in its decrypted state:
+
+ secret_key_base: 492f...
-* Cookies imply a strict size limit of 4kB. This is fine as you should not store large amounts of data in a session anyway, as described before. _Storing the current user's database id in a session is usually ok_.
+If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret.
-* The client can see everything you store in a session, because it is stored in clear-text (actually Base64-encoded, so not encrypted). So, of course, _you don't want to store any secrets here_. To prevent session hash tampering, a digest is calculated from the session with a server-side secret (`secrets.secret_token`) and inserted into the end of the cookie.
+### Rotating Encrypted and Signed Cookies Configurations
-In Rails 4, encrypted cookies through AES in CBC mode with HMAC using SHA1 for
-verification was introduced. This prevents the user from accessing and tampering
-the content of the cookie. Thus the session becomes a more secure place to store
-data. The encryption is performed using a server-side `secrets.secret_key_base`.
-Two salts are used when deriving keys for encryption and verification. These
-salts are set via the `config.action_dispatch.encrypted_cookie_salt` and
-`config.action_dispatch.encrypted_signed_cookie_salt` configuration values.
+Rotation is ideal for changing cookie configurations and ensuring old cookies
+aren't immediately invalid. Your users then have a chance to visit your site,
+get their cookie read with an old configuration and have it rewritten with the
+new change. The rotation can then be removed once you're comfortable enough
+users have had their chance to get their cookies upgraded.
-Rails 5.2 uses AES-GCM for the encryption which couples authentication
-and encryption in one faster step and produces shorter ciphertexts.
+It's possible to rotate the ciphers and digests used for encrypted and signed cookies.
-Encrypted cookies are automatically upgraded if the
-`config.action_dispatch.use_authenticated_cookie_encryption` is enabled.
+For instance to change the digest used for signed cookies from SHA1 to SHA256,
+you would first assign the new configuration value:
-_Do not use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters! Instead use `rails secret` to generate secret keys!_
+```ruby
+Rails.application.config.action_dispatch.signed_cookie_digest = "SHA256"
+```
-Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.:
+Now add a rotation for the old SHA1 digest so existing cookies are
+seamlessly upgraded to the new SHA256 digest.
- development:
- secret_key_base: a75d...
+```ruby
+Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
+ cookies.rotate :signed, digest: "SHA1"
+end
+```
- test:
- secret_key_base: 492f...
+Then any written signed cookies will be digested with SHA256. Old cookies
+that were written with SHA1 can still be read, and if accessed will be written
+with the new digest so they're upgraded and won't be invalid when you remove the
+rotation.
- production:
- secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
+Once users with SHA1 digested signed cookies should no longer have a chance to
+have their cookies rewritten, remove the rotation.
-Older versions of Rails use CookieStore, which uses `secret_token` instead of `secret_key_base` that is used by EncryptedCookieStore. Read the upgrade documentation for more information.
+While you can setup as many rotations as you'd like it's not common to have many
+rotations going at any one time.
-If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret.
+For more details on key rotation with encrypted and signed messages as
+well as the various options the `rotate` method accepts, please refer to
+the
+[MessageEncryptor API](api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html)
+and
+[MessageVerifier API](api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html)
+documentation.
### Replay Attacks for CookieStore Sessions
@@ -189,7 +260,7 @@ class Session < ApplicationRecord
end
```
-The section about session fixation introduced the problem of maintained sessions. An attacker maintaining a session every five minutes can keep the session alive forever, although you are expiring sessions. A simple solution for this would be to add a created_at column to the sessions table. Now you can delete sessions that were created a long time ago. Use this line in the sweep method above:
+The section about session fixation introduced the problem of maintained sessions. An attacker maintaining a session every five minutes can keep the session alive forever, although you are expiring sessions. A simple solution for this would be to add a `created_at` column to the sessions table. Now you can delete sessions that were created a long time ago. Use this line in the sweep method above:
```ruby
delete_all "updated_at < '#{time.ago.to_s(:db)}' OR
@@ -465,7 +536,7 @@ Depending on your web application, there may be more ways to hijack the user's a
INFO: _A CAPTCHA is a challenge-response test to determine that the response is not generated by a computer. It is often used to protect registration forms from attackers and comment forms from automatic spam bots by asking the user to type the letters of a distorted image. This is the positive CAPTCHA, but there is also the negative CAPTCHA. The idea of a negative CAPTCHA is not for a user to prove that they are human, but reveal that a robot is a robot._
-A popular positive CAPTCHA API is [reCAPTCHA](http://recaptcha.net/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API.
+A popular positive CAPTCHA API is [reCAPTCHA](https://developers.google.com/recaptcha/) which displays two distorted images of words from old books. It also adds an angled line, rather than a distorted background and high levels of warping on the text as earlier CAPTCHAs did, because the latter were broken. As a bonus, using reCAPTCHA helps to digitize old books. [ReCAPTCHA](https://github.com/ambethia/recaptcha/) is also a Rails plug-in with the same name as the API.
You will get two keys from the API, a public and a private key, which you have to put into your Rails environment. After that you can use the recaptcha_tags method in the view, and the verify_recaptcha method in the controller. Verify_recaptcha will return false if the validation fails.
The problem with CAPTCHAs is that they have a negative impact on the user experience. Additionally, some visually impaired users have found certain kinds of distorted CAPTCHAs difficult to read. Still, positive CAPTCHAs are one of the best methods to prevent all kinds of bots from submitting forms.
@@ -1032,27 +1103,33 @@ Environmental Security
It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, and your server-side secret, e.g. stored in `config/secrets.yml`. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information.
-### Custom secrets
+### Custom credentials
+
+Rails generates a `config/credentials.yml.enc` to store third-party credentials
+within the repo. This is only viable because Rails encrypts the file with a master
+key that's generated into a version control ignored `config/master.key` — Rails
+will also look for that key in `ENV["RAILS_MASTER_KEY"]`. Rails also requires the
+key to boot in production, so the credentials can be read.
+
+To edit stored credentials use `bin/rails credentials:edit`.
-Rails generates a `config/secrets.yml`. By default, this file contains the
-application's `secret_key_base`, but it could also be used to store other
-secrets such as access keys for external APIs.
+By default, this file contains the application's
+`secret_key_base`, but it could also be used to store other credentials such as
+access keys for external APIs.
-The secrets added to this file are accessible via `Rails.application.secrets`.
-For example, with the following `config/secrets.yml`:
+The credentials added to this file are accessible via `Rails.application.credentials`.
+For example, with the following decrypted `config/credentials.yml.enc`:
- development:
- secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
- some_api_key: SOMEKEY
+ secret_key_base: 3b7cd727ee24e8444053437c36cc66c3
+ some_api_key: SOMEKEY
-`Rails.application.secrets.some_api_key` returns `SOMEKEY` in the development
-environment.
+`Rails.application.credentials.some_api_key` returns `SOMEKEY` in any environment.
If you want an exception to be raised when some key is blank, use the bang
version:
```ruby
-Rails.application.secrets.some_api_key! # => raises KeyError: key not found: :some_api_key
+Rails.application.credentials.some_api_key! # => raises KeyError: :some_api_key is blank
```
Additional Resources
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 4ee3267261..c5b2a694e7 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -1513,7 +1513,7 @@ class BillingJobTest < ActiveJob::TestCase
end
```
-This test is pretty simple and only asserts that the job get the work done
+This test is pretty simple and only asserts that the job got the work done
as expected.
By default, `ActiveJob::TestCase` will set the queue adapter to `:test` so that
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index d932fc8d8f..9bc87e4bf0 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -27,7 +27,7 @@ The process should go as follows:
3. Fix tests and deprecated features.
4. Move to the latest patch version of the next minor version.
-Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the Gemfile (and possibly other gem versions) and run `bundle update`. Then run the Update task mentioned below to update configuration files, then run your tests.
+Repeat this process until you reach your target Rails version. Each time you move versions, you will need to change the Rails version number in the `Gemfile` (and possibly other gem versions) and run `bundle update`. Then run the Update task mentioned below to update configuration files, then run your tests.
You can find a list of all released Rails versions [here](https://rubygems.org/gems/rails/versions).
@@ -411,7 +411,7 @@ Upgrading from Rails 4.1 to Rails 4.2
### Web Console
-First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your Gemfile and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment.
+First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your `Gemfile` and run `bundle install` (it won't have been included when you upgraded Rails). Once it's been installed, you can simply drop a reference to the console helper (i.e., `<%= console %>`) into any view you want to enable it for. A console will also be provided on any error page you view in your development environment.
### Responders
@@ -1136,7 +1136,7 @@ full support for the last few changes in the specification.
### Gemfile
Rails 4.0 removed the `assets` group from Gemfile. You'd need to remove that
-line from your Gemfile when upgrading. You should also update your application
+line from your `Gemfile` when upgrading. You should also update your application
file (in `config/application.rb`):
```ruby
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index 27cef2bd27..b2716c7faa 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -174,7 +174,7 @@ passing the `:local` option `form_with`.
This will generate the following HTML:
```html
-<form action="/articles" method="post" data-remote="true">
+<form action="/articles" accept-charset="UTF-8" method="post" data-remote="true">
...
</form>
```
@@ -382,7 +382,7 @@ Rails 5.1 introduced rails-ujs and dropped jQuery as a dependency.
As a result the Unobtrusive JavaScript (UJS) driver has been rewritten to operate without jQuery.
These introductions cause small changes to `custom events` fired during the request:
-NOTE: Signature of calls to UJS’s event handlers has changed.
+NOTE: Signature of calls to UJS's event handlers has changed.
Unlike the version with jQuery, all custom events return only one parameter: `event`.
In this parameter, there is an additional attribute `detail` which contains an array of extra parameters.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index e61adda7c3..20603dedaf 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,3 +1,17 @@
+* Gemfile for new apps: upgrade redis-rb from ~> 3.0 to 4.0.
+
+ *Jeremy Daer*
+
+* Add `mini_magick` to default `Gemfile` as comment.
+
+ *Yoshiyuki Hirano*
+
+* Derive `secret_key_base` from the app name in development and test environments.
+
+ Spares away needless secret configs.
+
+ *DHH*, *Kasper Timm Hansen*
+
* Support multiple versions arguments for `gem` method of Generators.
*Yoshiyuki Hirano*
diff --git a/railties/Rakefile b/railties/Rakefile
index d41c6e7438..74615d2358 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -11,19 +11,61 @@ task test: "test:isolated"
namespace :test do
task :isolated do
+ dash_i = [
+ "test",
+ "lib",
+ "../activesupport/lib",
+ "../actionpack/lib",
+ "../actionview/lib",
+ "../activemodel/lib"
+ ].map { |dir| File.expand_path(dir, __dir__) }
+
+ dash_i.reverse_each do |x|
+ $:.unshift(x) unless $:.include?(x)
+ end
+ $-w = true
+
+ require "bundler/setup" unless defined?(Bundler)
+ require "active_support"
+
+ failing_files = []
+
dirs = (ENV["TEST_DIR"] || ENV["TEST_DIRS"] || "**").split(",")
test_files = dirs.map { |dir| "test/#{dir}/*_test.rb" }
Dir[*test_files].each do |file|
- next true if file.include?("fixtures")
- dash_i = [
- "test",
- "lib",
- "#{__dir__}/../activesupport/lib",
- "#{__dir__}/../actionpack/lib",
- "#{__dir__}/../actionview/lib",
- "#{__dir__}/../activemodel/lib"
- ]
- ruby "-w", "-I#{dash_i.join ':'}", file
+ next true if file.start_with?("test/fixtures/")
+
+ fake_command = Shellwords.join([
+ FileUtils::RUBY,
+ "-w",
+ *dash_i.map { |dir| "-I#{Pathname.new(dir).relative_path_from(Pathname.pwd)}" },
+ file,
+ ])
+ puts fake_command
+
+ # We could run these in parallel, but pretty much all of the
+ # railties tests already run in parallel, so ¯\_(⊙︿⊙)_/¯
+ Process.waitpid fork {
+ ARGV.clear
+ Rake.application = nil
+
+ load file
+ }
+
+ unless $?.success?
+ failing_files << file
+ end
+ end
+
+ unless failing_files.empty?
+ puts
+ puts "Failed in:"
+ failing_files.each do |file|
+ puts " #{file}"
+ end
+ puts
+
+ raise "Failure in isolated test runner"
end
end
end
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 04bb9ba94a..092105d502 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "rails/ruby_version_check"
+require "rails/ruby_version_check"
require "pathname"
@@ -11,8 +11,8 @@ require "active_support/core_ext/module/delegation"
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/object/blank"
-require_relative "rails/application"
-require_relative "rails/version"
+require "rails/application"
+require "rails/version"
require "active_support/railtie"
require "action_dispatch/railtie"
@@ -50,7 +50,7 @@ module Rails
def backtrace_cleaner
@backtrace_cleaner ||= begin
# Relies on Active Support, so we have to lazy load to postpone definition until Active Support has been loaded
- require_relative "rails/backtrace_cleaner"
+ require "rails/backtrace_cleaner"
Rails::BacktraceCleaner.new
end
end
diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb
index 184f5b14f1..e7f0557584 100644
--- a/railties/lib/rails/api/task.rb
+++ b/railties/lib/rails/api/task.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "rdoc/task"
-require_relative "generator"
+require "rails/api/generator"
module Rails
module API
diff --git a/railties/lib/rails/app_loader.rb b/railties/lib/rails/app_loader.rb
index 3e9b3bd4bb..20eb75d95c 100644
--- a/railties/lib/rails/app_loader.rb
+++ b/railties/lib/rails/app_loader.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "pathname"
-require_relative "version"
+require "rails/version"
module Rails
module AppLoader # :nodoc:
@@ -56,7 +56,7 @@ EOS
$stderr.puts(BUNDLER_WARNING)
Object.const_set(:APP_PATH, File.expand_path("config/application", Dir.pwd))
require File.expand_path("../boot", APP_PATH)
- require_relative "commands"
+ require "rails/commands"
break
end
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 72f8bf0e14..ade8cb6a48 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -5,8 +5,10 @@ require "active_support/core_ext/hash/keys"
require "active_support/core_ext/object/blank"
require "active_support/key_generator"
require "active_support/message_verifier"
-require_relative "engine"
-require_relative "secrets"
+require "active_support/encrypted_configuration"
+require "active_support/deprecation"
+require "rails/engine"
+require "rails/secrets"
module Rails
# An Engine with the responsibility of coordinating the whole boot process.
@@ -171,12 +173,9 @@ module Rails
# number of iterations selected based on consultation with the google security
# team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220
@caching_key_generator ||=
- if secrets.secret_key_base
- unless secrets.secret_key_base.kind_of?(String)
- raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String, change this value in `config/secrets.yml`"
- end
- key_generator = ActiveSupport::KeyGenerator.new(secrets.secret_key_base, iterations: 1000)
- ActiveSupport::CachingKeyGenerator.new(key_generator)
+ if secret_key_base
+ ActiveSupport::CachingKeyGenerator.new \
+ ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
else
ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token)
end
@@ -246,13 +245,11 @@ module Rails
# will be used by middlewares and engines to configure themselves.
def env_config
@app_env_config ||= begin
- validate_secret_key_config!
-
super.merge(
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.redirect_filter" => config.filter_redirect,
"action_dispatch.secret_token" => secrets.secret_token,
- "action_dispatch.secret_key_base" => secrets.secret_key_base,
+ "action_dispatch.secret_key_base" => secret_key_base,
"action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
"action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
"action_dispatch.logger" => Rails.logger,
@@ -263,8 +260,12 @@ module Rails
"action_dispatch.encrypted_cookie_salt" => config.action_dispatch.encrypted_cookie_salt,
"action_dispatch.encrypted_signed_cookie_salt" => config.action_dispatch.encrypted_signed_cookie_salt,
"action_dispatch.authenticated_encrypted_cookie_salt" => config.action_dispatch.authenticated_encrypted_cookie_salt,
+ "action_dispatch.use_authenticated_cookie_encryption" => config.action_dispatch.use_authenticated_cookie_encryption,
+ "action_dispatch.encrypted_cookie_cipher" => config.action_dispatch.encrypted_cookie_cipher,
+ "action_dispatch.signed_cookie_digest" => config.action_dispatch.signed_cookie_digest,
"action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer,
- "action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest
+ "action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest,
+ "action_dispatch.cookies_rotations" => config.action_dispatch.cookies_rotations
)
end
end
@@ -398,6 +399,11 @@ module Rails
# Fallback to config.secret_token if secrets.secret_token isn't set
secrets.secret_token ||= config.secret_token
+ if secrets.secret_token.present?
+ ActiveSupport::Deprecation.warn \
+ "`secrets.secret_token` is deprecated in favor of `secret_key_base` and will be removed in Rails 6.0."
+ end
+
secrets
end
end
@@ -406,6 +412,33 @@ module Rails
@secrets = secrets
end
+ # The secret_key_base is used as the input secret to the application's key generator, which in turn
+ # is used to create all MessageVerifiers/MessageEncryptors, including the ones that sign and encrypt cookies.
+ #
+ # In test and development, this is simply derived as a MD5 hash of the application's name.
+ #
+ # In all other environments, we look for it first in ENV["SECRET_KEY_BASE"],
+ # then credentials.secret_key_base, and finally secrets.secret_key_base. For most applications,
+ # the correct place to store it is in the encrypted credentials file.
+ def secret_key_base
+ if Rails.env.test? || Rails.env.development?
+ Digest::MD5.hexdigest self.class.name
+ else
+ validate_secret_key_base \
+ ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base
+ end
+ end
+
+ # Decrypts the credentials hash as kept in `config/credentials.yml.enc`. This file is encrypted with
+ # the Rails master key, which is either taken from ENV["RAILS_MASTER_KEY"] or from loading
+ # `config/master.key`.
+ def credentials
+ @credentials ||= ActiveSupport::EncryptedConfiguration.new \
+ config_path: Rails.root.join("config/credentials.yml.enc"),
+ key_path: Rails.root.join("config/master.key"),
+ env_key: "RAILS_MASTER_KEY"
+ end
+
def to_app #:nodoc:
self
end
@@ -441,7 +474,7 @@ module Rails
def run_tasks_blocks(app) #:nodoc:
railties.each { |r| r.run_tasks_blocks(app) }
super
- require_relative "tasks"
+ require "rails/tasks"
task :environment do
ActiveSupport.on_load(:before_initialize) { config.eager_load = false }
@@ -504,14 +537,13 @@ module Rails
default_stack.build_stack
end
- def validate_secret_key_config! #:nodoc:
- if secrets.secret_key_base.blank?
- ActiveSupport::Deprecation.warn "You didn't set `secret_key_base`. " \
- "Read the upgrade documentation to learn more about this new config option."
-
- if secrets.secret_token.blank?
- raise "Missing `secret_key_base` for '#{Rails.env}' environment, set this value in `config/secrets.yml`"
- end
+ def validate_secret_key_base(secret_key_base)
+ if secret_key_base.is_a?(String) && secret_key_base.present?
+ secret_key_base
+ elsif secret_key_base
+ raise ArgumentError, "`secret_key_base` for #{Rails.env} environment must be a type of String`"
+ elsif secrets.secret_token.blank?
+ raise ArgumentError, "Missing `secret_key_base` for '#{Rails.env}' environment, set this string with `rails credentials:edit`"
end
end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index c24d4573a9..e3c0759f95 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -4,7 +4,7 @@ require "fileutils"
require "active_support/notifications"
require "active_support/dependencies"
require "active_support/descendants_tracker"
-require_relative "../secrets"
+require "rails/secrets"
module Rails
class Application
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index b65289177f..290ec13878 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -2,8 +2,8 @@
require "active_support/core_ext/kernel/reporting"
require "active_support/file_update_checker"
-require_relative "../engine/configuration"
-require_relative "../source_annotation_extractor"
+require "rails/engine/configuration"
+require "rails/source_annotation_extractor"
module Rails
class Application
diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb
index 50a2ed30cf..e56e604fdc 100644
--- a/railties/lib/rails/cli.rb
+++ b/railties/lib/rails/cli.rb
@@ -1,15 +1,15 @@
# frozen_string_literal: true
-require_relative "app_loader"
+require "rails/app_loader"
# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.
Rails::AppLoader.exec_app
-require_relative "ruby_version_check"
+require "rails/ruby_version_check"
Signal.trap("INT") { puts; exit(1) }
-require_relative "command"
+require "rails/command"
if ARGV.first == "plugin"
ARGV.shift
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 7ceb86198f..9c447c366f 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "code_statistics_calculator"
+require "rails/code_statistics_calculator"
require "active_support/core_ext/enumerable"
class CodeStatistics #:nodoc:
diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb
index 8df4f98d3e..fa462ef7e9 100644
--- a/railties/lib/rails/command/base.rb
+++ b/railties/lib/rails/command/base.rb
@@ -6,7 +6,7 @@ require "erb"
require "active_support/core_ext/string/filters"
require "active_support/core_ext/string/inflections"
-require_relative "actions"
+require "rails/command/actions"
module Rails
module Command
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index 1aea1e1a96..77961a0292 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "command"
+require "rails/command"
aliases = {
"g" => "generate",
diff --git a/railties/lib/rails/commands/application/application_command.rb b/railties/lib/rails/commands/application/application_command.rb
index 13d47a63bc..f77553b830 100644
--- a/railties/lib/rails/commands/application/application_command.rb
+++ b/railties/lib/rails/commands/application/application_command.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../../generators"
-require_relative "../../generators/rails/app/app_generator"
+require "rails/generators"
+require "rails/generators/rails/app/app_generator"
module Rails
module Generators
diff --git a/railties/lib/rails/commands/console/console_command.rb b/railties/lib/rails/commands/console/console_command.rb
index 5dc695c240..e35faa5b01 100644
--- a/railties/lib/rails/commands/console/console_command.rb
+++ b/railties/lib/rails/commands/console/console_command.rb
@@ -3,7 +3,7 @@
require "irb"
require "irb/completion"
-require_relative "../../command/environment_argument"
+require "rails/command/environment_argument"
module Rails
class Console
diff --git a/railties/lib/rails/commands/credentials/USAGE b/railties/lib/rails/commands/credentials/USAGE
new file mode 100644
index 0000000000..85877c71b7
--- /dev/null
+++ b/railties/lib/rails/commands/credentials/USAGE
@@ -0,0 +1,40 @@
+=== Storing Encrypted Credentials in Source Control
+
+The Rails `credentials` commands provide access to encrypted credentials,
+so you can safely store access tokens, database passwords, and the like
+safely inside the app without relying on a mess of ENVs.
+
+This also allows for atomic deploys: no need to coordinate key changes
+to get everything working as the keys are shipped with the code.
+
+=== Setup
+
+Applications after Rails 5.2 automatically have a basic credentials file generated
+that just contains the secret_key_base used by MessageVerifiers/MessageEncryptors, like the ones
+signing and encrypting cookies.
+
+For applications created prior to Rails 5.2, we'll automatically generate a new
+credentials file in `config/credentials.yml.enc` the first time you run `bin/rails credentials:edit`.
+If you didn't have a master key saved in `config/master.key`, that'll be created too.
+
+Don't lose this master key! Put it in a password manager your team can access.
+Should you lose it no one, including you, will be able to access any encrypted
+credentials.
+
+Don't commit the key! Add `config/master.key` to your source control's
+ignore file. If you use Git, Rails handles this for you.
+
+Rails also looks for the master key in `ENV["RAILS_MASTER_KEY"]`, if that's easier to manage.
+
+You could prepend that to your server's start command like this:
+
+ RAILS_MASTER_KEY="very-secret-and-secure" server.start
+
+=== Editing Credentials
+
+This will open a temporary file in `$EDITOR` with the decrypted contents to edit
+the encrypted credentials.
+
+When the temporary file is next saved the contents are encrypted and written to
+`config/credentials.yml.enc` while the file itself is destroyed to prevent credentials
+from leaking.
diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb
new file mode 100644
index 0000000000..1ef7c1f343
--- /dev/null
+++ b/railties/lib/rails/commands/credentials/credentials_command.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require "active_support"
+
+module Rails
+ module Command
+ class CredentialsCommand < Rails::Command::Base # :nodoc:
+ no_commands do
+ def help
+ say "Usage:\n #{self.class.banner}"
+ say ""
+ say self.class.desc
+ end
+ end
+
+ def edit
+ require_application_and_environment!
+
+ ensure_editor_available || (return)
+ ensure_master_key_has_been_added
+ ensure_credentials_have_been_added
+
+ change_credentials_in_system_editor
+
+ say "New credentials encrypted and saved."
+ rescue Interrupt
+ say "Aborted changing credentials: nothing saved."
+ rescue ActiveSupport::EncryptedFile::MissingKeyError => error
+ say error.message
+ end
+
+ def show
+ require_application_and_environment!
+ say Rails.application.credentials.read.presence ||
+ "No credentials have been added yet. Use bin/rails credentials:edit to change that."
+ end
+
+ private
+ def ensure_editor_available
+ if ENV["EDITOR"].to_s.empty?
+ say "No $EDITOR to open credentials in. Assign one like this:"
+ say ""
+ say %(EDITOR="mate --wait" bin/rails credentials:edit)
+ say ""
+ say "For editors that fork and exit immediately, it's important to pass a wait flag,"
+ say "otherwise the credentials will be saved immediately with no chance to edit."
+
+ false
+ else
+ true
+ end
+ end
+
+ def ensure_master_key_has_been_added
+ master_key_generator.add_master_key_file
+ end
+
+ def ensure_credentials_have_been_added
+ credentials_generator.add_credentials_file_silently
+ end
+
+ def change_credentials_in_system_editor
+ Rails.application.credentials.change do |tmp_path|
+ system("#{ENV["EDITOR"]} #{tmp_path}")
+ end
+ end
+
+
+ def master_key_generator
+ require "rails/generators"
+ require "rails/generators/rails/master_key/master_key_generator"
+
+ Rails::Generators::MasterKeyGenerator.new
+ end
+
+ def credentials_generator
+ require "rails/generators"
+ require "rails/generators/rails/credentials/credentials_generator"
+
+ Rails::Generators::CredentialsGenerator.new
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
index 5234969743..8df548b5de 100644
--- a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
+++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../command/environment_argument"
+require "rails/command/environment_argument"
module Rails
class DBConsole
diff --git a/railties/lib/rails/commands/destroy/destroy_command.rb b/railties/lib/rails/commands/destroy/destroy_command.rb
index 686193ddb9..dd432d28fd 100644
--- a/railties/lib/rails/commands/destroy/destroy_command.rb
+++ b/railties/lib/rails/commands/destroy/destroy_command.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../generators"
+require "rails/generators"
module Rails
module Command
diff --git a/railties/lib/rails/commands/generate/generate_command.rb b/railties/lib/rails/commands/generate/generate_command.rb
index 73f627637d..93d7a0ce3a 100644
--- a/railties/lib/rails/commands/generate/generate_command.rb
+++ b/railties/lib/rails/commands/generate/generate_command.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../generators"
+require "rails/generators"
module Rails
module Command
diff --git a/railties/lib/rails/commands/plugin/plugin_command.rb b/railties/lib/rails/commands/plugin/plugin_command.rb
index 5d3dfadf84..2b192abf9b 100644
--- a/railties/lib/rails/commands/plugin/plugin_command.rb
+++ b/railties/lib/rails/commands/plugin/plugin_command.rb
@@ -36,8 +36,8 @@ module Rails
private
def run_plugin_generator(plugin_args)
- require_relative "../../generators"
- require_relative "../../generators/rails/plugin/plugin_generator"
+ require "rails/generators"
+ require "rails/generators/rails/plugin/plugin_generator"
Rails::Generators::PluginGenerator.start plugin_args
end
end
diff --git a/railties/lib/rails/commands/runner/runner_command.rb b/railties/lib/rails/commands/runner/runner_command.rb
index cd9462e08f..30fbf04982 100644
--- a/railties/lib/rails/commands/runner/runner_command.rb
+++ b/railties/lib/rails/commands/runner/runner_command.rb
@@ -32,13 +32,13 @@ module Rails
ARGV.replace(command_argv)
if code_or_file == "-"
- eval($stdin.read, binding, "stdin")
+ eval($stdin.read, TOPLEVEL_BINDING, "stdin")
elsif File.exist?(code_or_file)
$0 = code_or_file
Kernel.load code_or_file
else
begin
- eval(code_or_file, binding, __FILE__, __LINE__)
+ eval(code_or_file, TOPLEVEL_BINDING, __FILE__, __LINE__)
rescue SyntaxError, NameError => error
$stderr.puts "Please specify a valid ruby command or the path of a script to run."
$stderr.puts "Run '#{self.class.executable} -h' for help."
diff --git a/railties/lib/rails/commands/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb
index d93c4de74e..c91139e33b 100644
--- a/railties/lib/rails/commands/secrets/secrets_command.rb
+++ b/railties/lib/rails/commands/secrets/secrets_command.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support"
-require_relative "../../secrets"
+require "rails/secrets"
module Rails
module Command
@@ -56,8 +56,8 @@ module Rails
private
def generator
- require_relative "../../generators"
- require_relative "../../generators/rails/encrypted_secrets/encrypted_secrets_generator"
+ require "rails/generators"
+ require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
Rails::Generators::EncryptedSecretsGenerator
end
diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb
index 785265d766..703ec59087 100644
--- a/railties/lib/rails/commands/server/server_command.rb
+++ b/railties/lib/rails/commands/server/server_command.rb
@@ -6,7 +6,7 @@ require "action_dispatch"
require "rails"
require "active_support/deprecation"
require "active_support/core_ext/string/filters"
-require_relative "../../dev_caching"
+require "rails/dev_caching"
module Rails
class Server < ::Rack::Server
@@ -127,6 +127,7 @@ module Rails
class_option "dev-caching", aliases: "-C", type: :boolean, default: nil,
desc: "Specifies whether to perform caching in development."
class_option "restart", type: :boolean, default: nil, hide: true
+ class_option "early_hints", type: :boolean, default: nil, desc: "Enables HTTP/2 early hints."
def initialize(args = [], local_options = {}, config = {})
@original_options = local_options
@@ -161,7 +162,8 @@ module Rails
daemonize: options[:daemon],
pid: pid,
caching: options["dev-caching"],
- restart_cmd: restart_command
+ restart_cmd: restart_command,
+ early_hints: early_hints
}
end
end
@@ -227,6 +229,10 @@ module Rails
"bin/rails server #{@server} #{@original_options.join(" ")} --restart"
end
+ def early_hints
+ options[:early_hints]
+ end
+
def pid
File.expand_path(options[:pid])
end
diff --git a/railties/lib/rails/commands/test/test_command.rb b/railties/lib/rails/commands/test/test_command.rb
index a2216553ca..00ea9ac4a6 100644
--- a/railties/lib/rails/commands/test/test_command.rb
+++ b/railties/lib/rails/commands/test/test_command.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
-require_relative "../../command"
-require_relative "../../test_unit/runner"
-require_relative "../../test_unit/reporter"
+require "rails/command"
+require "rails/test_unit/runner"
+require "rails/test_unit/reporter"
module Rails
module Command
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index 70815d114d..d3a54d9364 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -2,8 +2,8 @@
require "active_support/ordered_options"
require "active_support/core_ext/object"
-require_relative "paths"
-require_relative "rack"
+require "rails/paths"
+require "rails/rack"
module Rails
module Configuration
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index cc2030d37d..6a13a84108 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "railtie"
-require_relative "engine/railties"
+require "rails/railtie"
+require "rails/engine/railties"
require "active_support/core_ext/module/delegation"
require "pathname"
require "thread"
@@ -439,8 +439,8 @@ module Rails
# Load console and invoke the registered hooks.
# Check <tt>Rails::Railtie.console</tt> for more info.
def load_console(app = self)
- require_relative "console/app"
- require_relative "console/helpers"
+ require "rails/console/app"
+ require "rails/console/helpers"
run_console_blocks(app)
self
end
@@ -463,7 +463,7 @@ module Rails
# Load Rails generators and invoke the registered hooks.
# Check <tt>Rails::Railtie.generators</tt> for more info.
def load_generators(app = self)
- require_relative "generators"
+ require "rails/generators"
run_generators_blocks(app)
Rails::Generators.configure!(app.config.generators)
self
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index 3854907507..05218640c6 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -6,4 +6,4 @@ unless defined?(APP_PATH)
end
end
-require_relative "../commands"
+require "rails/commands"
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 16ba7f9eb8..6bf0406b21 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../railtie/configuration"
+require "rails/railtie/configuration"
module Rails
class Engine
diff --git a/railties/lib/rails/engine/updater.rb b/railties/lib/rails/engine/updater.rb
index 21a0fc5562..be7a47124a 100644
--- a/railties/lib/rails/engine/updater.rb
+++ b/railties/lib/rails/engine/updater.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../generators"
-require_relative "../generators/rails/plugin/plugin_generator"
+require "rails/generators"
+require "rails/generators/rails/plugin/plugin_generator"
module Rails
class Engine
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index a630d55e59..2d265818f7 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -4,7 +4,7 @@ activesupport_path = File.expand_path("../../../activesupport/lib", __dir__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require "thor/group"
-require_relative "command"
+require "rails/command"
require "active_support"
require "active_support/core_ext/object/blank"
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index c8688fb7f3..bc00adabae 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -3,10 +3,10 @@
require "fileutils"
require "digest/md5"
require "active_support/core_ext/string/strip"
-require_relative "../version" unless defined?(Rails::VERSION)
+require "rails/version" unless defined?(Rails::VERSION)
require "open-uri"
require "uri"
-require_relative "../generators"
+require "rails/generators"
require "active_support/core_ext/array/extract_options"
module Rails
@@ -369,7 +369,7 @@ module Rails
return [] if options[:skip_action_cable]
comment = "Use Redis adapter to run Action Cable in production"
gems = []
- gems << GemfileEntry.new("redis", "~> 3.0", comment, {}, true)
+ gems << GemfileEntry.new("redis", "~> 4.0", comment, {}, true)
gems
end
diff --git a/railties/lib/rails/generators/css/assets/assets_generator.rb b/railties/lib/rails/generators/css/assets/assets_generator.rb
index 5f7be769b2..f657d1e50f 100644
--- a/railties/lib/rails/generators/css/assets/assets_generator.rb
+++ b/railties/lib/rails/generators/css/assets/assets_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../named_base"
+require "rails/generators/named_base"
module Css # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb
index 5996cb1483..89c560f382 100644
--- a/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/css/scaffold/scaffold_generator.rb
@@ -1,17 +1,17 @@
# frozen_string_literal: true
-require_relative "../../named_base"
+require "rails/generators/named_base"
module Css # :nodoc:
module Generators # :nodoc:
class ScaffoldGenerator < Rails::Generators::NamedBase # :nodoc:
+ source_root Rails::Generators::ScaffoldGenerator.source_root
+
# In order to allow the Sass generators to pick up the default Rails CSS and
# transform it, we leave it in a standard location for the CSS stylesheet
# generators to handle. For the simple, default case, just copy it over.
def copy_stylesheet
- dir = Rails::Generators::ScaffoldGenerator.source_root
- file = File.join(dir, "scaffold.css")
- create_file "app/assets/stylesheets/scaffold.css", File.read(file)
+ copy_file "scaffold.css", "app/assets/stylesheets/scaffold.css"
end
end
end
diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb
index 2c20834611..ba20bcd32a 100644
--- a/railties/lib/rails/generators/erb.rb
+++ b/railties/lib/rails/generators/erb.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "named_base"
+require "rails/generators/named_base"
module Erb # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/erb/controller/controller_generator.rb b/railties/lib/rails/generators/erb/controller/controller_generator.rb
index 1a6c84288b..8e13744b2a 100644
--- a/railties/lib/rails/generators/erb/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/erb/controller/controller_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../erb"
+require "rails/generators/erb"
module Erb # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index 5774d86c8e..e2ea66415f 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../erb"
+require "rails/generators/erb"
module Erb # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
index e80c6d4b7d..2fc04e4094 100644
--- a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../../erb"
-require_relative "../../resource_helpers"
+require "rails/generators/erb"
+require "rails/generators/resource_helpers"
module Erb # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index 4f2e84f924..0eb9d82bbb 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -1,4 +1,4 @@
-<%%= form_with(model: <%= singular_table_name %>, local: true) do |form| %>
+<%%= form_with(model: <%= model_resource_name %>, local: true) do |form| %>
<%% if <%= singular_table_name %>.errors.any? %>
<div id="error_explanation">
<h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
index 5f4904fee1..e1ede7c713 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -18,9 +18,9 @@
<% attributes.reject(&:password_digest?).each do |attribute| -%>
<td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
<% end -%>
- <td><%%= link_to 'Show', <%= singular_table_name %> %></td>
- <td><%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>) %></td>
- <td><%%= link_to 'Destroy', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
+ <td><%%= link_to 'Show', <%= model_resource_name %> %></td>
+ <td><%%= link_to 'Edit', edit_<%= singular_route_name %>_path(<%= singular_table_name %>) %></td>
+ <td><%%= link_to 'Destroy', <%= model_resource_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<%% end %>
</tbody>
@@ -28,4 +28,4 @@
<br>
-<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_table_name %>_path %>
+<%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_route_name %>_path %>
diff --git a/railties/lib/rails/generators/js/assets/assets_generator.rb b/railties/lib/rails/generators/js/assets/assets_generator.rb
index ea151aa04e..9d32c666dc 100644
--- a/railties/lib/rails/generators/js/assets/assets_generator.rb
+++ b/railties/lib/rails/generators/js/assets/assets_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../named_base"
+require "rails/generators/named_base"
module Js # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index 7162b8c0b4..1cbccfe461 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require "active_support/concern"
-require_relative "actions/create_migration"
+require "rails/generators/actions/create_migration"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/model_helpers.rb b/railties/lib/rails/generators/model_helpers.rb
index aa3564476a..50078404b3 100644
--- a/railties/lib/rails/generators/model_helpers.rb
+++ b/railties/lib/rails/generators/model_helpers.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "active_model"
+require "rails/generators/active_model"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index fe8447be23..44f5ab45d3 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
require "active_support/core_ext/module/introspection"
-require_relative "base"
-require_relative "generated_attribute"
+require "rails/generators/base"
+require "rails/generators/generated_attribute"
module Rails
module Generators
@@ -100,11 +100,11 @@ module Rails
end
def index_helper # :doc:
- uncountable? ? "#{plural_table_name}_index" : plural_table_name
+ uncountable? ? "#{plural_route_name}_index" : plural_route_name
end
def show_helper # :doc:
- "#{singular_table_name}_url(@#{singular_table_name})"
+ "#{singular_route_name}_url(@#{singular_table_name})"
end
def edit_helper # :doc:
@@ -112,7 +112,7 @@ module Rails
end
def new_helper # :doc:
- "new_#{singular_table_name}_url"
+ "new_#{singular_route_name}_url"
end
def field_id(attribute_name)
@@ -152,6 +152,35 @@ module Rails
end
end
+ def redirect_resource_name # :doc:
+ model_resource_name(prefix: "@")
+ end
+
+ def model_resource_name(prefix: "") # :doc:
+ resource_name = "#{prefix}#{singular_table_name}"
+ if controller_class_path.empty?
+ resource_name
+ else
+ "[#{controller_class_path.map { |name| ":" + name }.join(", ")}, #{resource_name}]"
+ end
+ end
+
+ def singular_route_name # :doc:
+ if controller_class_path.empty?
+ singular_table_name
+ else
+ "#{controller_class_path.join('_')}_#{singular_table_name}"
+ end
+ end
+
+ def plural_route_name # :doc:
+ if controller_class_path.empty?
+ plural_table_name
+ else
+ "#{controller_class_path.join('_')}_#{plural_table_name}"
+ end
+ end
+
def assign_names!(name)
@class_path = name.include?("/") ? name.split("/") : name.split("::")
@class_path.map!(&:underscore)
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 0f73cc4755..39ef0d62de 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../app_base"
+require "rails/generators/app_base"
module Rails
module ActionMethods # :nodoc:
@@ -69,7 +69,7 @@ module Rails
def version_control
if !options[:skip_git] && !options[:pretend]
- run "git init"
+ run "git init", capture: options[:quiet]
end
end
@@ -111,7 +111,6 @@ module Rails
template "routes.rb"
template "application.rb"
template "environment.rb"
- template "secrets.yml"
template "cable.yml" unless options[:skip_action_cable]
template "puma.rb" unless options[:skip_puma]
template "spring.rb" if spring_install?
@@ -159,6 +158,26 @@ module Rails
end
end
+ def master_key
+ return if options[:pretend]
+
+ require "rails/generators/rails/master_key/master_key_generator"
+
+ after_bundle do
+ Rails::Generators::MasterKeyGenerator.new([], quiet: options[:quiet]).add_master_key_file
+ end
+ end
+
+ def credentials
+ return if options[:pretend]
+
+ require "rails/generators/rails/credentials/credentials_generator"
+
+ after_bundle do
+ Rails::Generators::CredentialsGenerator.new([], quiet: options[:quiet]).add_credentials_file_silently
+ end
+ end
+
def database_yml
template "config/databases/#{options[:database]}.yml", "config/database.yml"
end
@@ -289,6 +308,14 @@ module Rails
end
remove_task :update_config_files
+ def create_master_key
+ build(:master_key)
+ end
+
+ def create_credentials
+ build(:credentials)
+ end
+
def display_upgrade_guide_info
say "\nAfter this, check Rails upgrade guide at http://guides.rubyonrails.org/upgrading_ruby_on_rails.html for more details about upgrading your app."
end
@@ -532,7 +559,7 @@ module Rails
def handle_version_request!(argument)
if ["--version", "-v"].include?(argument)
- require_relative "../../../version"
+ require "rails/version"
puts "Rails #{Rails::VERSION::STRING}"
exit(0)
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 7b7bebc957..95e8be96b6 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -21,6 +21,9 @@ ruby <%= "'#{RUBY_VERSION}'" -%>
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
+# Use ActiveStorage variant
+# gem 'mini_magick', '~> 4.8'
+
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
@@ -38,8 +41,10 @@ group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
<%- if depends_on_system_test? -%>
# Adds support for Capybara system testing and selenium driver
- gem 'capybara', '~> 2.13'
+ gem 'capybara', '~> 2.15'
gem 'selenium-webdriver'
+ # Easy installation and use of chromedriver to run system tests with Chrome
+ gem 'chromedriver-helper'
<%- end -%>
end
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
index d744bec32f..70cc71d83b 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
@@ -15,6 +15,11 @@ chdir APP_ROOT do
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
+<% unless options.skip_yarn? -%>
+
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
+<% end -%>
<% unless options.skip_active_record? -%>
puts "\n== Updating database =="
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn b/railties/lib/rails/generators/rails/app/templates/bin/yarn
index c2f9b6768a..b4e4d95286 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/yarn
+++ b/railties/lib/rails/generators/rails/app/templates/bin/yarn
@@ -1,5 +1,5 @@
-VENDOR_PATH = File.expand_path('..', __dir__)
-Dir.chdir(VENDOR_PATH) do
+APP_ROOT = File.expand_path('..', __dir__)
+Dir.chdir(APP_ROOT) do
begin
exec "yarnpkg #{ARGV.join(' ')}"
rescue Errno::ENOENT
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index f68e13aa8b..2e0b555f6f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -14,10 +14,9 @@ Rails.application.configure do
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
- # Attempt to read encrypted secrets from `config/secrets.yml.enc`.
- # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
- # `config/secrets.yml.key`.
- config.read_encrypted_secrets = true
+ # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
+ # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
+ # config.require_master_key = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/storage.yml b/railties/lib/rails/generators/rails/app/templates/config/storage.yml
index 089ed4567a..9bada4b66d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/storage.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/storage.yml
@@ -6,11 +6,11 @@ local:
service: Disk
root: <%%= Rails.root.join("storage") %>
-# Use rails secrets:edit to set the AWS secrets (as shared:aws:access_key_id|secret_access_key)
+# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
# service: S3
-# access_key_id: <%%= Rails.application.secrets.dig(:aws, :access_key_id) %>
-# secret_access_key: <%%= Rails.application.secrets.dig(:aws, :secret_access_key) %>
+# access_key_id: <%%= Rails.application.credentials.dig(:aws, :access_key_id) %>
+# secret_access_key: <%%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
# region: us-east-1
# bucket: your_own_bucket
@@ -21,12 +21,12 @@ local:
# keyfile: <%%= Rails.root.join("path/to/gcs.keyfile") %>
# bucket: your_own_bucket
-# Use rails secrets:edit to set the Azure Storage secret (as shared:azure_storage:storage_access_key)
+# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
# microsoft:
# service: AzureStorage
# path: your_azure_storage_path
# storage_account_name: your_account_name
-# storage_access_key: <%%= Rails.application.secrets.dig(:azure_storage, :storage_access_key) %>
+# storage_access_key: <%%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
# container: your_container_name
# mirror:
diff --git a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb
new file mode 100644
index 0000000000..52cb4bd8bf
--- /dev/null
+++ b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require "rails/generators/base"
+require "rails/generators/rails/master_key/master_key_generator"
+require "active_support/encrypted_configuration"
+
+module Rails
+ module Generators
+ class CredentialsGenerator < Base
+ CONFIG_PATH = "config/credentials.yml.enc"
+ KEY_PATH = "config/master.key"
+
+ def add_credentials_file
+ unless File.exist?(CONFIG_PATH)
+ template = credentials_template
+
+ say "Adding #{CONFIG_PATH} to store encrypted credentials."
+ say ""
+ say "The following content has been encrypted with the Rails master key:"
+ say ""
+ say template, :on_green
+ say ""
+
+ add_credentials_file_silently(template)
+
+ say "You can edit encrypted credentials with `bin/rails credentials:edit`."
+ say ""
+ end
+ end
+
+ def add_credentials_file_silently(template = nil)
+ unless File.exist?(CONFIG_PATH)
+ setup = { config_path: CONFIG_PATH, key_path: KEY_PATH, env_key: "RAILS_MASTER_KEY" }
+ ActiveSupport::EncryptedConfiguration.new(setup).write(credentials_template)
+ end
+ end
+
+ private
+ def credentials_template
+ "# aws:\n# access_key_id: 123\n# secret_access_key: 345\n\n" +
+ "# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.\n" +
+ "secret_key_base: #{SecureRandom.hex(64)}"
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb b/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
index d054e8cad2..1aa7a2622a 100644
--- a/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
+++ b/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../../base"
-require_relative "../../../secrets"
+require "rails/generators/base"
+require "rails/secrets"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/rails/master_key/master_key_generator.rb b/railties/lib/rails/generators/rails/master_key/master_key_generator.rb
new file mode 100644
index 0000000000..23dbed20ac
--- /dev/null
+++ b/railties/lib/rails/generators/rails/master_key/master_key_generator.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require "rails/generators/base"
+require "pathname"
+require "active_support/encrypted_file"
+
+module Rails
+ module Generators
+ class MasterKeyGenerator < Base
+ MASTER_KEY_PATH = Pathname.new("config/master.key")
+
+ def add_master_key_file
+ unless MASTER_KEY_PATH.exist?
+ key = ActiveSupport::EncryptedFile.generate_key
+
+ log "Adding #{MASTER_KEY_PATH} to store the master encryption key: #{key}"
+ log ""
+ log "Save this in a password manager your team can access."
+ log ""
+ log "If you lose the key, no one, including you, can access anything encrypted with it."
+
+ log ""
+ create_file MASTER_KEY_PATH, key
+ log ""
+
+ ignore_master_key_file
+ end
+ end
+
+ private
+ def ignore_master_key_file
+ if File.exist?(".gitignore")
+ unless File.read(".gitignore").include?(key_ignore)
+ log "Ignoring #{MASTER_KEY_PATH} so it won't end up in Git history:"
+ log ""
+ append_to_file ".gitignore", key_ignore
+ log ""
+ end
+ else
+ log "IMPORTANT: Don't commit #{MASTER_KEY_PATH}. Add this to your ignore file:"
+ log key_ignore, :on_green
+ log ""
+ end
+ end
+
+ def key_ignore
+ [ "", "# Ignore master key for decrypting credentials and more.", "/#{MASTER_KEY_PATH}", "" ].join("\n")
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb
index 1dca03e0bb..de4de2cae2 100644
--- a/railties/lib/rails/generators/rails/model/model_generator.rb
+++ b/railties/lib/rails/generators/rails/model/model_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../model_helpers"
+require "rails/generators/model_helpers"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index eb941adf95..d9ca4712d0 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
-require "active_support/core_ext/hash/slice"
-require_relative "../app/app_generator"
+require "rails/generators/rails/app/app_generator"
require "date"
module Rails
@@ -93,7 +92,7 @@ task default: :test
]
def generate_test_dummy(force = false)
- opts = (options || {}).slice(*PASSTHROUGH_OPTIONS)
+ opts = (options.dup || {}).keep_if { |k, _| PASSTHROUGH_OPTIONS.map(&:to_s).include?(k) }
opts[:force] = force
opts[:skip_bundle] = true
opts[:skip_listen] = true
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
index 2af7e06041..7fa9973931 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb
@@ -12,6 +12,7 @@ require "rails/test_help"
Minitest.backtrace_filter = Minitest::BacktraceFilter.new
<% unless engine? -%>
+require "rails/test_unit/reporter"
Rails::TestUnitReporter.executable = 'bin/test'
<% end -%>
diff --git a/railties/lib/rails/generators/rails/resource/USAGE b/railties/lib/rails/generators/rails/resource/USAGE
index e359cd574f..66d0ee546a 100644
--- a/railties/lib/rails/generators/rails/resource/USAGE
+++ b/railties/lib/rails/generators/rails/resource/USAGE
@@ -1,6 +1,6 @@
Description:
Stubs out a new resource including an empty model and controller suitable
- for a restful, resource-oriented application. Pass the singular model name,
+ for a RESTful, resource-oriented application. Pass the singular model name,
either CamelCased or under_scored, as the first argument, and an optional
list of attribute pairs.
diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb
index 74b1574863..3ba25ef0fe 100644
--- a/railties/lib/rails/generators/rails/resource/resource_generator.rb
+++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../../resource_helpers"
-require_relative "../model/model_generator"
+require "rails/generators/resource_helpers"
+require "rails/generators/rails/model/model_generator"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index c0fb873d36..8beb7416c0 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../resource/resource_generator"
+require "rails/generators/rails/resource/resource_generator"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index 601a4b3a6e..7030561a33 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../resource_helpers"
+require "rails/generators/resource_helpers"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
index 42b9e34274..05f1c2b2d3 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -29,7 +29,7 @@ class <%= controller_class_name %>Controller < ApplicationController
@<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
if @<%= orm_instance.save %>
- redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %>
+ redirect_to <%= redirect_resource_name %>, notice: <%= "'#{human_name} was successfully created.'" %>
else
render :new
end
@@ -38,7 +38,7 @@ class <%= controller_class_name %>Controller < ApplicationController
# PATCH/PUT <%= route_url %>/1
def update
if @<%= orm_instance.update("#{singular_table_name}_params") %>
- redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %>
+ redirect_to <%= redirect_resource_name %>, notice: <%= "'#{human_name} was successfully updated.'" %>
else
render :edit
end
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index 8bd5110940..a146a8fda6 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "active_model"
-require_relative "model_helpers"
+require "rails/generators/active_model"
+require "rails/generators/model_helpers"
module Rails
module Generators
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 9c06eed95a..5c71bf0be9 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -1,9 +1,9 @@
# frozen_string_literal: true
-require_relative "../generators"
-require_relative "testing/behaviour"
-require_relative "testing/setup_and_teardown"
-require_relative "testing/assertions"
+require "rails/generators"
+require "rails/generators/testing/behaviour"
+require "rails/generators/testing/setup_and_teardown"
+require "rails/generators/testing/assertions"
require "fileutils"
module Rails
diff --git a/railties/lib/rails/generators/test_unit.rb b/railties/lib/rails/generators/test_unit.rb
index 1aa03ace99..1005ac557c 100644
--- a/railties/lib/rails/generators/test_unit.rb
+++ b/railties/lib/rails/generators/test_unit.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "named_base"
+require "rails/generators/named_base"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb
index 3947b74205..1a9ac6bf2a 100644
--- a/railties/lib/rails/generators/test_unit/controller/controller_generator.rb
+++ b/railties/lib/rails/generators/test_unit/controller/controller_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../test_unit"
+require "rails/generators/test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
index 4efa977a89..ff41fef9e9 100644
--- a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require 'test_helper'
<% module_namespacing do -%>
diff --git a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb
index 9f79e9eee1..19be4f2f51 100644
--- a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb
+++ b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../test_unit"
+require "rails/generators/test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb b/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb
index e6fb6c5ff4..a7f1fc4fba 100644
--- a/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb
+++ b/railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require 'test_helper'
require '<%= generator_path %>'
diff --git a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb
index 1c60c3573f..77308dcf7d 100644
--- a/railties/lib/rails/generators/test_unit/helper/helper_generator.rb
+++ b/railties/lib/rails/generators/test_unit/helper/helper_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../test_unit"
+require "rails/generators/test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb
index c859ba5e4e..ae307c5cd9 100644
--- a/railties/lib/rails/generators/test_unit/integration/integration_generator.rb
+++ b/railties/lib/rails/generators/test_unit/integration/integration_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../test_unit"
+require "rails/generators/test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb
index 65708b6c3b..118e0f1271 100644
--- a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb
+++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require 'test_helper'
<% module_namespacing do -%>
diff --git a/railties/lib/rails/generators/test_unit/job/job_generator.rb b/railties/lib/rails/generators/test_unit/job/job_generator.rb
index cbbcd1cd4e..9225af4e0c 100644
--- a/railties/lib/rails/generators/test_unit/job/job_generator.rb
+++ b/railties/lib/rails/generators/test_unit/job/job_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../test_unit"
+require "rails/generators/test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
index 45f82158e9..610d47a729 100644
--- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../test_unit"
+require "rails/generators/test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
index 1ec3a2f360..a2f2d30de5 100644
--- a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require 'test_helper'
<% module_namespacing do -%>
diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb
index 9876210b6c..b063cbc47b 100644
--- a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
<% module_namespacing do -%>
# Preview all emails at http://localhost:3000/rails/mailers/<%= file_path %>_mailer
class <%= class_name %>MailerPreview < ActionMailer::Preview
diff --git a/railties/lib/rails/generators/test_unit/model/model_generator.rb b/railties/lib/rails/generators/test_unit/model/model_generator.rb
index 2b1cc50c20..02d7502592 100644
--- a/railties/lib/rails/generators/test_unit/model/model_generator.rb
+++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../test_unit"
+require "rails/generators/test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb
index 5f1ffeb33b..c9bc7d5b90 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb
+++ b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require 'test_helper'
<% module_namespacing do -%>
diff --git a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb
index 10fe27b2c3..0657bc2389 100644
--- a/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/test_unit/plugin/plugin_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../test_unit"
+require "rails/generators/test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
index 2147b09568..30a861f09d 100644
--- a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
+++ b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
@@ -1,4 +1,2 @@
-# frozen_string_literal: true
-
require 'active_support/testing/autorun'
require 'active_support'
diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
index 23d5c63614..b6c13b41ae 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../../test_unit"
-require_relative "../../resource_helpers"
+require "rails/generators/test_unit"
+require "rails/generators/resource_helpers"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
index 2ef93b8aea..f21861d8e6 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require 'test_helper'
<% module_namespacing do -%>
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
index bcf9392bd1..195d60be20 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require 'test_helper'
<% module_namespacing do -%>
diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb
index ba8bdc192e..f83f5a5c62 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "application_system_test_case"
<% module_namespacing do -%>
diff --git a/railties/lib/rails/generators/test_unit/system/system_generator.rb b/railties/lib/rails/generators/test_unit/system/system_generator.rb
index d3acbfd738..08504d4124 100644
--- a/railties/lib/rails/generators/test_unit/system/system_generator.rb
+++ b/railties/lib/rails/generators/test_unit/system/system_generator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../../test_unit"
+require "rails/generators/test_unit"
module TestUnit # :nodoc:
module Generators # :nodoc:
diff --git a/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb b/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb
index c05709aff8..d19212abd5 100644
--- a/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb
+++ b/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
diff --git a/railties/lib/rails/generators/test_unit/system/templates/system_test.rb b/railties/lib/rails/generators/test_unit/system/templates/system_test.rb
index cfac061cd1..b5ce2ba5c8 100644
--- a/railties/lib/rails/generators/test_unit/system/templates/system_test.rb
+++ b/railties/lib/rails/generators/test_unit/system/templates/system_test.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
require "application_system_test_case"
class <%= class_name.pluralize %>Test < ApplicationSystemTestCase
diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb
index 97a09c1166..6ab88bd59f 100644
--- a/railties/lib/rails/generators/testing/behaviour.rb
+++ b/railties/lib/rails/generators/testing/behaviour.rb
@@ -6,7 +6,7 @@ require "active_support/core_ext/hash/reverse_merge"
require "active_support/core_ext/kernel/reporting"
require "active_support/testing/stream"
require "active_support/concern"
-require_relative "../../generators"
+require "rails/generators"
module Rails
module Generators
diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb
index 6535b5b6ad..b4f4a5922a 100644
--- a/railties/lib/rails/info_controller.rb
+++ b/railties/lib/rails/info_controller.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "application_controller"
+require "rails/application_controller"
require "action_dispatch/routing/inspector"
class Rails::InfoController < Rails::ApplicationController # :nodoc:
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index 6bd3161fdd..66636e5d6b 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "application_controller"
+require "rails/application_controller"
class Rails::MailersController < Rails::ApplicationController # :nodoc:
prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH
diff --git a/railties/lib/rails/plugin/test.rb b/railties/lib/rails/plugin/test.rb
index 2426521e35..18b6fd1757 100644
--- a/railties/lib/rails/plugin/test.rb
+++ b/railties/lib/rails/plugin/test.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_relative "../test_unit/runner"
-require_relative "../test_unit/reporter"
+require "rails/test_unit/runner"
+require "rails/test_unit/reporter"
Rails::TestUnitReporter.executable = "bin/test"
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index 42c4a82b1b..88dd932370 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "initializable"
+require "rails/initializable"
require "active_support/inflector"
require "active_support/core_ext/module/introspection"
require "active_support/core_ext/module/delegation"
diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb
index d9997759f2..48853129bc 100644
--- a/railties/lib/rails/railtie/configuration.rb
+++ b/railties/lib/rails/railtie/configuration.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../configuration"
+require "rails/configuration"
module Rails
class Railtie
diff --git a/railties/lib/rails/tasks/annotations.rake b/railties/lib/rails/tasks/annotations.rake
index 931f81ac7e..93bc66e2db 100644
--- a/railties/lib/rails/tasks/annotations.rake
+++ b/railties/lib/rails/tasks/annotations.rake
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../source_annotation_extractor"
+require "rails/source_annotation_extractor"
desc "Enumerate all annotations (use notes:optimize, :fixme, :todo for focus)"
task :notes do
diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake
index 90fd3ccd8b..5aea6f7dc5 100644
--- a/railties/lib/rails/tasks/dev.rake
+++ b/railties/lib/rails/tasks/dev.rake
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "../dev_caching"
+require "rails/dev_caching"
namespace :dev do
desc "Toggle development mode caching on/off"
diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake
index 3ce49a1641..c24d1ad532 100644
--- a/railties/lib/rails/tasks/engine.rake
+++ b/railties/lib/rails/tasks/engine.rake
@@ -8,7 +8,7 @@ task "load_app" do
task update: [ "update:bin" ]
namespace :update do
- require_relative "../engine/updater"
+ require "rails/engine/updater"
# desc "Adds new executables to the engine bin/ directory"
task :bin do
Rails::Engine::Updater.run(:create_bin_files)
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index 8931aabcda..7dfcd14bd0 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -9,8 +9,8 @@ namespace :app do
template = ENV["LOCATION"]
raise "No LOCATION value given. Please set LOCATION either as path to a file or a URL" if template.blank?
template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://}
- require_relative "../generators"
- require_relative "../generators/rails/app/app_generator"
+ require "rails/generators"
+ require "rails/generators/rails/app/app_generator"
generator = Rails::Generators::AppGenerator.new [Rails.root], {}, { destination_root: Rails.root }
generator.apply template, verbose: false
end
@@ -38,7 +38,7 @@ namespace :app do
end
namespace :update do
- require_relative "../app_updater"
+ require "rails/app_updater"
# desc "Update config/boot.rb from your current rails install"
task :configs do
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index 0449a13586..594db91eec 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -26,6 +26,6 @@ end.select { |name, dir| File.directory?(dir) }
desc "Report code statistics (KLOCs, etc) from the application or engine"
task :stats do
- require_relative "../code_statistics"
+ require "rails/code_statistics"
CodeStatistics.new(*STATS_DIRECTORIES).to_s
end
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index f755474488..732c5c1e1f 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -8,7 +8,7 @@ require "active_support/test_case"
require "action_controller"
require "action_controller/test_case"
require "action_dispatch/testing/integration"
-require_relative "generators/test_case"
+require "rails/generators/test_case"
require "active_support/testing/autorun"
diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb
index 05f9a177d0..42b6daa3d1 100644
--- a/railties/lib/rails/test_unit/railtie.rb
+++ b/railties/lib/rails/test_unit/railtie.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "line_filtering"
+require "rails/test_unit/line_filtering"
if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any?
ENV["RAILS_ENV"] ||= Rake.application.options.show_tasks ? "development" : "test"
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 18d699f806..32ac27a135 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -2,7 +2,7 @@
gem "minitest"
require "minitest"
-require_relative "runner"
+require "rails/test_unit/runner"
task default: :test
diff --git a/railties/lib/rails/welcome_controller.rb b/railties/lib/rails/welcome_controller.rb
index 0cb43e0f31..5b84b57679 100644
--- a/railties/lib/rails/welcome_controller.rb
+++ b/railties/lib/rails/welcome_controller.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_relative "application_controller"
+require "rails/application_controller"
class Rails::WelcomeController < Rails::ApplicationController # :nodoc:
layout false
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index a63b7a8377..b42f37d6b9 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -15,7 +15,6 @@ require "rails/all"
module TestApp
class Application < Rails::Application
config.root = __dir__
- secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33"
end
end
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index 128040262f..e56c7b958e 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -9,10 +9,7 @@ module ApplicationTests
include Rack::Test::Methods
def setup
- # FIXME: shush Sass warning spam, not relevant to testing Railties
- Kernel.silence_warnings do
- build_app(initializers: true)
- end
+ build_app(initializers: true)
app_file "app/assets/javascripts/application.js", "//= require_tree ."
app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }"
@@ -36,16 +33,10 @@ module ApplicationTests
teardown_app
end
- # FIXME: shush Sass warning spam, not relevant to testing Railties
- def get(*)
- Kernel.silence_warnings { super }
- end
-
test "assets are concatenated when debug is off and compile is off either if debug_assets param is provided" do
# config.assets.debug and config.assets.compile are false for production environment
ENV["RAILS_ENV"] = "production"
- output = Dir.chdir(app_path) { `bin/rails assets:precompile --trace 2>&1` }
- assert $?.success?, output
+ rails "assets:precompile", "--trace"
# Load app env
app "production"
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 99fe6749d9..0d3262d6f6 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -62,10 +62,7 @@ module ApplicationTests
add_to_env_config "development", "config.assets.digest = false"
- # FIXME: shush Sass warning spam, not relevant to testing Railties
- Kernel.silence_warnings do
- require "#{app_path}/config/environment"
- end
+ require "#{app_path}/config/environment"
get "/assets/demo.js"
assert_equal 'a = "/assets/rails.png";', last_response.body.strip
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
index a09c371033..54934dbe24 100644
--- a/railties/test/application/bin_setup_test.rb
+++ b/railties/test/application/bin_setup_test.rb
@@ -22,7 +22,7 @@ module ApplicationTests
end
RUBY
- list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip }
+ list_tables = lambda { rails("runner", "p ActiveRecord::Base.connection.tables").strip }
File.write("log/test.log", "zomg!")
assert_equal "[]", list_tables.call
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index f6d26c8ff7..bb8cc0876c 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -314,6 +314,7 @@ module ApplicationTests
end
test "the application can be eager loaded even when there are no frameworks" do
+ FileUtils.rm_rf("#{app_path}/app/jobs/application_job.rb")
FileUtils.rm_rf("#{app_path}/app/models/application_record.rb")
FileUtils.rm_rf("#{app_path}/app/mailers/application_mailer.rb")
FileUtils.rm_rf("#{app_path}/config/environments")
@@ -475,45 +476,61 @@ module ApplicationTests
test "application message verifier can be used when the key_generator is ActiveSupport::LegacyKeyGenerator" do
app_file "config/initializers/secret_token.rb", <<-RUBY
+ Rails.application.credentials.secret_key_base = nil
Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
RUBY
- app_file "config/secrets.yml", <<-YAML
- development:
- secret_key_base:
- YAML
- app "development"
+ app "production"
- assert_equal app.env_config["action_dispatch.key_generator"], Rails.application.key_generator
- assert_equal app.env_config["action_dispatch.key_generator"].class, ActiveSupport::LegacyKeyGenerator
+ assert_kind_of ActiveSupport::LegacyKeyGenerator, Rails.application.key_generator
message = app.message_verifier(:sensitive_value).generate("some_value")
assert_equal "some_value", Rails.application.message_verifier(:sensitive_value).verify(message)
end
- test "warns when secrets.secret_key_base is blank and config.secret_token is set" do
+ test "config.secret_token is deprecated" do
app_file "config/initializers/secret_token.rb", <<-RUBY
Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
RUBY
- app_file "config/secrets.yml", <<-YAML
- development:
- secret_key_base:
- YAML
- app "development"
+ app "production"
- assert_deprecated(/You didn't set `secret_key_base`./) do
- app.env_config
+ assert_deprecated(/secret_token/) do
+ app.secrets
end
end
- test "raise when secrets.secret_key_base is not a type of string" do
+ test "secrets.secret_token is deprecated" do
app_file "config/secrets.yml", <<-YAML
- development:
- secret_key_base: 123
+ production:
+ secret_token: "b3c631c314c0bbca50c1b2843150fe33"
YAML
+ app "production"
+
+ assert_deprecated(/secret_token/) do
+ app.secrets
+ end
+ end
+
+
+ test "raises when secret_key_base is blank" do
+ app_file "config/initializers/secret_token.rb", <<-RUBY
+ Rails.application.credentials.secret_key_base = nil
+ RUBY
+
+ error = assert_raise(ArgumentError) do
+ app "production"
+ end
+ assert_match(/Missing `secret_key_base`./, error.message)
+ end
+
+ test "raise when secret_key_base is not a type of string" do
+ add_to_config <<-RUBY
+ Rails.application.credentials.secret_key_base = 123
+ RUBY
+
assert_raise(ArgumentError) do
- app "development"
+ app "production"
end
end
@@ -533,7 +550,7 @@ module ApplicationTests
test "application verifier can build different verifiers" do
make_basic_app do |application|
- application.secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33"
+ application.credentials.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33"
application.config.session_store :disabled
end
@@ -651,37 +668,15 @@ module ApplicationTests
test "uses ActiveSupport::LegacyKeyGenerator as app.key_generator when secrets.secret_key_base is blank" do
app_file "config/initializers/secret_token.rb", <<-RUBY
+ Rails.application.credentials.secret_key_base = nil
Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33"
RUBY
- app_file "config/secrets.yml", <<-YAML
- development:
- secret_key_base:
- YAML
- app "development"
+ app "production"
assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token
- assert_nil app.secrets.secret_key_base
- assert_equal app.key_generator.class, ActiveSupport::LegacyKeyGenerator
- end
-
- test "uses ActiveSupport::LegacyKeyGenerator with config.secret_token as app.key_generator when secrets.secret_key_base is blank" do
- app_file "config/initializers/secret_token.rb", <<-RUBY
- Rails.application.config.secret_token = ""
- RUBY
- app_file "config/secrets.yml", <<-YAML
- development:
- secret_key_base:
- YAML
-
- app "development"
-
- assert_equal "", app.config.secret_token
- assert_nil app.secrets.secret_key_base
- e = assert_raise ArgumentError do
- app.key_generator
- end
- assert_match(/\AA secret is required/, e.message)
+ assert_nil app.credentials.secret_key_base
+ assert_kind_of ActiveSupport::LegacyKeyGenerator, app.key_generator
end
test "that nested keys are symbolized the same as parents for hashes more than one level deep" do
@@ -698,6 +693,20 @@ module ApplicationTests
assert_equal "697361616320736c6f616e2028656c6f7265737429", app.secrets.smtp_settings[:password]
end
+ test "require_master_key aborts app boot when missing key" do
+ skip "can't run without fork" unless Process.respond_to?(:fork)
+
+ remove_file "config/master.key"
+ add_to_config "config.require_master_key = true"
+
+ error = capture(:stderr) do
+ Process.wait(Process.fork { app "development" })
+ end
+
+ assert_equal 1, $?.exitstatus
+ assert_match(/Missing.*RAILS_MASTER_KEY/, error)
+ end
+
test "protect from forgery is the default in a new app" do
make_basic_app
diff --git a/railties/test/application/current_attributes_integration_test.rb b/railties/test/application/current_attributes_integration_test.rb
index 811721b331..146e96facc 100644
--- a/railties/test/application/current_attributes_integration_test.rb
+++ b/railties/test/application/current_attributes_integration_test.rb
@@ -39,6 +39,8 @@ class CurrentAttributesIntegrationTest < ActiveSupport::TestCase
app_file "app/controllers/customers_controller.rb", <<-RUBY
class CustomersController < ApplicationController
+ layout false
+
def set_current_customer
Current.customer = Customer.new("david")
render :index
diff --git a/railties/test/application/dbconsole_test.rb b/railties/test/application/dbconsole_test.rb
index ba04c81b4d..8eb293c179 100644
--- a/railties/test/application/dbconsole_test.rb
+++ b/railties/test/application/dbconsole_test.rb
@@ -19,21 +19,19 @@ module ApplicationTests
end
def test_use_value_defined_in_environment_file_in_database_yml
- Dir.chdir(app_path) do
- app_file "config/database.yml", <<-YAML
- development:
- database: <%= Rails.application.config.database %>
- adapter: sqlite3
- pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
- timeout: 5000
- YAML
+ app_file "config/database.yml", <<-YAML
+ development:
+ database: <%= Rails.application.config.database %>
+ adapter: sqlite3
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+ timeout: 5000
+ YAML
- app_file "config/environments/development.rb", <<-RUBY
- Rails.application.configure do
- config.database = "db/development.sqlite3"
- end
- RUBY
- end
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ config.database = "db/development.sqlite3"
+ end
+ RUBY
master, slave = PTY.open
spawn_dbconsole(slave)
@@ -43,22 +41,20 @@ module ApplicationTests
end
def test_respect_environment_option
- Dir.chdir(app_path) do
- app_file "config/database.yml", <<-YAML
- default: &default
- adapter: sqlite3
- pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
- timeout: 5000
+ app_file "config/database.yml", <<-YAML
+ default: &default
+ adapter: sqlite3
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+ timeout: 5000
- development:
- <<: *default
- database: db/development.sqlite3
+ development:
+ <<: *default
+ database: db/development.sqlite3
- production:
- <<: *default
- database: db/production.sqlite3
- YAML
- end
+ production:
+ <<: *default
+ database: db/production.sqlite3
+ YAML
master, slave = PTY.open
spawn_dbconsole(slave, "-e production")
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index 58197a8f6b..47c815d221 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -31,7 +31,7 @@ module ApplicationTests
end
test "allow running plugin new generator inside Rails app directory" do
- FileUtils.cd(rails_root) { `ruby bin/rails plugin new vendor/plugins/bukkits` }
+ rails "plugin", "new", "vendor/plugins/bukkits"
assert File.exist?(File.join(rails_root, "vendor/plugins/bukkits/test/dummy/config/application.rb"))
end
@@ -167,13 +167,14 @@ module ApplicationTests
config.api_only = true
RUBY
- FileUtils.cd(rails_root) { `bin/rails generate mailer notifier foo` }
+ rails "generate", "mailer", "notifier", "foo"
assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.text.erb"))
assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.html.erb"))
end
test "ARGV is mutated as expected" do
require "#{app_path}/config/environment"
+ require "rails/command"
Rails::Command.const_set("APP_PATH", "rails/all")
FileUtils.cd(rails_root) do
@@ -189,10 +190,10 @@ module ApplicationTests
test "help does not show hidden namespaces" do
FileUtils.cd(rails_root) do
- output = `bin/rails generate --help`
+ output = rails("generate", "--help")
assert_no_match "active_record:migration", output
- output = `bin/rails destroy --help`
+ output = rails("destroy", "--help")
assert_no_match "active_record:migration", output
end
end
diff --git a/railties/test/application/help_test.rb b/railties/test/application/help_test.rb
index 3a9384b927..f728fc3b85 100644
--- a/railties/test/application/help_test.rb
+++ b/railties/test/application/help_test.rb
@@ -14,12 +14,12 @@ class HelpTest < ActiveSupport::TestCase
end
test "command works" do
- output = Dir.chdir(app_path) { `bin/rails help` }
+ output = rails("help")
assert_match "The most common rails commands are", output
end
test "short-cut alias works" do
- output = Dir.chdir(app_path) { `bin/rails -h` }
+ output = rails("-h")
assert_match "The most common rails commands are", output
end
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index d643c85181..d2b77bd015 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -211,24 +211,20 @@ module ApplicationTests
test "database middleware doesn't initialize when activerecord is not in frameworks" do
use_frameworks []
require "#{app_path}/config/environment"
- assert_nil defined?(ActiveRecord::Base)
+ assert !defined?(ActiveRecord::Base) || ActiveRecord.autoload?(:Base)
end
test "use schema cache dump" do
- Dir.chdir(app_path) do
- `rails generate model post title:string;
- bin/rails db:migrate db:schema:cache:dump`
- end
+ rails %w(generate model post title:string)
+ rails %w(db:migrate db:schema:cache:dump)
require "#{app_path}/config/environment"
ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test.
assert ActiveRecord::Base.connection.schema_cache.data_sources("posts")
end
test "expire schema cache dump" do
- Dir.chdir(app_path) do
- `rails generate model post title:string;
- bin/rails db:migrate db:schema:cache:dump db:rollback`
- end
+ rails %w(generate model post title:string)
+ rails %w(db:migrate db:schema:cache:dump db:rollback)
require "#{app_path}/config/environment"
assert !ActiveRecord::Base.connection.schema_cache.data_sources("posts")
end
diff --git a/railties/test/application/integration_test_case_test.rb b/railties/test/application/integration_test_case_test.rb
index 88b54af8c8..9edc907fce 100644
--- a/railties/test/application/integration_test_case_test.rb
+++ b/railties/test/application/integration_test_case_test.rb
@@ -15,7 +15,7 @@ module ApplicationTests
end
test "resets Action Mailer test deliveries" do
- script("generate mailer BaseMailer welcome")
+ rails "generate", "mailer", "BaseMailer", "welcome"
app_file "test/integration/mailer_integration_test.rb", <<-RUBY
require 'test_helper'
@@ -39,8 +39,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) { `bin/rails test 2>&1` }
- assert_equal 0, $?.to_i, output
+ output = rails("test")
assert_match(/0 failures, 0 errors/, output)
end
end
@@ -67,8 +66,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) { `bin/rails test 2>&1` }
- assert_equal 0, $?.to_i, output
+ output = rails("test")
assert_match(/0 failures, 0 errors/, output)
end
end
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 11c886e991..de1e240fd3 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -300,7 +300,7 @@ class LoadingTest < ActiveSupport::TestCase
end
MIGRATION
- Dir.chdir(app_path) { `rake db:migrate` }
+ rails("db:migrate")
require "#{rails_root}/config/environment"
get "/title"
@@ -314,7 +314,7 @@ class LoadingTest < ActiveSupport::TestCase
end
MIGRATION
- Dir.chdir(app_path) { `rake db:migrate` }
+ rails("db:migrate")
get "/body"
assert_equal "BODY", last_response.body
diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb
index 23f1ec3e35..ecb4ee3446 100644
--- a/railties/test/application/middleware/cookies_test.rb
+++ b/railties/test/application/middleware/cookies_test.rb
@@ -1,10 +1,12 @@
# frozen_string_literal: true
require "isolation/abstract_unit"
+require "rack/test"
module ApplicationTests
class CookiesTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
def new_app
File.expand_path("#{app_path}/../new_app")
@@ -15,6 +17,10 @@ module ApplicationTests
FileUtils.rm_rf("#{app_path}/config/environments")
end
+ def app
+ Rails.application
+ end
+
def teardown
teardown_app
FileUtils.rm_rf(new_app) if File.directory?(new_app)
@@ -44,5 +50,144 @@ module ApplicationTests
require "#{app_path}/config/environment"
assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie
end
+
+ test "signed cookies with SHA512 digest and rotated out SHA256 and SHA1 digests" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get ':controller(/:action)'
+ post ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ protect_from_forgery with: :null_session
+
+ def write_raw_cookie_sha1
+ cookies[:signed_cookie] = TestVerifiers.sha1.generate("signed cookie")
+ head :ok
+ end
+
+ def write_raw_cookie_sha256
+ cookies[:signed_cookie] = TestVerifiers.sha256.generate("signed cookie")
+ head :ok
+ end
+
+ def read_signed
+ render plain: cookies.signed[:signed_cookie].inspect
+ end
+
+ def read_raw_cookie
+ render plain: cookies[:signed_cookie]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ sha1_secret = Rails.application.key_generator.generate_key("sha1")
+ sha256_secret = Rails.application.key_generator.generate_key("sha256")
+
+ ::TestVerifiers = Class.new do
+ class_attribute :sha1, default: ActiveSupport::MessageVerifier.new(sha1_secret, digest: "SHA1")
+ class_attribute :sha256, default: ActiveSupport::MessageVerifier.new(sha256_secret, digest: "SHA256")
+ end
+
+ config.action_dispatch.signed_cookie_digest = "SHA512"
+ config.action_dispatch.signed_cookie_salt = "sha512 salt"
+
+ config.action_dispatch.cookies_rotations.tap do |cookies|
+ cookies.rotate :signed, sha1_secret, digest: "SHA1"
+ cookies.rotate :signed, sha256_secret, digest: "SHA256"
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ verifier_sha512 = ActiveSupport::MessageVerifier.new(app.key_generator.generate_key("sha512 salt"), digest: :SHA512)
+
+ get "/foo/write_raw_cookie_sha1"
+ get "/foo/read_signed"
+ assert_equal "signed cookie".inspect, last_response.body
+
+ get "/foo/read_raw_cookie"
+ assert_equal "signed cookie", verifier_sha512.verify(last_response.body)
+
+ get "/foo/write_raw_cookie_sha256"
+ get "/foo/read_signed"
+ assert_equal "signed cookie".inspect, last_response.body
+
+ get "/foo/read_raw_cookie"
+ assert_equal "signed cookie", verifier_sha512.verify(last_response.body)
+ end
+
+ test "encrypted cookies rotating multiple encryption keys" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get ':controller(/:action)'
+ post ':controller(/:action)'
+ end
+ RUBY
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ protect_from_forgery with: :null_session
+
+ def write_raw_cookie_one
+ cookies[:encrypted_cookie] = TestEncryptors.first_gcm.encrypt_and_sign("encrypted cookie")
+ head :ok
+ end
+
+ def write_raw_cookie_two
+ cookies[:encrypted_cookie] = TestEncryptors.second_gcm.encrypt_and_sign("encrypted cookie")
+ head :ok
+ end
+
+ def read_encrypted
+ render plain: cookies.encrypted[:encrypted_cookie].inspect
+ end
+
+ def read_raw_cookie
+ render plain: cookies[:encrypted_cookie]
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ first_secret = Rails.application.key_generator.generate_key("first", 32)
+ second_secret = Rails.application.key_generator.generate_key("second", 32)
+
+ ::TestEncryptors = Class.new do
+ class_attribute :first_gcm, default: ActiveSupport::MessageEncryptor.new(first_secret, cipher: "aes-256-gcm")
+ class_attribute :second_gcm, default: ActiveSupport::MessageEncryptor.new(second_secret, cipher: "aes-256-gcm")
+ end
+
+ config.action_dispatch.use_authenticated_cookie_encryption = true
+ config.action_dispatch.encrypted_cookie_cipher = "aes-256-gcm"
+ config.action_dispatch.authenticated_encrypted_cookie_salt = "salt"
+
+ config.action_dispatch.cookies_rotations.tap do |cookies|
+ cookies.rotate :encrypted, first_secret
+ cookies.rotate :encrypted, second_secret
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ encryptor = ActiveSupport::MessageEncryptor.new(app.key_generator.generate_key("salt", 32), cipher: "aes-256-gcm")
+
+ get "/foo/write_raw_cookie_one"
+ get "/foo/read_encrypted"
+ assert_equal "encrypted cookie".inspect, last_response.body
+
+ get "/foo/read_raw_cookie"
+ assert_equal "encrypted cookie", encryptor.decrypt_and_verify(last_response.body)
+
+ get "/foo/write_raw_cookie_sha256"
+ get "/foo/read_encrypted"
+ assert_equal "encrypted cookie".inspect, last_response.body
+
+ get "/foo/read_raw_cookie"
+ assert_equal "encrypted cookie", encryptor.decrypt_and_verify(last_response.body)
+ end
end
end
diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb
index 4731396029..9def3a0ce7 100644
--- a/railties/test/application/middleware/sendfile_test.rb
+++ b/railties/test/application/middleware/sendfile_test.rb
@@ -15,10 +15,6 @@ module ApplicationTests
teardown_app
end
- def app
- @app ||= Rails.application
- end
-
define_method :simple_controller do
class ::OmgController < ActionController::Base
def index
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
index 15acfe93e9..a17988235a 100644
--- a/railties/test/application/middleware/session_test.rb
+++ b/railties/test/application/middleware/session_test.rb
@@ -337,31 +337,37 @@ module ApplicationTests
add_to_config <<-RUBY
# Use a static key
- secrets.secret_key_base = "known key base"
+ Rails.application.credentials.secret_key_base = "known key base"
# Enable AEAD cookies
config.action_dispatch.use_authenticated_cookie_encryption = true
RUBY
- require "#{app_path}/config/environment"
+ begin
+ old_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production"
- get "/foo/write_raw_session"
- get "/foo/read_session"
- assert_equal "1", last_response.body
+ require "#{app_path}/config/environment"
- get "/foo/write_session"
- get "/foo/read_session"
- assert_equal "2", last_response.body
+ get "/foo/write_raw_session"
+ get "/foo/read_session"
+ assert_equal "1", last_response.body
- get "/foo/read_encrypted_cookie"
- assert_equal "2", last_response.body
+ get "/foo/write_session"
+ get "/foo/read_session"
+ assert_equal "2", last_response.body
- cipher = "aes-256-gcm"
- secret = app.key_generator.generate_key("authenticated encrypted cookie")
- encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
+ get "/foo/read_encrypted_cookie"
+ assert_equal "2", last_response.body
- get "/foo/read_raw_cookie"
- assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"]
+ cipher = "aes-256-gcm"
+ secret = app.key_generator.generate_key("authenticated encrypted cookie")
+ encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher)
+
+ get "/foo/read_raw_cookie"
+ assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"]
+ ensure
+ ENV["RAILS_ENV"] = old_rails_env
+ end
end
test "session upgrading legacy signed cookies to new signed cookies" do
@@ -400,26 +406,32 @@ module ApplicationTests
add_to_config <<-RUBY
secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
- secrets.secret_key_base = nil
+ Rails.application.credentials.secret_key_base = nil
RUBY
- require "#{app_path}/config/environment"
+ begin
+ old_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production"
- get "/foo/write_raw_session"
- get "/foo/read_session"
- assert_equal "1", last_response.body
+ require "#{app_path}/config/environment"
- get "/foo/write_session"
- get "/foo/read_session"
- assert_equal "2", last_response.body
+ get "/foo/write_raw_session"
+ get "/foo/read_session"
+ assert_equal "1", last_response.body
- get "/foo/read_signed_cookie"
- assert_equal "2", last_response.body
+ get "/foo/write_session"
+ get "/foo/read_session"
+ assert_equal "2", last_response.body
- verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token)
+ get "/foo/read_signed_cookie"
+ assert_equal "2", last_response.body
- get "/foo/read_raw_cookie"
- assert_equal 2, verifier.verify(last_response.body)["foo"]
+ verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token)
+
+ get "/foo/read_raw_cookie"
+ assert_equal 2, verifier.verify(last_response.body)["foo"]
+ ensure
+ ENV["RAILS_ENV"] = old_rails_env
+ end
end
test "calling reset_session on request does not trigger an error for API apps" do
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index db88eb0fd3..fd22477539 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -28,11 +28,11 @@ module ApplicationTests
def db_create_and_drop(expected_database)
Dir.chdir(app_path) do
- output = `bin/rails db:create`
+ output = rails("db:create")
assert_match(/Created database/, output)
assert File.exist?(expected_database)
assert_equal expected_database, ActiveRecord::Base.connection_config[:database]
- output = `bin/rails db:drop`
+ output = rails("db:drop")
assert_match(/Dropped database/, output)
assert !File.exist?(expected_database)
end
@@ -52,17 +52,16 @@ module ApplicationTests
def with_database_existing
Dir.chdir(app_path) do
set_database_url
- `bin/rails db:create`
+ rails "db:create"
yield
- `bin/rails db:drop`
+ rails "db:drop"
end
end
test "db:create failure because database exists" do
with_database_existing do
- output = `bin/rails db:create 2>&1`
+ output = rails("db:create")
assert_match(/already exists/, output)
- assert_equal 0, $?.exitstatus
end
end
@@ -77,24 +76,21 @@ module ApplicationTests
test "db:create failure because bad permissions" do
with_bad_permissions do
- output = `bin/rails db:create 2>&1`
+ output = rails("db:create", allow_failure: true)
assert_match(/Couldn't create database/, output)
assert_equal 1, $?.exitstatus
end
end
test "db:drop failure because database does not exist" do
- Dir.chdir(app_path) do
- output = `bin/rails db:drop:_unsafe --trace 2>&1`
- assert_match(/does not exist/, output)
- assert_equal 0, $?.exitstatus
- end
+ output = rails("db:drop:_unsafe", "--trace")
+ assert_match(/does not exist/, output)
end
test "db:drop failure because bad permissions" do
with_database_existing do
with_bad_permissions do
- output = `bin/rails db:drop 2>&1`
+ output = rails("db:drop", allow_failure: true)
assert_match(/Couldn't drop/, output)
assert_equal 1, $?.exitstatus
end
@@ -102,13 +98,11 @@ module ApplicationTests
end
def db_migrate_and_status(expected_database)
- Dir.chdir(app_path) do
- `bin/rails generate model book title:string;
- bin/rails db:migrate`
- output = `bin/rails db:migrate:status`
- assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output)
- assert_match(/up\s+\d{14}\s+Create books/, output)
- end
+ rails "generate", "model", "book", "title:string"
+ rails "db:migrate"
+ output = rails("db:migrate:status")
+ assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output)
+ assert_match(/up\s+\d{14}\s+Create books/, output)
end
test "db:migrate and db:migrate:status without database_url" do
@@ -124,8 +118,8 @@ module ApplicationTests
def db_schema_dump
Dir.chdir(app_path) do
- `bin/rails generate model book title:string;
- bin/rails db:migrate db:schema:dump`
+ rails "generate", "model", "book", "title:string"
+ rails "db:migrate", "db:schema:dump"
schema_dump = File.read("db/schema.rb")
assert_match(/create_table \"books\"/, schema_dump)
end
@@ -142,8 +136,8 @@ module ApplicationTests
def db_fixtures_load(expected_database)
Dir.chdir(app_path) do
- `bin/rails generate model book title:string;
- bin/rails db:migrate db:fixtures:load`
+ rails "generate", "model", "book", "title:string"
+ rails "db:migrate", "db:fixtures:load"
assert_match expected_database, ActiveRecord::Base.connection_config[:database]
require "#{app_path}/app/models/book"
assert_equal 2, Book.count
@@ -163,21 +157,20 @@ module ApplicationTests
test "db:fixtures:load with namespaced fixture" do
require "#{app_path}/config/environment"
- Dir.chdir(app_path) do
- `bin/rails generate model admin::book title:string;
- bin/rails db:migrate db:fixtures:load`
- require "#{app_path}/app/models/admin/book"
- assert_equal 2, Admin::Book.count
- end
+
+ rails "generate", "model", "admin::book", "title:string"
+ rails "db:migrate", "db:fixtures:load"
+ require "#{app_path}/app/models/admin/book"
+ assert_equal 2, Admin::Book.count
end
def db_structure_dump_and_load(expected_database)
Dir.chdir(app_path) do
- `bin/rails generate model book title:string;
- bin/rails db:migrate db:structure:dump`
+ rails "generate", "model", "book", "title:string"
+ rails "db:migrate", "db:structure:dump"
structure_dump = File.read("db/structure.sql")
assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"books\"/, structure_dump)
- `bin/rails environment db:drop db:structure:load`
+ rails "environment", "db:drop", "db:structure:load"
assert_match expected_database, ActiveRecord::Base.connection_config[:database]
require "#{app_path}/app/models/book"
#if structure is not loaded correctly, exception would be raised
@@ -196,82 +189,82 @@ module ApplicationTests
db_structure_dump_and_load database_url_db_name
end
+ test "db:structure:dump and db:structure:load set ar_internal_metadata" do
+ require "#{app_path}/config/environment"
+ db_structure_dump_and_load ActiveRecord::Base.configurations[Rails.env]["database"]
+
+ assert_equal "test", rails("runner", "-e", "test", "puts ActiveRecord::InternalMetadata[:environment]").strip
+ assert_equal "development", rails("runner", "puts ActiveRecord::InternalMetadata[:environment]").strip
+ end
+
test "db:structure:dump does not dump schema information when no migrations are used" do
- Dir.chdir(app_path) do
- # create table without migrations
- `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'`
+ # create table without migrations
+ rails "runner", "ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }"
- stderr_output = capture(:stderr) { `bin/rails db:structure:dump` }
- assert_empty stderr_output
- structure_dump = File.read("db/structure.sql")
- assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"posts\"/, structure_dump)
- end
+ stderr_output = capture(:stderr) { rails("db:structure:dump", stderr: true, allow_failure: true) }
+ assert_empty stderr_output
+ structure_dump = File.read("#{app_path}/db/structure.sql")
+ assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"posts\"/, structure_dump)
end
test "db:schema:load and db:structure:load do not purge the existing database" do
- Dir.chdir(app_path) do
- `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'`
+ rails "runner", "ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }"
- app_file "db/schema.rb", <<-RUBY
- ActiveRecord::Schema.define(version: 20140423102712) do
- create_table(:comments) {}
- end
- RUBY
+ app_file "db/schema.rb", <<-RUBY
+ ActiveRecord::Schema.define(version: 20140423102712) do
+ create_table(:comments) {}
+ end
+ RUBY
- list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip }
+ list_tables = lambda { rails("runner", "p ActiveRecord::Base.connection.tables").strip }
- assert_equal '["posts"]', list_tables[]
- `bin/rails db:schema:load`
- assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[]
+ assert_equal '["posts"]', list_tables[]
+ rails "db:schema:load"
+ assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[]
- app_file "db/structure.sql", <<-SQL
- CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255));
- SQL
+ app_file "db/structure.sql", <<-SQL
+ CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255));
+ SQL
- `bin/rails db:structure:load`
- assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata", "users"]', list_tables[]
- end
+ rails "db:structure:load"
+ assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata", "users"]', list_tables[]
end
test "db:schema:load with inflections" do
- Dir.chdir(app_path) do
- app_file "config/initializers/inflection.rb", <<-RUBY
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.irregular 'goose', 'geese'
- end
- RUBY
- app_file "config/initializers/primary_key_table_name.rb", <<-RUBY
- ActiveRecord::Base.primary_key_prefix_type = :table_name
- RUBY
- app_file "db/schema.rb", <<-RUBY
- ActiveRecord::Schema.define(version: 20140423102712) do
- create_table("goose".pluralize) do |t|
- t.string :name
- end
+ app_file "config/initializers/inflection.rb", <<-RUBY
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular 'goose', 'geese'
+ end
+ RUBY
+ app_file "config/initializers/primary_key_table_name.rb", <<-RUBY
+ ActiveRecord::Base.primary_key_prefix_type = :table_name
+ RUBY
+ app_file "db/schema.rb", <<-RUBY
+ ActiveRecord::Schema.define(version: 20140423102712) do
+ create_table("goose".pluralize) do |t|
+ t.string :name
end
- RUBY
+ end
+ RUBY
- `bin/rails db:schema:load`
+ rails "db:schema:load"
- tables = `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip
- assert_match(/"geese"/, tables)
+ tables = rails("runner", "p ActiveRecord::Base.connection.tables").strip
+ assert_match(/"geese"/, tables)
- columns = `bin/rails runner 'p ActiveRecord::Base.connection.columns("geese").map(&:name)'`.strip
- assert_equal columns, '["gooseid", "name"]'
- end
+ columns = rails("runner", "p ActiveRecord::Base.connection.columns('geese').map(&:name)").strip
+ assert_equal columns, '["gooseid", "name"]'
end
test "db:schema:load fails if schema.rb doesn't exist yet" do
- Dir.chdir(app_path) do
- stderr_output = capture(:stderr) { `bin/rails db:schema:load` }
- assert_match(/Run `rails db:migrate` to create it/, stderr_output)
- end
+ stderr_output = capture(:stderr) { rails("db:schema:load", stderr: true, allow_failure: true) }
+ assert_match(/Run `rails db:migrate` to create it/, stderr_output)
end
def db_test_load_structure
Dir.chdir(app_path) do
- `bin/rails generate model book title:string;
- bin/rails db:migrate db:structure:dump db:test:load_structure`
+ rails "generate", "model", "book", "title:string"
+ rails "db:migrate", "db:structure:dump", "db:test:load_structure"
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
ActiveRecord::Base.establish_connection :test
require "#{app_path}/app/models/book"
@@ -306,15 +299,52 @@ module ApplicationTests
puts ActiveRecord::Base.connection_config[:database]
RUBY
- Dir.chdir(app_path) do
- database_path = `bin/rails db:setup`
- assert_equal "development.sqlite3", File.basename(database_path.strip)
- end
+ database_path = rails("db:setup")
+ assert_equal "development.sqlite3", File.basename(database_path.strip)
ensure
ENV["RAILS_ENV"] = @old_rails_env
ENV["RACK_ENV"] = @old_rack_env
end
end
+
+ test "db:setup sets ar_internal_metadata" do
+ app_file "db/schema.rb", ""
+ rails "db:setup"
+
+ test_environment = lambda { rails("runner", "-e", "test", "puts ActiveRecord::InternalMetadata[:environment]").strip }
+ development_environment = lambda { rails("runner", "puts ActiveRecord::InternalMetadata[:environment]").strip }
+
+ assert_equal "test", test_environment.call
+ assert_equal "development", development_environment.call
+
+ app_file "db/structure.sql", ""
+ app_file "config/initializers/enable_sql_schema_format.rb", <<-RUBY
+ Rails.application.config.active_record.schema_format = :sql
+ RUBY
+
+ rails "db:setup"
+
+ assert_equal "test", test_environment.call
+ assert_equal "development", development_environment.call
+ end
+
+ test "db:test:prepare sets test ar_internal_metadata" do
+ app_file "db/schema.rb", ""
+ rails "db:test:prepare"
+
+ test_environment = lambda { rails("runner", "-e", "test", "puts ActiveRecord::InternalMetadata[:environment]").strip }
+
+ assert_equal "test", test_environment.call
+
+ app_file "db/structure.sql", ""
+ app_file "config/initializers/enable_sql_schema_format.rb", <<-RUBY
+ Rails.application.config.active_record.schema_format = :sql
+ RUBY
+
+ rails "db:test:prepare"
+
+ assert_equal "test", test_environment.call
+ end
end
end
end
diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb
index b25593ee1c..66e1ac9d99 100644
--- a/railties/test/application/rake/dev_test.rb
+++ b/railties/test/application/rake/dev_test.rb
@@ -17,7 +17,7 @@ module ApplicationTests
test "dev:cache creates file and outputs message" do
Dir.chdir(app_path) do
- output = `rails dev:cache`
+ output = rails("dev:cache")
assert File.exist?("tmp/caching-dev.txt")
assert_match(/Development mode is now being cached/, output)
end
@@ -25,8 +25,8 @@ module ApplicationTests
test "dev:cache deletes file and outputs message" do
Dir.chdir(app_path) do
- `rails dev:cache` # Create caching file.
- output = `rails dev:cache` # Delete caching file.
+ rails "dev:cache" # Create caching file.
+ output = rails("dev:cache") # Delete caching file.
assert_not File.exist?("tmp/caching-dev.txt")
assert_match(/Development mode is no longer being cached/, output)
end
@@ -34,12 +34,12 @@ module ApplicationTests
test "dev:cache touches tmp/restart.txt" do
Dir.chdir(app_path) do
- `rails dev:cache`
+ rails "dev:cache"
assert File.exist?("tmp/restart.txt")
prev_mtime = File.mtime("tmp/restart.txt")
sleep(1)
- `rails dev:cache`
+ rails "dev:cache"
curr_mtime = File.mtime("tmp/restart.txt")
assert_not_equal prev_mtime, curr_mtime
end
diff --git a/railties/test/application/rake/log_test.rb b/railties/test/application/rake/log_test.rb
index c52be5f396..678f26db26 100644
--- a/railties/test/application/rake/log_test.rb
+++ b/railties/test/application/rake/log_test.rb
@@ -23,7 +23,7 @@ module ApplicationTests
File.write("log/test.log", "test")
File.write("log/dummy.log", "dummy")
- `rails log:clear`
+ rails "log:clear"
assert_equal 0, File.size("log/test.log")
assert_equal 0, File.size("log/staging.log")
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index d130a58441..33f2b7038c 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -15,192 +15,178 @@ module ApplicationTests
end
test "running migrations with given scope" do
- Dir.chdir(app_path) do
- `bin/rails generate model user username:string password:string`
+ rails "generate", "model", "user", "username:string", "password:string"
- app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION
- class AMigration < ActiveRecord::Migration::Current
- end
- MIGRATION
+ app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION
+ class AMigration < ActiveRecord::Migration::Current
+ end
+ MIGRATION
- output = `bin/rails db:migrate SCOPE=bukkits`
- assert_no_match(/create_table\(:users\)/, output)
- assert_no_match(/CreateUsers/, output)
- assert_no_match(/add_column\(:users, :email, :string\)/, output)
+ output = rails("db:migrate", "SCOPE=bukkits")
+ assert_no_match(/create_table\(:users\)/, output)
+ assert_no_match(/CreateUsers/, output)
+ assert_no_match(/add_column\(:users, :email, :string\)/, output)
- assert_match(/AMigration: migrated/, output)
+ assert_match(/AMigration: migrated/, output)
- output = `bin/rails db:migrate SCOPE=bukkits VERSION=0`
- assert_no_match(/drop_table\(:users\)/, output)
- assert_no_match(/CreateUsers/, output)
- assert_no_match(/remove_column\(:users, :email\)/, output)
+ output = rails("db:migrate", "SCOPE=bukkits", "VERSION=0")
+ assert_no_match(/drop_table\(:users\)/, output)
+ assert_no_match(/CreateUsers/, output)
+ assert_no_match(/remove_column\(:users, :email\)/, output)
- assert_match(/AMigration: reverted/, output)
- end
+ assert_match(/AMigration: reverted/, output)
end
test "migration with empty version" do
- Dir.chdir(app_path) do
- output = `bin/rails db:migrate VERSION= 2>&1`
- assert_match(/Empty VERSION provided/, output)
+ output = rails("db:migrate", "VERSION=", allow_failure: true)
+ assert_match(/Empty VERSION provided/, output)
- output = `bin/rails db:migrate:redo VERSION= 2>&1`
- assert_match(/Empty VERSION provided/, output)
+ output = rails("db:migrate:redo", "VERSION=", allow_failure: true)
+ assert_match(/Empty VERSION provided/, output)
- output = `bin/rails db:migrate:up VERSION= 2>&1`
- assert_match(/VERSION is required/, output)
+ output = rails("db:migrate:up", "VERSION=", allow_failure: true)
+ assert_match(/VERSION is required/, output)
- output = `bin/rails db:migrate:up 2>&1`
- assert_match(/VERSION is required/, output)
+ output = rails("db:migrate:up", allow_failure: true)
+ assert_match(/VERSION is required/, output)
- output = `bin/rails db:migrate:down VERSION= 2>&1`
- assert_match(/VERSION is required - To go down one migration, use db:rollback/, output)
+ output = rails("db:migrate:down", "VERSION=", allow_failure: true)
+ assert_match(/VERSION is required - To go down one migration, use db:rollback/, output)
- output = `bin/rails db:migrate:down 2>&1`
- assert_match(/VERSION is required - To go down one migration, use db:rollback/, output)
- end
+ output = rails("db:migrate:down", allow_failure: true)
+ assert_match(/VERSION is required - To go down one migration, use db:rollback/, output)
end
test "model and migration generator with change syntax" do
- Dir.chdir(app_path) do
- `bin/rails generate model user username:string password:string;
- bin/rails generate migration add_email_to_users email:string`
-
- output = `bin/rails db:migrate`
- assert_match(/create_table\(:users\)/, output)
- assert_match(/CreateUsers: migrated/, output)
- assert_match(/add_column\(:users, :email, :string\)/, output)
- assert_match(/AddEmailToUsers: migrated/, output)
-
- output = `bin/rails db:rollback STEP=2`
- assert_match(/drop_table\(:users\)/, output)
- assert_match(/CreateUsers: reverted/, output)
- assert_match(/remove_column\(:users, :email, :string\)/, output)
- assert_match(/AddEmailToUsers: reverted/, output)
- end
+ rails "generate", "model", "user", "username:string", "password:string"
+ rails "generate", "migration", "add_email_to_users", "email:string"
+
+ output = rails("db:migrate")
+ assert_match(/create_table\(:users\)/, output)
+ assert_match(/CreateUsers: migrated/, output)
+ assert_match(/add_column\(:users, :email, :string\)/, output)
+ assert_match(/AddEmailToUsers: migrated/, output)
+
+ output = rails("db:rollback", "STEP=2")
+ assert_match(/drop_table\(:users\)/, output)
+ assert_match(/CreateUsers: reverted/, output)
+ assert_match(/remove_column\(:users, :email, :string\)/, output)
+ assert_match(/AddEmailToUsers: reverted/, output)
end
test "migration status when schema migrations table is not present" do
- output = Dir.chdir(app_path) { `bin/rails db:migrate:status 2>&1` }
+ output = rails("db:migrate:status", allow_failure: true)
assert_equal "Schema migrations table does not exist yet.\n", output
end
test "migration status" do
- Dir.chdir(app_path) do
- `bin/rails generate model user username:string password:string;
- bin/rails generate migration add_email_to_users email:string;
- bin/rails db:migrate`
+ rails "generate", "model", "user", "username:string", "password:string"
+ rails "generate", "migration", "add_email_to_users", "email:string"
+ rails "db:migrate"
- output = `bin/rails db:migrate:status`
+ output = rails("db:migrate:status")
- assert_match(/up\s+\d{14}\s+Create users/, output)
- assert_match(/up\s+\d{14}\s+Add email to users/, output)
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+Add email to users/, output)
- `bin/rails db:rollback STEP=1`
- output = `bin/rails db:migrate:status`
+ rails "db:rollback", "STEP=1"
+ output = rails("db:migrate:status")
- assert_match(/up\s+\d{14}\s+Create users/, output)
- assert_match(/down\s+\d{14}\s+Add email to users/, output)
- end
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/down\s+\d{14}\s+Add email to users/, output)
end
test "migration status without timestamps" do
add_to_config("config.active_record.timestamped_migrations = false")
- Dir.chdir(app_path) do
- `bin/rails generate model user username:string password:string;
- bin/rails generate migration add_email_to_users email:string;
- bin/rails db:migrate`
+ rails "generate", "model", "user", "username:string", "password:string"
+ rails "generate", "migration", "add_email_to_users", "email:string"
+ rails "db:migrate"
- output = `bin/rails db:migrate:status`
+ output = rails("db:migrate:status")
- assert_match(/up\s+\d{3,}\s+Create users/, output)
- assert_match(/up\s+\d{3,}\s+Add email to users/, output)
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/up\s+\d{3,}\s+Add email to users/, output)
- `bin/rails db:rollback STEP=1`
- output = `bin/rails db:migrate:status`
+ rails "db:rollback", "STEP=1"
+ output = rails("db:migrate:status")
- assert_match(/up\s+\d{3,}\s+Create users/, output)
- assert_match(/down\s+\d{3,}\s+Add email to users/, output)
- end
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/down\s+\d{3,}\s+Add email to users/, output)
end
test "migration status after rollback and redo" do
- Dir.chdir(app_path) do
- `bin/rails generate model user username:string password:string;
- bin/rails generate migration add_email_to_users email:string;
- bin/rails db:migrate`
+ rails "generate", "model", "user", "username:string", "password:string"
+ rails "generate", "migration", "add_email_to_users", "email:string"
+ rails "db:migrate"
- output = `bin/rails db:migrate:status`
+ output = rails("db:migrate:status")
- assert_match(/up\s+\d{14}\s+Create users/, output)
- assert_match(/up\s+\d{14}\s+Add email to users/, output)
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+Add email to users/, output)
- `bin/rails db:rollback STEP=2`
- output = `bin/rails db:migrate:status`
+ rails "db:rollback", "STEP=2"
+ output = rails("db:migrate:status")
- assert_match(/down\s+\d{14}\s+Create users/, output)
- assert_match(/down\s+\d{14}\s+Add email to users/, output)
+ assert_match(/down\s+\d{14}\s+Create users/, output)
+ assert_match(/down\s+\d{14}\s+Add email to users/, output)
- `bin/rails db:migrate:redo`
- output = `bin/rails db:migrate:status`
+ rails "db:migrate:redo"
+ output = rails("db:migrate:status")
- assert_match(/up\s+\d{14}\s+Create users/, output)
- assert_match(/up\s+\d{14}\s+Add email to users/, output)
- end
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+Add email to users/, output)
end
test "migration status after rollback and forward" do
- Dir.chdir(app_path) do
- `bin/rails generate model user username:string password:string;
- bin/rails generate migration add_email_to_users email:string;
- bin/rails db:migrate`
+ rails "generate", "model", "user", "username:string", "password:string"
+ rails "generate", "migration", "add_email_to_users", "email:string"
+ rails "db:migrate"
- output = `bin/rails db:migrate:status`
+ output = rails("db:migrate:status")
- assert_match(/up\s+\d{14}\s+Create users/, output)
- assert_match(/up\s+\d{14}\s+Add email to users/, output)
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+Add email to users/, output)
- `bin/rails db:rollback STEP=2`
- output = `bin/rails db:migrate:status`
+ rails "db:rollback", "STEP=2"
+ output = rails("db:migrate:status")
- assert_match(/down\s+\d{14}\s+Create users/, output)
- assert_match(/down\s+\d{14}\s+Add email to users/, output)
+ assert_match(/down\s+\d{14}\s+Create users/, output)
+ assert_match(/down\s+\d{14}\s+Add email to users/, output)
- `bin/rails db:forward STEP=2`
- output = `bin/rails db:migrate:status`
+ rails "db:forward", "STEP=2"
+ output = rails("db:migrate:status")
- assert_match(/up\s+\d{14}\s+Create users/, output)
- assert_match(/up\s+\d{14}\s+Add email to users/, output)
- end
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+Add email to users/, output)
end
test "raise error on any move when current migration does not exist" do
Dir.chdir(app_path) do
- `bin/rails generate model user username:string password:string;
- bin/rails generate migration add_email_to_users email:string;
- bin/rails db:migrate
- rm db/migrate/*email*.rb`
+ rails "generate", "model", "user", "username:string", "password:string"
+ rails "generate", "migration", "add_email_to_users", "email:string"
+ rails "db:migrate"
+ `rm db/migrate/*email*.rb`
- output = `bin/rails db:migrate:status`
+ output = rails("db:migrate:status")
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output)
- output = `bin/rails db:rollback 2>&1`
+ output = rails("db:rollback", allow_failure: true)
assert_match(/rails aborted!/, output)
assert_match(/ActiveRecord::UnknownMigrationVersionError:/, output)
assert_match(/No migration with version number\s\d{14}\./, output)
- output = `bin/rails db:migrate:status`
+ output = rails("db:migrate:status")
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output)
- output = `bin/rails db:forward 2>&1`
+ output = rails("db:forward", allow_failure: true)
assert_match(/rails aborted!/, output)
assert_match(/ActiveRecord::UnknownMigrationVersionError:/, output)
assert_match(/No migration with version number\s\d{14}\./, output)
- output = `bin/rails db:migrate:status`
+ output = rails("db:migrate:status")
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output)
end
@@ -209,69 +195,64 @@ module ApplicationTests
test "migration status after rollback and redo without timestamps" do
add_to_config("config.active_record.timestamped_migrations = false")
- Dir.chdir(app_path) do
- `bin/rails generate model user username:string password:string;
- bin/rails generate migration add_email_to_users email:string;
- bin/rails db:migrate`
+ rails "generate", "model", "user", "username:string", "password:string"
+ rails "generate", "migration", "add_email_to_users", "email:string"
+ rails "db:migrate"
- output = `bin/rails db:migrate:status`
+ output = rails("db:migrate:status")
- assert_match(/up\s+\d{3,}\s+Create users/, output)
- assert_match(/up\s+\d{3,}\s+Add email to users/, output)
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/up\s+\d{3,}\s+Add email to users/, output)
- `bin/rails db:rollback STEP=2`
- output = `bin/rails db:migrate:status`
+ rails "db:rollback", "STEP=2"
+ output = rails("db:migrate:status")
- assert_match(/down\s+\d{3,}\s+Create users/, output)
- assert_match(/down\s+\d{3,}\s+Add email to users/, output)
+ assert_match(/down\s+\d{3,}\s+Create users/, output)
+ assert_match(/down\s+\d{3,}\s+Add email to users/, output)
- `bin/rails db:migrate:redo`
- output = `bin/rails db:migrate:status`
+ rails "db:migrate:redo"
+ output = rails("db:migrate:status")
- assert_match(/up\s+\d{3,}\s+Create users/, output)
- assert_match(/up\s+\d{3,}\s+Add email to users/, output)
- end
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/up\s+\d{3,}\s+Add email to users/, output)
end
test "running migrations with not timestamp head migration files" do
- Dir.chdir(app_path) do
-
- app_file "db/migrate/1_one_migration.rb", <<-MIGRATION
- class OneMigration < ActiveRecord::Migration::Current
- end
- MIGRATION
+ app_file "db/migrate/1_one_migration.rb", <<-MIGRATION
+ class OneMigration < ActiveRecord::Migration::Current
+ end
+ MIGRATION
- app_file "db/migrate/02_two_migration.rb", <<-MIGRATION
- class TwoMigration < ActiveRecord::Migration::Current
- end
- MIGRATION
+ app_file "db/migrate/02_two_migration.rb", <<-MIGRATION
+ class TwoMigration < ActiveRecord::Migration::Current
+ end
+ MIGRATION
- `bin/rails db:migrate`
+ rails "db:migrate"
- output = `bin/rails db:migrate:status`
+ output = rails("db:migrate:status")
- assert_match(/up\s+001\s+One migration/, output)
- assert_match(/up\s+002\s+Two migration/, output)
- end
+ assert_match(/up\s+001\s+One migration/, output)
+ assert_match(/up\s+002\s+Two migration/, output)
end
test "schema generation when dump_schema_after_migration is set" do
add_to_config("config.active_record.dump_schema_after_migration = false")
Dir.chdir(app_path) do
- `bin/rails generate model book title:string`
- output = `bin/rails generate model author name:string`
+ rails "generate", "model", "book", "title:string"
+ output = rails("generate", "model", "author", "name:string")
version = output =~ %r{[^/]+db/migrate/(\d+)_create_authors\.rb} && $1
- `bin/rails db:migrate db:rollback db:forward db:migrate:up db:migrate:down VERSION=#{version}`
+ rails "db:migrate", "db:rollback", "db:forward", "db:migrate:up", "db:migrate:down", "VERSION=#{version}"
assert !File.exist?("db/schema.rb"), "should not dump schema when configured not to"
end
add_to_config("config.active_record.dump_schema_after_migration = true")
Dir.chdir(app_path) do
- `bin/rails generate model reviews book_id:integer`
- `bin/rails db:migrate`
+ rails "generate", "model", "reviews", "book_id:integer"
+ rails "db:migrate"
structure_dump = File.read("db/schema.rb")
assert_match(/create_table "reviews"/, structure_dump)
@@ -280,8 +261,8 @@ module ApplicationTests
test "default schema generation after migration" do
Dir.chdir(app_path) do
- `bin/rails generate model book title:string;
- bin/rails db:migrate`
+ rails "generate", "model", "book", "title:string"
+ rails "db:migrate"
structure_dump = File.read("db/schema.rb")
assert_match(/create_table "books"/, structure_dump)
@@ -290,12 +271,12 @@ module ApplicationTests
test "migration status migrated file is deleted" do
Dir.chdir(app_path) do
- `bin/rails generate model user username:string password:string;
- bin/rails generate migration add_email_to_users email:string;
- bin/rails db:migrate
- rm db/migrate/*email*.rb`
+ rails "generate", "model", "user", "username:string", "password:string"
+ rails "generate", "migration", "add_email_to_users", "email:string"
+ rails "db:migrate"
+ `rm db/migrate/*email*.rb`
- output = `bin/rails db:migrate:status`
+ output = rails("db:migrate:status")
assert_match(/up\s+\d{14}\s+Create users/, output)
assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output)
diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb
index ed96dcb6b1..8614560bf2 100644
--- a/railties/test/application/rake/restart_test.rb
+++ b/railties/test/application/rake/restart_test.rb
@@ -17,12 +17,12 @@ module ApplicationTests
test "rails restart touches tmp/restart.txt" do
Dir.chdir(app_path) do
- `bin/rails restart`
+ rails "restart"
assert File.exist?("tmp/restart.txt")
prev_mtime = File.mtime("tmp/restart.txt")
sleep(1)
- `bin/rails restart`
+ rails "restart"
curr_mtime = File.mtime("tmp/restart.txt")
assert_not_equal prev_mtime, curr_mtime
end
@@ -31,7 +31,7 @@ module ApplicationTests
test "rails restart should work even if tmp folder does not exist" do
Dir.chdir(app_path) do
FileUtils.remove_dir("tmp")
- `bin/rails restart`
+ rails "restart"
assert File.exist?("tmp/restart.txt")
end
end
diff --git a/railties/test/application/rake/tmp_test.rb b/railties/test/application/rake/tmp_test.rb
index 8641d140cb..048fd7adcc 100644
--- a/railties/test/application/rake/tmp_test.rb
+++ b/railties/test/application/rake/tmp_test.rb
@@ -26,7 +26,7 @@ module ApplicationTests
FileUtils.mkdir_p("tmp/screenshots")
FileUtils.touch("tmp/screenshots/fail.png")
- `rails tmp:clear`
+ rails "tmp:clear"
assert_not File.exist?("tmp/cache/cache_file")
assert_not File.exist?("tmp/sockets/socket_file")
@@ -36,9 +36,7 @@ module ApplicationTests
test "tmp:clear should work if folder missing" do
FileUtils.remove_dir("#{app_path}/tmp")
- errormsg = Dir.chdir(app_path) { `bin/rails tmp:clear` }
- assert_predicate $?, :success?
- assert_empty errormsg
+ rails "tmp:clear"
end
end
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index fe59bed874..bf89098645 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -1,11 +1,12 @@
# frozen_string_literal: true
require "isolation/abstract_unit"
+require "env_helpers"
require "active_support/core_ext/string/strip"
module ApplicationTests
class RakeTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation
+ include ActiveSupport::Testing::Isolation, EnvHelpers
def setup
build_app
@@ -26,20 +27,20 @@ module ApplicationTests
end
test "task is protected when previous migration was production" do
- Dir.chdir(app_path) do
- output = `bin/rails generate model product name:string;
- env RAILS_ENV=production bin/rails db:create db:migrate;
- env RAILS_ENV=production bin/rails db:test:prepare test 2>&1`
+ with_rails_env "production" do
+ rails "generate", "model", "product", "name:string"
+ rails "db:create", "db:migrate"
+ output = rails("db:test:prepare", allow_failure: true)
assert_match(/ActiveRecord::ProtectedEnvironmentError/, output)
end
end
def test_not_protected_when_previous_migration_was_not_production
- Dir.chdir(app_path) do
- output = `bin/rails generate model product name:string;
- env RAILS_ENV=test bin/rails db:create db:migrate;
- env RAILS_ENV=test bin/rails db:test:prepare test 2>&1`
+ with_rails_env "test" do
+ rails "generate", "model", "product", "name:string"
+ rails "db:create", "db:migrate"
+ output = rails("db:test:prepare", "test")
refute_match(/ActiveRecord::ProtectedEnvironmentError/, output)
end
@@ -56,7 +57,7 @@ module ApplicationTests
Rails.application.initialize!
RUBY
- assert_match("SuperMiddleware", Dir.chdir(app_path) { `bin/rails middleware` })
+ assert_match("SuperMiddleware", rails("middleware"))
end
def test_initializers_are_executed_in_rake_tasks
@@ -71,7 +72,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) { `bin/rails do_nothing` }
+ output = rails("do_nothing")
assert_match "Doing something...", output
end
@@ -92,7 +93,7 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) { `bin/rails do_nothing` }
+ output = rails("do_nothing")
assert_match "Hello world", output
end
@@ -100,6 +101,7 @@ module ApplicationTests
add_to_config <<-RUBY
rake_tasks do
task do_nothing: :environment do
+ puts 'There is nothing'
end
end
RUBY
@@ -112,15 +114,13 @@ module ApplicationTests
raise 'should not be pre-required for rake even eager_load=true'
RUBY
- Dir.chdir(app_path) do
- assert system("bin/rails do_nothing RAILS_ENV=production"),
- "should not be pre-required for rake even eager_load=true"
- end
+ output = rails("do_nothing", "RAILS_ENV=production")
+ assert_match "There is nothing", output
end
def test_code_statistics_sanity
assert_match "Code LOC: 25 Test LOC: 0 Code to Test Ratio: 1:0.0",
- Dir.chdir(app_path) { `bin/rails stats` }
+ rails("stats")
end
def test_rails_routes_calls_the_route_inspector
@@ -130,15 +130,10 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) { `bin/rails routes` }
+ output = rails("routes")
assert_equal <<-MESSAGE.strip_heredoc, output
- Prefix Verb URI Pattern Controller#Action
- cart GET /cart(.:format) cart#show
- rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
- rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show
- rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
- update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
- rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
+ Prefix Verb URI Pattern Controller#Action
+ cart GET /cart(.:format) cart#show
MESSAGE
end
@@ -158,7 +153,7 @@ module ApplicationTests
" DELETE /post(.:format) posts#destroy",
" POST /post(.:format) posts#create\n"].join("\n")
- output = Dir.chdir(app_path) { `bin/rails routes -c PostController` }
+ output = rails("routes", "-c", "PostController")
assert_equal expected_output, output
end
@@ -171,23 +166,19 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) { `bin/rails routes -g show` }
+ output = rails("routes", "-g", "show")
assert_equal <<-MESSAGE.strip_heredoc, output
- Prefix Verb URI Pattern Controller#Action
- cart GET /cart(.:format) cart#show
- rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
- rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show
- rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
+ Prefix Verb URI Pattern Controller#Action
+ cart GET /cart(.:format) cart#show
MESSAGE
- output = Dir.chdir(app_path) { `bin/rails routes -g POST` }
+ output = rails("routes", "-g", "POST")
assert_equal <<-MESSAGE.strip_heredoc, output
- Prefix Verb URI Pattern Controller#Action
- POST /cart(.:format) cart#create
- rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
+ Prefix Verb URI Pattern Controller#Action
+ POST /cart(.:format) cart#create
MESSAGE
- output = Dir.chdir(app_path) { `bin/rails routes -g basketballs` }
+ output = rails("routes", "-g", "basketballs")
assert_equal " Prefix Verb URI Pattern Controller#Action\n" \
"basketballs GET /basketballs(.:format) basketball#index\n", output
end
@@ -200,13 +191,13 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) { `bin/rails routes -c cart` }
+ output = rails("routes", "-c", "cart")
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
- output = Dir.chdir(app_path) { `bin/rails routes -c Cart` }
+ output = rails("routes", "-c", "Cart")
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
- output = Dir.chdir(app_path) { `bin/rails routes -c CartController` }
+ output = rails("routes", "-c", "CartController")
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
@@ -227,10 +218,10 @@ module ApplicationTests
" DELETE /admin/post(.:format) admin/posts#destroy",
" POST /admin/post(.:format) admin/posts#create\n"].join("\n")
- output = Dir.chdir(app_path) { `bin/rails routes -c Admin::PostController` }
+ output = rails("routes", "-c", "Admin::PostController")
assert_equal expected_output, output
- output = Dir.chdir(app_path) { `bin/rails routes -c PostController` }
+ output = rails("routes", "-c", "PostController")
assert_equal expected_output, output
end
@@ -240,13 +231,12 @@ module ApplicationTests
end
RUBY
- assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path) { `bin/rails routes` }
- Prefix Verb URI Pattern Controller#Action
- rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
- rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show
- rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
- update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
- rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
+ assert_equal <<-MESSAGE.strip_heredoc, rails("routes")
+ You don't have any routes defined!
+
+ Please add some routes in config/routes.rb.
+
+ For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html.
MESSAGE
end
@@ -260,13 +250,8 @@ module ApplicationTests
output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile routes` }
assert_equal <<-MESSAGE.strip_heredoc, output
- Prefix Verb URI Pattern Controller#Action
- cart GET /cart(.:format) cart#show
- rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show
- rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show
- rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
- update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update
- rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create
+ Prefix Verb URI Pattern Controller#Action
+ cart GET /cart(.:format) cart#show
MESSAGE
end
@@ -279,44 +264,37 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) { `bin/rails log_something RAILS_ENV=production && cat log/production.log` }
- assert_match "Sample log message", output
+ rails "log_something", "RAILS_ENV=production"
+ assert_match "Sample log message", File.read("#{app_path}/log/production.log")
end
def test_loading_specific_fixtures
- Dir.chdir(app_path) do
- `bin/rails generate model user username:string password:string;
- bin/rails generate model product name:string;
- bin/rails db:migrate`
- end
+ rails "generate", "model", "user", "username:string", "password:string"
+ rails "generate", "model", "product", "name:string"
+ rails "db:migrate"
require "#{rails_root}/config/environment"
# loading a specific fixture
- errormsg = Dir.chdir(app_path) { `bin/rails db:fixtures:load FIXTURES=products` }
- assert $?.success?, errormsg
+ rails "db:fixtures:load", "FIXTURES=products"
assert_equal 2, ::AppTemplate::Application::Product.count
assert_equal 0, ::AppTemplate::Application::User.count
end
def test_loading_only_yml_fixtures
- Dir.chdir(app_path) do
- `bin/rails db:migrate`
- end
+ rails "db:migrate"
app_file "test/fixtures/products.csv", ""
require "#{rails_root}/config/environment"
- errormsg = Dir.chdir(app_path) { `bin/rails db:fixtures:load` }
- assert $?.success?, errormsg
+ rails "db:fixtures:load"
end
def test_scaffold_tests_pass_by_default
- output = Dir.chdir(app_path) do
- `bin/rails generate scaffold user username:string password:string;
- RAILS_ENV=test bin/rails db:migrate test`
- end
+ rails "generate", "scaffold", "user", "username:string", "password:string"
+ with_rails_env("test") { rails("db:migrate") }
+ output = rails("test")
assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
@@ -332,22 +310,20 @@ module ApplicationTests
end
RUBY
- output = Dir.chdir(app_path) do
- `bin/rails generate scaffold user username:string password:string;
- RAILS_ENV=test bin/rails db:migrate test`
- end
+ rails "generate", "scaffold", "user", "username:string", "password:string"
+ with_rails_env("test") { rails("db:migrate") }
+ output = rails("test")
assert_match(/5 runs, 7 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
end
def test_scaffold_with_references_columns_tests_pass_by_default
- output = Dir.chdir(app_path) do
- `bin/rails generate model Product;
- bin/rails generate model Cart;
- bin/rails generate scaffold LineItems product:references cart:belongs_to;
- RAILS_ENV=test bin/rails db:migrate test`
- end
+ rails "generate", "model", "Product"
+ rails "generate", "model", "Cart"
+ rails "generate", "scaffold", "LineItems", "product:references", "cart:belongs_to"
+ with_rails_env("test") { rails("db:migrate") }
+ output = rails("test")
assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output)
assert_no_match(/Errors running/, output)
@@ -355,59 +331,47 @@ module ApplicationTests
def test_db_test_prepare_when_using_sql_format
add_to_config "config.active_record.schema_format = :sql"
- output = Dir.chdir(app_path) do
- `bin/rails generate scaffold user username:string;
- bin/rails db:migrate;
- bin/rails db:test:prepare 2>&1 --trace`
- end
+ rails "generate", "scaffold", "user", "username:string"
+ rails "db:migrate"
+ output = rails("db:test:prepare", "--trace")
assert_match(/Execute db:test:load_structure/, output)
end
def test_rake_dump_structure_should_respect_db_structure_env_variable
- Dir.chdir(app_path) do
- # ensure we have a schema_migrations table to dump
- `bin/rails db:migrate db:structure:dump SCHEMA=db/my_structure.sql`
- end
+ # ensure we have a schema_migrations table to dump
+ rails "db:migrate", "db:structure:dump", "SCHEMA=db/my_structure.sql"
assert File.exist?(File.join(app_path, "db", "my_structure.sql"))
end
def test_rake_dump_structure_should_be_called_twice_when_migrate_redo
add_to_config "config.active_record.schema_format = :sql"
- output = Dir.chdir(app_path) do
- `bin/rails g model post title:string;
- bin/rails db:migrate:redo 2>&1 --trace;`
- end
+ rails "g", "model", "post", "title:string"
+ output = rails("db:migrate:redo", "--trace")
# expect only Invoke db:structure:dump (first_time)
assert_no_match(/^\*\* Invoke db:structure:dump\s+$/, output)
end
def test_rake_dump_schema_cache
- Dir.chdir(app_path) do
- `bin/rails generate model post title:string;
- bin/rails generate model product name:string;
- bin/rails db:migrate db:schema:cache:dump`
- end
+ rails "generate", "model", "post", "title:string"
+ rails "generate", "model", "product", "name:string"
+ rails "db:migrate", "db:schema:cache:dump"
assert File.exist?(File.join(app_path, "db", "schema_cache.yml"))
end
def test_rake_clear_schema_cache
- Dir.chdir(app_path) do
- `bin/rails db:schema:cache:dump db:schema:cache:clear`
- end
+ rails "db:schema:cache:dump", "db:schema:cache:clear"
assert !File.exist?(File.join(app_path, "db", "schema_cache.yml"))
end
def test_copy_templates
- Dir.chdir(app_path) do
- `bin/rails app:templates:copy`
- %w(controller mailer scaffold).each do |dir|
- assert File.exist?(File.join(app_path, "lib", "templates", "erb", dir))
- end
- %w(controller helper scaffold_controller assets).each do |dir|
- assert File.exist?(File.join(app_path, "lib", "templates", "rails", dir))
- end
+ rails "app:templates:copy"
+ %w(controller mailer scaffold).each do |dir|
+ assert File.exist?(File.join(app_path, "lib", "templates", "erb", dir))
+ end
+ %w(controller helper scaffold_controller assets).each do |dir|
+ assert File.exist?(File.join(app_path, "lib", "templates", "rails", dir))
end
end
@@ -415,10 +379,7 @@ module ApplicationTests
app_file "config/initializers/dummy.rb", "puts 'Hello, World!'"
app_file "template.rb", ""
- output = Dir.chdir(app_path) do
- `bin/rails app:template LOCATION=template.rb`
- end
-
+ output = rails("app:template", "LOCATION=template.rb")
assert_match(/Hello, World!/, output)
end
end
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index a66f288795..aa5d495c97 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -26,22 +26,19 @@ module ApplicationTests
end
def test_should_include_runner_in_shebang_line_in_help_without_option
- assert_match "/rails runner", Dir.chdir(app_path) { `bin/rails runner` }
+ assert_match "/rails runner", rails("runner", allow_failure: true)
end
def test_should_include_runner_in_shebang_line_in_help
- assert_match "/rails runner", Dir.chdir(app_path) { `bin/rails runner --help` }
+ assert_match "/rails runner", rails("runner", "--help")
end
def test_should_run_ruby_statement
- assert_match "42", Dir.chdir(app_path) { `bin/rails runner "puts User.count"` }
+ assert_match "42", rails("runner", "puts User.count")
end
def test_should_set_argv_when_running_code
- output = Dir.chdir(app_path) {
- # Both long and short args, at start and end of ARGV
- `bin/rails runner "puts ARGV.join(',')" --foo a1 -b a2 a3 --moo`
- }
+ output = rails("runner", "puts ARGV.join(',')", "--foo", "a1", "-b", "a2", "a3", "--moo")
assert_equal "--foo,a1,-b,a2,a3,--moo", output.chomp
end
@@ -50,7 +47,7 @@ module ApplicationTests
puts User.count
SCRIPT
- assert_match "42", Dir.chdir(app_path) { `bin/rails runner "bin/count_users.rb"` }
+ assert_match "42", rails("runner", "bin/count_users.rb")
end
def test_no_minitest_loaded_in_production_mode
@@ -67,7 +64,7 @@ module ApplicationTests
puts $0
SCRIPT
- assert_match "bin/dollar0.rb", Dir.chdir(app_path) { `bin/rails runner "bin/dollar0.rb"` }
+ assert_match "bin/dollar0.rb", rails("runner", "bin/dollar0.rb")
end
def test_should_set_dollar_program_name_to_file
@@ -75,7 +72,7 @@ module ApplicationTests
puts $PROGRAM_NAME
SCRIPT
- assert_match "bin/program_name.rb", Dir.chdir(app_path) { `bin/rails runner "bin/program_name.rb"` }
+ assert_match "bin/program_name.rb", rails("runner", "bin/program_name.rb")
end
def test_passes_extra_args_to_file
@@ -83,7 +80,7 @@ module ApplicationTests
p ARGV
SCRIPT
- assert_match %w( a b ).to_s, Dir.chdir(app_path) { `bin/rails runner "bin/program_name.rb" a b` }
+ assert_match %w( a b ).to_s, rails("runner", "bin/program_name.rb", "a", "b")
end
def test_should_run_stdin
@@ -101,35 +98,47 @@ module ApplicationTests
end
RUBY
- assert_match "true", Dir.chdir(app_path) { `bin/rails runner "puts Rails.application.config.ran"` }
+ assert_match "true", rails("runner", "puts Rails.application.config.ran")
end
def test_default_environment
- assert_match "development", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` }
+ assert_match "development", rails("runner", "puts Rails.env")
end
def test_runner_detects_syntax_errors
- output = Dir.chdir(app_path) { `bin/rails runner "puts 'hello world" 2>&1` }
+ output = rails("runner", "puts 'hello world", allow_failure: true)
assert_not $?.success?
assert_match "unterminated string meets end of file", output
end
def test_runner_detects_bad_script_name
- output = Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` }
+ output = rails("runner", "iuiqwiourowe", allow_failure: true)
assert_not $?.success?
assert_match "undefined local variable or method `iuiqwiourowe' for", output
end
def test_environment_with_rails_env
with_rails_env "production" do
- assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` }
+ assert_match "production", rails("runner", "puts Rails.env")
end
end
def test_environment_with_rack_env
with_rack_env "production" do
- assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` }
+ assert_match "production", rails("runner", "puts Rails.env")
end
end
+
+ def test_can_call_same_name_class_as_defined_in_thor
+ app_file "app/models/task.rb", <<-MODEL
+ class Task
+ def self.count
+ 42
+ end
+ end
+ MODEL
+
+ assert_match "42", rails("runner", "puts Task.count")
+ end
end
end
diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb
index 6db9a3b9e8..2238f4f63a 100644
--- a/railties/test/application/server_test.rb
+++ b/railties/test/application/server_test.rb
@@ -42,7 +42,7 @@ module ApplicationTests
pid = Process.spawn("#{app_path}/bin/rails server -P tmp/dummy.pid", in: slave, out: slave, err: slave)
assert_output("Listening", master)
- Dir.chdir(app_path) { system("bin/rails restart") }
+ rails("restart")
assert_output("Restarting", master)
assert_output("Inherited", master)
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 1eda2a5eaf..e92a0466dd 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -59,7 +59,7 @@ module ApplicationTests
def; end
RUBY
- error = capture(:stderr) { run_test_command("test/models/error_test.rb") }
+ error = capture(:stderr) { run_test_command("test/models/error_test.rb", stderr: true) }
assert_match "syntax error", error
end
@@ -91,13 +91,11 @@ module ApplicationTests
create_test_file :unit, "baz_unit"
create_test_file :controllers, "foobar_controller"
- Dir.chdir(app_path) do
- `bin/rails test:units`.tap do |output|
- assert_match "FooTest", output
- assert_match "BarHelperTest", output
- assert_match "BazUnitTest", output
- assert_match "3 runs, 3 assertions, 0 failures", output
- end
+ rails("test:units").tap do |output|
+ assert_match "FooTest", output
+ assert_match "BarHelperTest", output
+ assert_match "BazUnitTest", output
+ assert_match "3 runs, 3 assertions, 0 failures", output
end
end
@@ -140,13 +138,11 @@ module ApplicationTests
create_test_file :functional, "baz_functional"
create_test_file :models, "foo"
- Dir.chdir(app_path) do
- `bin/rails test:functionals`.tap do |output|
- assert_match "FooMailerTest", output
- assert_match "BarControllerTest", output
- assert_match "BazFunctionalTest", output
- assert_match "3 runs, 3 assertions, 0 failures", output
- end
+ rails("test:functionals").tap do |output|
+ assert_match "FooMailerTest", output
+ assert_match "BarControllerTest", output
+ assert_match "BazFunctionalTest", output
+ assert_match "3 runs, 3 assertions, 0 failures", output
end
end
@@ -524,11 +520,11 @@ module ApplicationTests
create_test_file :models, "post", pass: false
assert_match(/Interrupt/,
- capture(:stderr) { run_test_command("test/models/post_test.rb --fail-fast") })
+ capture(:stderr) { run_test_command("test/models/post_test.rb --fail-fast", stderr: true) })
end
def test_raise_error_when_specified_file_does_not_exist
- error = capture(:stderr) { run_test_command("test/not_exists.rb") }
+ error = capture(:stderr) { run_test_command("test/not_exists.rb", stderr: true) }
assert_match(%r{cannot load such file.+test/not_exists\.rb}, error)
end
@@ -554,14 +550,16 @@ module ApplicationTests
def test_rails_db_create_all_restores_db_connection
create_test_file :models, "account"
- output = Dir.chdir(app_path) { `bin/rails db:create:all db:migrate && echo ".tables" | rails dbconsole` }
+ rails "db:create:all", "db:migrate"
+ output = Dir.chdir(app_path) { `echo ".tables" | rails dbconsole` }
assert_match "ar_internal_metadata", output, "tables should be dumped"
end
def test_rails_db_create_all_restores_db_connection_after_drop
create_test_file :models, "account"
- Dir.chdir(app_path) { `bin/rails db:create:all` } # create all to avoid warnings
- output = Dir.chdir(app_path) { `bin/rails db:drop:all db:create:all db:migrate && echo ".tables" | rails dbconsole` }
+ rails "db:create:all" # create all to avoid warnings
+ rails "db:drop:all", "db:create:all", "db:migrate"
+ output = Dir.chdir(app_path) { `echo ".tables" | rails dbconsole` }
assert_match "ar_internal_metadata", output, "tables should be dumped"
end
@@ -601,7 +599,7 @@ module ApplicationTests
end
RUBY
assert_match(/warning: assigned but unused variable/,
- capture(:stderr) { run_test_command("test/models/warnings_test.rb -w") })
+ capture(:stderr) { run_test_command("test/models/warnings_test.rb -w", stderr: true) })
end
def test_reset_sessions_before_rollback_on_system_tests
@@ -679,12 +677,12 @@ module ApplicationTests
end
private
- def run_test_command(arguments = "test/unit/test_test.rb")
- Dir.chdir(app_path) { `bin/rails t #{arguments}` }
+ def run_test_command(arguments = "test/unit/test_test.rb", **opts)
+ rails "t", *Shellwords.split(arguments), allow_failure: true, **opts
end
def create_model_with_fixture
- script "generate model user name:string"
+ rails "generate", "model", "user", "name:string"
app_file "test/fixtures/users.yml", <<-YAML.strip_heredoc
vampire:
@@ -755,17 +753,17 @@ module ApplicationTests
end
def create_scaffold
- script "generate scaffold user name:string"
- Dir.chdir(app_path) { File.exist?("app/models/user.rb") }
+ rails "generate", "scaffold", "user", "name:string"
+ assert File.exist?("#{app_path}/app/models/user.rb")
run_migration
end
def create_controller
- script "generate controller admin/dashboard index"
+ rails "generate", "controller", "admin/dashboard", "index"
end
def run_migration
- Dir.chdir(app_path) { `bin/rails db:migrate` }
+ rails "db:migrate"
end
end
end
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index 7e2364c0a6..0a51e98656 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -102,7 +102,7 @@ module ApplicationTests
end
test "ruby schema migrations" do
- output = script("generate model user name:string")
+ output = rails("generate", "model", "user", "name:string")
version = output.match(/(\d+)_create_users\.rb/)[1]
app_file "test/models/user_test.rb", <<-RUBY
@@ -139,7 +139,7 @@ module ApplicationTests
end
test "sql structure migrations" do
- output = script("generate model user name:string")
+ output = rails("generate", "model", "user", "name:string")
version = output.match(/(\d+)_create_users\.rb/)[1]
app_file "test/models/user_test.rb", <<-RUBY
@@ -178,7 +178,7 @@ module ApplicationTests
end
test "sql structure migrations when adding column to existing table" do
- output_1 = script("generate model user name:string")
+ output_1 = rails("generate", "model", "user", "name:string")
version_1 = output_1.match(/(\d+)_create_users\.rb/)[1]
app_file "test/models/user_test.rb", <<-RUBY
@@ -203,7 +203,7 @@ module ApplicationTests
assert_successful_test_run("models/user_test.rb")
- output_2 = script("generate migration add_email_to_users")
+ output_2 = rails("generate", "migration", "add_email_to_users")
version_2 = output_2.match(/(\d+)_add_email_to_users\.rb/)[1]
app_file "test/models/user_test.rb", <<-RUBY
@@ -231,7 +231,7 @@ module ApplicationTests
# For now, the user has to synchronize the schema manually.
# This test-case serves as a reminder for this use-case.
test "manually synchronize test schema after rollback" do
- output = script("generate model user name:string")
+ output = rails("generate", "model", "user", "name:string")
version = output.match(/(\d+)_create_users\.rb/)[1]
app_file "test/models/user_test.rb", <<-RUBY
@@ -265,7 +265,7 @@ module ApplicationTests
assert_successful_test_run "models/user_test.rb"
- Dir.chdir(app_path) { `bin/rails db:test:prepare` }
+ rails "db:test:prepare"
assert_unsuccessful_run "models/user_test.rb", <<-ASSERTION
Expected: ["id", "name"]
@@ -274,7 +274,7 @@ Expected: ["id", "name"]
end
test "hooks for plugins" do
- output = script("generate model user name:string")
+ output = rails("generate", "model", "user", "name:string")
version = output.match(/(\d+)_create_users\.rb/)[1]
app_file "lib/tasks/hooks.rake", <<-RUBY
@@ -334,7 +334,7 @@ Expected: ["id", "name"]
end
def run_test_file(name, options = {})
- Dir.chdir(app_path) { `bin/rails test "#{app_path}/test/#{name}" 2>&1` }
+ rails "test", "#{app_path}/test/#{name}", allow_failure: true
end
end
end
diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb
index 4f962db6c4..f22b9fda3d 100644
--- a/railties/test/application/url_generation_test.rb
+++ b/railties/test/application/url_generation_test.rb
@@ -16,7 +16,6 @@ module ApplicationTests
require "action_view/railtie"
class MyApp < Rails::Application
- secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, key: "_myapp_session"
config.active_support.deprecation = :log
config.eager_load = false
diff --git a/railties/test/application/version_test.rb b/railties/test/application/version_test.rb
index 62d3c7b254..ae85cf8f05 100644
--- a/railties/test/application/version_test.rb
+++ b/railties/test/application/version_test.rb
@@ -15,12 +15,12 @@ class VersionTest < ActiveSupport::TestCase
end
test "command works" do
- output = Dir.chdir(app_path) { `bin/rails version` }
+ output = rails("version")
assert_equal "Rails #{Rails.gem_version}\n", output
end
test "short-cut alias works" do
- output = Dir.chdir(app_path) { `bin/rails -v` }
+ output = rails("-v")
assert_equal "Rails #{Rails.gem_version}\n", output
end
end
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index 7eb26c355c..45ab8d87ff 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -172,21 +172,8 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
end
def parse_arguments(args)
- Rails::Command::ConsoleCommand.class_eval do
- alias_method :old_perform, :perform
- define_method(:perform) do
- extract_environment_option_from_argument
-
- options
- end
- end
-
- Rails::Command.invoke(:console, args)
- ensure
- Rails::Command::ConsoleCommand.class_eval do
- undef_method :perform
- alias_method :perform, :old_perform
- undef_method :old_perform
- end
+ command = Rails::Command::ConsoleCommand.new([], args)
+ command.send(:extract_environment_option_from_argument)
+ command.options
end
end
diff --git a/railties/test/commands/credentials_test.rb b/railties/test/commands/credentials_test.rb
new file mode 100644
index 0000000000..743fb5f788
--- /dev/null
+++ b/railties/test/commands/credentials_test.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require "isolation/abstract_unit"
+require "env_helpers"
+require "rails/command"
+require "rails/commands/credentials/credentials_command"
+
+class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation, EnvHelpers
+
+ setup { build_app }
+
+ teardown { teardown_app }
+
+ test "edit without editor gives hint" do
+ assert_match "No $EDITOR to open credentials in", run_edit_command(editor: "")
+ end
+
+ test "edit credentials" do
+ # Run twice to ensure credentials can be reread after first edit pass.
+ 2.times do
+ assert_match(/access_key_id: 123/, run_edit_command)
+ end
+ end
+
+ test "show credentials" do
+ assert_match(/access_key_id: 123/, run_show_command)
+ end
+
+ test "edit command does not add master key to gitignore when already exist" do
+ run_edit_command
+
+ Dir.chdir(app_path) do
+ gitignore = File.read(".gitignore")
+ assert_equal 1, gitignore.scan(%r|config/master\.key|).length
+ end
+ end
+
+ private
+ def run_edit_command(editor: "cat")
+ switch_env("EDITOR", editor) do
+ rails "credentials:edit"
+ end
+ end
+
+ def run_show_command
+ rails "credentials:show"
+ end
+end
diff --git a/railties/test/commands/secrets_test.rb b/railties/test/commands/secrets_test.rb
index a1213a57f8..66a81826e3 100644
--- a/railties/test/commands/secrets_test.rb
+++ b/railties/test/commands/secrets_test.rb
@@ -1,11 +1,12 @@
# frozen_string_literal: true
require "isolation/abstract_unit"
+require "env_helpers"
require "rails/command"
require "rails/commands/secrets/secrets_command"
class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation
+ include ActiveSupport::Testing::Isolation, EnvHelpers
def setup
build_app
@@ -36,14 +37,16 @@ class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
private
def run_edit_command(editor: "cat")
- Dir.chdir(app_path) { `EDITOR="#{editor}" bin/rails secrets:edit` }
+ switch_env("EDITOR", editor) do
+ rails "secrets:edit"
+ end
end
def run_show_command
- Dir.chdir(app_path) { `bin/rails secrets:show` }
+ rails "secrets:show"
end
def run_setup_command
- Dir.chdir(app_path) { `bin/rails secrets:setup` }
+ rails "secrets:setup"
end
end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 556c2289e7..33715ea75f 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -22,6 +22,18 @@ class Rails::ServerTest < ActiveSupport::TestCase
assert_nil options[:server]
end
+ def test_daemon_with_option
+ args = ["-d"]
+ options = parse_arguments(args)
+ assert_equal true, options[:daemonize]
+ end
+
+ def test_daemon_without_option
+ args = []
+ options = parse_arguments(args)
+ assert_equal false, options[:daemonize]
+ end
+
def test_server_option_without_environment
args = ["thin"]
with_rack_env nil do
@@ -81,6 +93,18 @@ class Rails::ServerTest < ActiveSupport::TestCase
assert_equal false, options[:caching]
end
+ def test_early_hints_with_option
+ args = ["--early-hints"]
+ options = parse_arguments(args)
+ assert_equal true, options[:early_hints]
+ end
+
+ def test_early_hints_is_nil_by_default
+ args = []
+ options = parse_arguments(args)
+ assert_nil options[:early_hints]
+ end
+
def test_log_stdout
with_rack_env nil do
with_rails_env nil do
diff --git a/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml b/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml
deleted file mode 100644
index fe80872a16..0000000000
--- a/railties/test/fixtures/about_yml_plugins/bad_about_yml/about.yml
+++ /dev/null
@@ -1 +0,0 @@
-# an empty YAML file - any content in here seems to get parsed as a string \ No newline at end of file
diff --git a/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb b/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb
deleted file mode 100644
index 1a82a2bdd4..0000000000
--- a/railties/test/fixtures/about_yml_plugins/bad_about_yml/init.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-# frozen_string_literal: true
-
-# intentionally empty
diff --git a/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb b/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb
deleted file mode 100644
index 1a82a2bdd4..0000000000
--- a/railties/test/fixtures/about_yml_plugins/plugin_without_about_yml/init.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-# frozen_string_literal: true
-
-# intentionally empty
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index d141b1d4b4..7791d472d8 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -125,7 +125,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
config/locales/en.yml
config/puma.rb
config/routes.rb
- config/secrets.yml
+ config/credentials.yml.enc
config/spring.rb
config/storage.yml
db
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index f64ebf5f1f..160ad1a928 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -64,7 +64,7 @@ DEFAULT_APP_FILES = %w(
config/locales/en.yml
config/puma.rb
config/routes.rb
- config/secrets.yml
+ config/credentials.yml.enc
config/spring.rb
config/storage.yml
db
@@ -287,8 +287,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [app_root, "--skip-action-cable"]
FileUtils.cd(app_root) do
- # For avoid conflict file
- FileUtils.rm("#{app_root}/config/secrets.yml")
quietly { system("bin/rails app:update") }
end
@@ -414,6 +412,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile" do |content|
assert_no_match(/capybara/, content)
assert_no_match(/selenium-webdriver/, content)
+ assert_no_match(/chromedriver-helper/, content)
end
assert_no_directory("test")
@@ -424,6 +423,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile" do |content|
assert_no_match(/capybara/, content)
assert_no_match(/selenium-webdriver/, content)
+ assert_no_match(/chromedriver-helper/, content)
end
assert_directory("test")
@@ -562,6 +562,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_no_match(/run git init/, output)
end
+ def test_quiet_option
+ output = run_generator [File.join(destination_root, "myapp"), "--quiet"]
+ assert_empty output
+ end
+
def test_application_name_with_spaces
path = File.join(destination_root, "foo bar")
@@ -739,7 +744,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
sequence = ["git init", "install", "exec spring binstub --all", "echo ran after_bundle"]
@sequence_step ||= 0
- ensure_bundler_first = -> command do
+ ensure_bundler_first = -> command, options = nil do
assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}"
@sequence_step += 1
end
diff --git a/railties/test/generators/argv_scrubber_test.rb b/railties/test/generators/argv_scrubber_test.rb
index 3f4c985a33..9ef61dc978 100644
--- a/railties/test/generators/argv_scrubber_test.rb
+++ b/railties/test/generators/argv_scrubber_test.rb
@@ -82,9 +82,8 @@ module Rails
file.puts "--hello --world"
file.flush
- message = nil
scrubber = Class.new(ARGVScrubber) {
- define_method(:puts) { |msg| message = msg }
+ define_method(:puts) { |msg| }
}.new ["new", "--rc=#{file.path}"]
args = scrubber.prepare!
assert_equal ["--hello", "--world"], args
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index 67f05926e3..967754c813 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -59,7 +59,7 @@ class NamedBaseTest < Rails::Generators::TestCase
ActiveRecord::Base.pluralize_table_names = original_pluralize_table_names
end
- def test_scaffold_plural_names
+ def test_namespaced_scaffold_plural_names
g = generator ["admin/foo"]
assert_name g, "admin/foos", :controller_name
assert_name g, %w(admin), :controller_class_path
@@ -69,7 +69,7 @@ class NamedBaseTest < Rails::Generators::TestCase
assert_name g, "admin.foos", :controller_i18n_scope
end
- def test_scaffold_plural_names_as_ruby
+ def test_namespaced_scaffold_plural_names_as_ruby
g = generator ["Admin::Foo"]
assert_name g, "Admin::Foos", :controller_name
assert_name g, %w(admin), :controller_class_path
@@ -131,6 +131,19 @@ class NamedBaseTest < Rails::Generators::TestCase
assert_name g, "admin/foos", :controller_file_path
assert_name g, "foos", :controller_file_name
assert_name g, "admin.foos", :controller_i18n_scope
+ assert_name g, "admin_user", :singular_route_name
+ assert_name g, "admin_users", :plural_route_name
+ assert_name g, "[:admin, @user]", :redirect_resource_name
+ assert_name g, "[:admin, user]", :model_resource_name
+ assert_name g, "admin_users", :index_helper
+ end
+
+ def test_scaffold_plural_names
+ g = generator ["User"]
+ assert_name g, "@user", :redirect_resource_name
+ assert_name g, "user", :model_resource_name
+ assert_name g, "user", :singular_route_name
+ assert_name g, "users", :plural_route_name
end
private
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index 205014c80a..4b75a31f17 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -152,7 +152,7 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase
assert_file "app/mailers/test_app/notifier_mailer.rb" do |mailer|
assert_match(/module TestApp/, mailer)
assert_match(/class NotifierMailer < ApplicationMailer/, mailer)
- assert_no_match(/default from: "from@example.com"/, mailer)
+ assert_no_match(/default from: "from@example\.com"/, mailer)
end
end
diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb
index 28a76f0617..89c3f1e496 100644
--- a/railties/test/generators/plugin_test_runner_test.rb
+++ b/railties/test/generators/plugin_test_runner_test.rb
@@ -105,6 +105,12 @@ class PluginTestRunnerTest < ActiveSupport::TestCase
capture(:stderr) { run_test_command("test/models/warnings_test.rb -w") })
end
+ def test_run_rake_test
+ create_test_file "foo"
+ result = Dir.chdir(plugin_path) { `rake test TEST=test/foo_test.rb` }
+ assert_match "1 runs, 1 assertions, 0 failures", result
+ end
+
private
def plugin_path
"#{@destination_root}/bukkits"
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 384524aba9..513b037043 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -174,6 +174,29 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_instance_method :index, content do |m|
assert_match("@users = User.all", m)
end
+
+ assert_instance_method :create, content do |m|
+ assert_match("redirect_to [:admin, @user]", m)
+ end
+
+ assert_instance_method :update, content do |m|
+ assert_match("redirect_to [:admin, @user]", m)
+ end
+ end
+
+ assert_file "app/views/admin/users/index.html.erb" do |content|
+ assert_match("'Show', [:admin, user]", content)
+ assert_match("'Edit', edit_admin_user_path(user)", content)
+ assert_match("'Destroy', [:admin, user]", content)
+ assert_match("'New User', new_admin_user_path", content)
+ end
+
+ assert_file "app/views/admin/users/new.html.erb" do |content|
+ assert_match("'Back', admin_users_path", content)
+ end
+
+ assert_file "app/views/admin/users/_form.html.erb" do |content|
+ assert_match("model: [:admin, user]", content)
end
end
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 56c9b37e1b..654d16de65 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -149,7 +149,7 @@ module SharedGeneratorTests
end
assert_file "#{application_path}/config/environments/production.rb" do |content|
assert_match(/# config\.action_mailer\.raise_delivery_errors = false/, content)
- assert_match(/^ config\.read_encrypted_secrets = true/, content)
+ assert_match(/^ # config\.require_master_key = true/, content)
end
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index e79bf09654..29daaacdb2 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -56,10 +56,7 @@ module TestHelpers
@app ||= begin
ENV["RAILS_ENV"] = env
- # FIXME: shush Sass warning spam, not relevant to testing Railties
- Kernel.silence_warnings do
- require "#{app_path}/config/environment"
- end
+ require "#{app_path}/config/environment"
Rails.application
end
@@ -108,7 +105,6 @@ module TestHelpers
def build_app(options = {})
@prev_rails_env = ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
- ENV["SECRET_KEY_BASE"] ||= SecureRandom.hex(16)
FileUtils.rm_rf(app_path)
FileUtils.cp_r(app_template_path, app_path)
@@ -166,9 +162,10 @@ module TestHelpers
require "action_controller/railtie"
require "action_view/railtie"
- @app = Class.new(Rails::Application)
+ @app = Class.new(Rails::Application) do
+ def self.name; "RailtiesTestApp"; end
+ end
@app.config.eager_load = false
- @app.secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4"
@app.config.session_store :cookie_store, key: "_myapp_session"
@app.config.active_support.deprecation = :log
@app.config.active_support.test_order = :random
@@ -236,10 +233,86 @@ module TestHelpers
end
end
- def script(script)
- Dir.chdir(app_path) do
- `#{Gem.ruby} #{app_path}/bin/rails #{script}`
+ # Invoke a bin/rails command inside the app
+ #
+ # allow_failure:: true to return normally if the command exits with
+ # a non-zero status. By default, this method will raise.
+ # stderr:: true to pass STDERR output straight to the "real" STDERR.
+ # By default, the STDERR and STDOUT of the process will be
+ # combined in the returned string.
+ def rails(*args, allow_failure: false, stderr: false)
+ args = args.flatten
+ fork = true
+
+ command = "bin/rails #{Shellwords.join args}#{' 2>&1' unless stderr}"
+
+ # Don't fork if the environment has disabled it
+ fork = false if ENV["NO_FORK"]
+
+ # Don't fork if the runtime isn't able to
+ fork = false if !Process.respond_to?(:fork)
+
+ # Don't fork if we're re-invoking minitest
+ fork = false if args.first == "t" || args.grep(/\Atest(:|\z)/).any?
+
+ if fork
+ out_read, out_write = IO.pipe
+ if stderr
+ err_read, err_write = IO.pipe
+ else
+ err_write = out_write
+ end
+
+ pid = fork do
+ out_read.close
+ err_read.close if err_read
+
+ $stdin.reopen(File::NULL, "r")
+ $stdout.reopen(out_write)
+ $stderr.reopen(err_write)
+
+ at_exit do
+ case $!
+ when SystemExit
+ exit! $!.status
+ when nil
+ exit! 0
+ else
+ err_write.puts "#{$!.class}: #{$!}"
+ exit! 1
+ end
+ end
+
+ Rails.instance_variable_set :@_env, nil
+
+ $-v = $-w = false
+ Dir.chdir app_path unless Dir.pwd == app_path
+
+ ARGV.replace(args)
+ load "./bin/rails"
+
+ exit! 0
+ end
+
+ out_write.close
+
+ if err_read
+ err_write.close
+
+ $stderr.write err_read.read
+ end
+
+ output = out_read.read
+
+ Process.waitpid pid
+
+ else
+ output = `cd #{app_path}; #{command}`
end
+
+ raise "rails command failed (#{$?.exitstatus}): #{command}\n#{output}" unless allow_failure || $?.success?
+
+ output
end
def add_to_top_of_config(str)
@@ -299,7 +372,7 @@ module TestHelpers
end
def use_frameworks(arr)
- to_remove = [:actionmailer, :activerecord] - arr
+ to_remove = [:actionmailer, :activerecord, :activestorage, :activejob] - arr
if to_remove.include?(:activerecord)
remove_from_config "config.active_record.*"
@@ -329,4 +402,25 @@ Module.new do
File.open("#{app_template_path}/config/boot.rb", "w") do |f|
f.puts "require 'rails/all'"
end
+
+ # Fake 'Bundler.require' -- we run using the repo's Gemfile, not an
+ # app-specific one: we don't want to require every gem that lists.
+ contents = File.read("#{app_template_path}/config/application.rb")
+ contents.sub!(/^Bundler\.require.*/, "%w(turbolinks).each { |r| require r }")
+ File.write("#{app_template_path}/config/application.rb", contents)
+
+ require "rails"
+
+ require "active_model"
+ require "active_job"
+ require "active_record"
+ require "action_controller"
+ require "action_mailer"
+ require "action_view"
+ require "active_storage"
+ require "action_cable"
+ require "sprockets"
+
+ require "action_view/helpers"
+ require "action_dispatch/routing/route_set"
end unless defined?(RAILS_ISOLATED_ENGINE)
diff --git a/railties/test/path_generation_test.rb b/railties/test/path_generation_test.rb
index d4dfa8e4a6..849b183b37 100644
--- a/railties/test/path_generation_test.rb
+++ b/railties/test/path_generation_test.rb
@@ -58,12 +58,14 @@ class PathGenerationTest < ActiveSupport::TestCase
Rails.logger = Logger.new nil
app = Class.new(Rails::Application) {
+ def self.name; "ScriptNameTestApp"; end
+
attr_accessor :controller
+
def initialize
super
app = self
@routes = TestSet.new ->(c) { app.controller = c }
- secrets.secret_key_base = "foo"
secrets.secret_token = "foo"
end
def app; routes; end
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index 227a739b71..43b60b9144 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -2,20 +2,7 @@
require "abstract_unit"
-unless defined?(Rails) && defined?(Rails::Info)
- module Rails
- class Info; end
- end
-end
-
-require "active_support/core_ext/kernel/reporting"
-
class InfoTest < ActiveSupport::TestCase
- def setup
- Rails.send :remove_const, :Info
- silence_warnings { load "rails/info.rb" }
- end
-
def test_property_with_block_swallows_exceptions_and_ignores_property
assert_nothing_raised do
Rails::Info.module_eval do
diff --git a/railties/test/secrets_test.rb b/railties/test/secrets_test.rb
index ff6f955a78..888fee173a 100644
--- a/railties/test/secrets_test.rb
+++ b/railties/test/secrets_test.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-require "abstract_unit"
require "isolation/abstract_unit"
require "rails/generators"
require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
@@ -20,12 +19,13 @@ class Rails::SecretsTest < ActiveSupport::TestCase
test "setting read to false skips parsing" do
run_secrets_generator do
Rails::Secrets.write(<<-end_of_secrets)
- test:
+ production:
yeah_yeah: lets-walk-in-the-cool-evening-light
end_of_secrets
- Rails.application.config.read_encrypted_secrets = false
- Rails.application.instance_variable_set(:@secrets, nil) # Dance around caching 💃🕺
+ add_to_env_config("production", "config.read_encrypted_secrets = false")
+ app("production")
+
assert_not Rails.application.secrets.yeah_yeah
end
end
@@ -82,17 +82,18 @@ class Rails::SecretsTest < ActiveSupport::TestCase
test "merging secrets with encrypted precedence" do
run_secrets_generator do
File.write("config/secrets.yml", <<-end_of_secrets)
- test:
+ production:
yeah_yeah: lets-go-walking-down-this-empty-street
end_of_secrets
Rails::Secrets.write(<<-end_of_secrets)
- test:
+ production:
yeah_yeah: lets-walk-in-the-cool-evening-light
end_of_secrets
- Rails.application.config.read_encrypted_secrets = true
- Rails.application.instance_variable_set(:@secrets, nil) # Dance around caching 💃🕺
+ add_to_env_config("production", "config.read_encrypted_secrets = true")
+ app("production")
+
assert_equal "lets-walk-in-the-cool-evening-light", Rails.application.secrets.yeah_yeah
end
end
@@ -108,7 +109,9 @@ class Rails::SecretsTest < ActiveSupport::TestCase
config.dereferenced_secret = Rails.application.secrets.some_secret
end_of_config
- assert_equal "yeah yeah\n", `bin/rails runner -e production "puts Rails.application.config.dereferenced_secret"`
+ app("production")
+
+ assert_equal "yeah yeah", Rails.application.config.dereferenced_secret
end
end
@@ -141,7 +144,9 @@ class Rails::SecretsTest < ActiveSupport::TestCase
assert_match(/production:\n\s*api_key: 00112233445566778899aabbccddeeff…\n/, File.read(tmp_path))
end
- assert_equal "00112233445566778899aabbccddeeff…\n", `bin/rails runner -e production "puts Rails.application.secrets.api_key"`
+ app("production")
+
+ assert_equal "00112233445566778899aabbccddeeff…", Rails.application.secrets.api_key
end
end
@@ -158,7 +163,9 @@ class Rails::SecretsTest < ActiveSupport::TestCase
assert_equal(secrets.dup.force_encoding(Encoding::ASCII_8BIT), IO.binread(tmp_path))
end
- assert_equal "00112233445566778899aabbccddeeff…\n", `bin/rails runner -e production "puts Rails.application.secrets.api_key"`
+ app("production")
+
+ assert_equal "00112233445566778899aabbccddeeff…", Rails.application.secrets.api_key
end
end
@@ -169,8 +176,9 @@ class Rails::SecretsTest < ActiveSupport::TestCase
Rails::Generators::EncryptedSecretsGenerator.start
end
- # Make config.paths["config/secrets"] to be relative to app_path
- Rails.application.config.root = app_path
+ add_to_config <<-RUBY
+ config.read_encrypted_secrets = true
+ RUBY
yield
end
diff --git a/tasks/release.rb b/tasks/release.rb
index aa8ba44c1a..6ff06f3c4a 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -6,7 +6,6 @@ FRAMEWORK_NAMES = Hash.new { |h, k| k.split(/(?<=active|action)/).map(&:capitali
root = File.expand_path("..", __dir__)
version = File.read("#{root}/RAILS_VERSION").strip
tag = "v#{version}"
-gem_version = Gem::Version.new(version)
directory "pkg"