aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.codeclimate.yml22
-rw-r--r--.rubocop.yml13
-rw-r--r--.travis.yml43
-rw-r--r--Gemfile22
-rw-r--r--Gemfile.lock261
-rw-r--r--MIT-LICENSE2
-rw-r--r--RAILS_VERSION2
-rw-r--r--actioncable/CHANGELOG.md26
-rw-r--r--actioncable/MIT-LICENSE2
-rw-r--r--actioncable/README.md2
-rw-r--r--actioncable/actioncable.gemspec2
-rw-r--r--actioncable/lib/action_cable.rb2
-rw-r--r--actioncable/lib/action_cable/connection/client_socket.rb2
-rw-r--r--actioncable/lib/action_cable/gem_version.rb4
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/postgresql.rb2
-rw-r--r--actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb.tt (renamed from actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb)0
-rw-r--r--actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb.tt (renamed from actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb)0
-rw-r--r--actioncable/lib/rails/generators/channel/templates/assets/cable.js.tt (renamed from actioncable/lib/rails/generators/channel/templates/assets/cable.js)0
-rw-r--r--actioncable/lib/rails/generators/channel/templates/assets/channel.coffee.tt (renamed from actioncable/lib/rails/generators/channel/templates/assets/channel.coffee)0
-rw-r--r--actioncable/lib/rails/generators/channel/templates/assets/channel.js.tt (renamed from actioncable/lib/rails/generators/channel/templates/assets/channel.js)0
-rw-r--r--actioncable/lib/rails/generators/channel/templates/channel.rb.tt (renamed from actioncable/lib/rails/generators/channel/templates/channel.rb)0
-rw-r--r--actioncable/package.json2
-rw-r--r--actioncable/test/channel/base_test.rb6
-rw-r--r--actioncable/test/channel/stream_test.rb6
-rw-r--r--actioncable/test/client_test.rb2
-rw-r--r--actioncable/test/connection/base_test.rb6
-rw-r--r--actioncable/test/connection/client_socket_test.rb12
-rw-r--r--actioncable/test/connection/subscriptions_test.rb6
-rw-r--r--actioncable/test/subscription_adapter/common.rb2
-rw-r--r--actioncable/test/subscription_adapter/redis_test.rb23
-rw-r--r--actionmailer/CHANGELOG.md19
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/README.rdoc4
-rw-r--r--actionmailer/lib/action_mailer.rb2
-rw-r--r--actionmailer/lib/action_mailer/base.rb12
-rw-r--r--actionmailer/lib/action_mailer/gem_version.rb4
-rw-r--r--actionmailer/lib/action_mailer/inline_preview_interceptor.rb2
-rw-r--r--actionmailer/lib/action_mailer/message_delivery.rb24
-rw-r--r--actionmailer/lib/action_mailer/preview.rb2
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb.tt (renamed from actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb)0
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/mailer.rb.tt (renamed from actionmailer/lib/rails/generators/mailer/templates/mailer.rb)0
-rw-r--r--actionmailer/test/base_test.rb13
-rw-r--r--actionmailer/test/caching_test.rb4
-rw-r--r--actionmailer/test/mailers/proc_mailer.rb9
-rw-r--r--actionmailer/test/message_delivery_test.rb2
-rw-r--r--actionmailer/test/url_test.rb2
-rw-r--r--actionpack/CHANGELOG.md140
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/README.rdoc6
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/metal.rb18
-rw-r--r--actionpack/lib/action_controller/metal/content_security_policy.rb26
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb4
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb11
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb6
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb21
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb2
-rw-r--r--actionpack/lib/action_controller/test_case.rb2
-rw-r--r--actionpack/lib/action_dispatch.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/content_security_policy.rb231
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb13
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/nfa/dot.rb20
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb5
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb13
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb5
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb1
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb18
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb21
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb2
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb12
-rw-r--r--actionpack/lib/action_dispatch/system_testing/browser.rb49
-rw-r--r--actionpack/lib/action_dispatch/system_testing/driver.rb18
-rw-r--r--actionpack/lib/action_dispatch/system_testing/server.rb2
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb16
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_pack/gem_version.rb4
-rw-r--r--actionpack/test/abstract_unit.rb8
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb30
-rw-r--r--actionpack/test/controller/api/conditional_get_test.rb2
-rw-r--r--actionpack/test/controller/base_test.rb4
-rw-r--r--actionpack/test/controller/caching_test.rb4
-rw-r--r--actionpack/test/controller/filters_test.rb2
-rw-r--r--actionpack/test/controller/flash_hash_test.rb6
-rw-r--r--actionpack/test/controller/integration_test.rb10
-rw-r--r--actionpack/test/controller/live_stream_test.rb2
-rw-r--r--actionpack/test/controller/metal_test.rb8
-rw-r--r--actionpack/test/controller/output_escaping_test.rb2
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb84
-rw-r--r--actionpack/test/controller/parameters/always_permitted_parameters_test.rb2
-rw-r--r--actionpack/test/controller/parameters/dup_test.rb6
-rw-r--r--actionpack/test/controller/parameters/multi_parameter_attributes_test.rb2
-rw-r--r--actionpack/test/controller/parameters/mutators_test.rb36
-rw-r--r--actionpack/test/controller/parameters/nested_parameters_permit_test.rb2
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb36
-rw-r--r--actionpack/test/controller/parameters/serialization_test.rb6
-rw-r--r--actionpack/test/controller/render_test.rb32
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb15
-rw-r--r--actionpack/test/controller/resources_test.rb2
-rw-r--r--actionpack/test/controller/routing_test.rb6
-rw-r--r--actionpack/test/controller/runner_test.rb4
-rw-r--r--actionpack/test/controller/send_file_test.rb2
-rw-r--r--actionpack/test/controller/test_case_test.rb4
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb1
-rw-r--r--actionpack/test/controller/url_for_test.rb2
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb2
-rw-r--r--actionpack/test/dispatch/content_security_policy_test.rb368
-rw-r--r--actionpack/test/dispatch/cookies_test.rb10
-rw-r--r--actionpack/test/dispatch/live_response_test.rb2
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb12
-rw-r--r--actionpack/test/dispatch/request/session_test.rb1
-rw-r--r--actionpack/test/dispatch/request_id_test.rb5
-rw-r--r--actionpack/test/dispatch/request_test.rb240
-rw-r--r--actionpack/test/dispatch/response_test.rb28
-rw-r--r--actionpack/test/dispatch/routing_assertions_test.rb6
-rw-r--r--actionpack/test/dispatch/routing_test.rb43
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb123
-rw-r--r--actionpack/test/dispatch/system_testing/driver_test.rb13
-rw-r--r--actionpack/test/dispatch/system_testing/screenshot_helper_test.rb7
-rw-r--r--actionpack/test/dispatch/system_testing/server_test.rb19
-rw-r--r--actionpack/test/dispatch/system_testing/system_test_case_test.rb6
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb8
-rw-r--r--actionpack/test/journey/nodes/symbol_test.rb4
-rw-r--r--actionpack/test/journey/router_test.rb2
-rw-r--r--actionpack/test/journey/routes_test.rb6
-rw-r--r--actionview/CHANGELOG.md44
-rw-r--r--actionview/MIT-LICENSE2
-rw-r--r--actionview/README.rdoc4
-rw-r--r--actionview/RUNNING_UJS_TESTS.rdoc9
-rw-r--r--actionview/RUNNING_UNIT_TESTS.rdoc13
-rw-r--r--actionview/app/assets/javascripts/MIT-LICENSE2
-rw-r--r--actionview/app/assets/javascripts/README.md2
-rw-r--r--actionview/app/assets/javascripts/rails-ujs/utils/event.coffee10
-rw-r--r--actionview/lib/action_view.rb2
-rw-r--r--actionview/lib/action_view/digestor.rb2
-rw-r--r--actionview/lib/action_view/gem_version.rb4
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb113
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb24
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb41
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb6
-rw-r--r--actionview/lib/action_view/helpers/tags/label.rb4
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb8
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb20
-rw-r--r--actionview/lib/action_view/railtie.rb11
-rw-r--r--actionview/lib/action_view/renderer/streaming_template_renderer.rb2
-rw-r--r--actionview/package.json2
-rw-r--r--actionview/test/actionpack/controller/render_test.rb2
-rw-r--r--actionview/test/active_record_unit.rb2
-rw-r--r--actionview/test/fixtures/layouts/streaming_with_locale.erb2
-rw-r--r--actionview/test/fixtures/test/streaming_with_locale.erb1
-rw-r--r--actionview/test/template/asset_tag_helper_test.rb51
-rw-r--r--actionview/test/template/atom_feed_helper_test.rb2
-rw-r--r--actionview/test/template/capture_helper_test.rb4
-rw-r--r--actionview/test/template/date_helper_test.rb26
-rw-r--r--actionview/test/template/erb_util_test.rb12
-rw-r--r--actionview/test/template/form_helper/form_with_test.rb421
-rw-r--r--actionview/test/template/form_helper_test.rb44
-rw-r--r--actionview/test/template/form_options_helper_test.rb24
-rw-r--r--actionview/test/template/form_tag_helper_test.rb4
-rw-r--r--actionview/test/template/lookup_context_test.rb2
-rw-r--r--actionview/test/template/number_helper_test.rb74
-rw-r--r--actionview/test/template/output_safety_helper_test.rb12
-rw-r--r--actionview/test/template/sanitize_helper_test.rb2
-rw-r--r--actionview/test/template/streaming_render_test.rb21
-rw-r--r--actionview/test/template/tag_helper_test.rb16
-rw-r--r--actionview/test/template/text_helper_test.rb16
-rw-r--r--actionview/test/template/translation_helper_test.rb6
-rw-r--r--actionview/test/template/url_helper_test.rb2
-rw-r--r--activejob/CHANGELOG.md4
-rw-r--r--activejob/MIT-LICENSE2
-rw-r--r--activejob/README.md4
-rw-r--r--activejob/Rakefile2
-rw-r--r--activejob/lib/active_job.rb2
-rw-r--r--activejob/lib/active_job/core.rb14
-rw-r--r--activejob/lib/active_job/exceptions.rb12
-rw-r--r--activejob/lib/active_job/gem_version.rb4
-rw-r--r--activejob/lib/active_job/queue_adapter.rb30
-rw-r--r--activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb4
-rw-r--r--activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb2
-rw-r--r--activejob/lib/rails/generators/job/templates/application_job.rb.tt (renamed from activejob/lib/rails/generators/job/templates/application_job.rb)0
-rw-r--r--activejob/lib/rails/generators/job/templates/job.rb.tt (renamed from activejob/lib/rails/generators/job/templates/job.rb)0
-rw-r--r--activejob/test/cases/argument_serialization_test.rb2
-rw-r--r--activejob/test/cases/exceptions_test.rb7
-rw-r--r--activejob/test/integration/queuing_test.rb7
-rw-r--r--activejob/test/jobs/retry_job.rb2
-rw-r--r--activejob/test/support/delayed_job/delayed/backend/test.rb2
-rw-r--r--activejob/test/support/integration/adapters/resque.rb5
-rw-r--r--activejob/test/support/integration/adapters/sidekiq.rb12
-rw-r--r--activemodel/CHANGELOG.md48
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/README.rdoc4
-rw-r--r--activemodel/lib/active_model.rb2
-rw-r--r--activemodel/lib/active_model/attribute.rb4
-rw-r--r--activemodel/lib/active_model/attribute/user_provided_default.rb22
-rw-r--r--activemodel/lib/active_model/attribute_mutation_tracker.rb7
-rw-r--r--activemodel/lib/active_model/attribute_set.rb3
-rw-r--r--activemodel/lib/active_model/attribute_set/builder.rb46
-rw-r--r--activemodel/lib/active_model/attributes.rb3
-rw-r--r--activemodel/lib/active_model/dirty.rb106
-rw-r--r--activemodel/lib/active_model/errors.rb10
-rw-r--r--activemodel/lib/active_model/gem_version.rb4
-rw-r--r--activemodel/lib/active_model/lint.rb24
-rw-r--r--activemodel/lib/active_model/type.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb6
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb24
-rw-r--r--activemodel/lib/active_model/validations/validates.rb2
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb4
-rw-r--r--activemodel/test/cases/attribute_set_test.rb40
-rw-r--r--activemodel/test/cases/attribute_test.rb18
-rw-r--r--activemodel/test/cases/attributes_dirty_test.rb38
-rw-r--r--activemodel/test/cases/attributes_test.rb11
-rw-r--r--activemodel/test/cases/callbacks_test.rb12
-rw-r--r--activemodel/test/cases/dirty_test.rb67
-rw-r--r--activemodel/test/cases/errors_test.rb17
-rw-r--r--activemodel/test/cases/type/boolean_test.rb4
-rw-r--r--activemodel/test/cases/type/decimal_test.rb16
-rw-r--r--activemodel/test/cases/validations/absence_validation_test.rb18
-rw-r--r--activemodel/test/cases/validations/acceptance_validation_test.rb26
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb43
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb42
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb28
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb36
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb36
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb88
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb196
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb38
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb20
-rw-r--r--activemodel/test/cases/validations/validates_test.rb22
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb28
-rw-r--r--activemodel/test/cases/validations_test.rb56
-rw-r--r--activerecord/CHANGELOG.md383
-rw-r--r--activerecord/MIT-LICENSE2
-rw-r--r--activerecord/README.rdoc2
-rw-r--r--activerecord/Rakefile2
-rw-r--r--activerecord/activerecord.gemspec2
-rwxr-xr-xactiverecord/bin/test7
-rw-r--r--activerecord/lib/active_record.rb2
-rw-r--r--activerecord/lib/active_record/aggregations.rb9
-rw-r--r--activerecord/lib/active_record/association_relation.rb4
-rw-r--r--activerecord/lib/active_record/associations.rb30
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb12
-rw-r--r--activerecord/lib/active_record/associations/association.rb6
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb18
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb4
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb10
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb21
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb11
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb4
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb31
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb46
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb6
-rw-r--r--activerecord/lib/active_record/attributes.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb3
-rw-r--r--activerecord/lib/active_record/callbacks.rb4
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb122
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb78
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb23
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb70
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb49
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb129
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb2
-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/schema_creation.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb218
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb28
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb81
-rw-r--r--activerecord/lib/active_record/connection_handling.rb2
-rw-r--r--activerecord/lib/active_record/core.rb26
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb38
-rw-r--r--activerecord/lib/active_record/fixtures.rb65
-rw-r--r--activerecord/lib/active_record/gem_version.rb4
-rw-r--r--activerecord/lib/active_record/inheritance.rb7
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb41
-rw-r--r--activerecord/lib/active_record/migration.rb279
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb2
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb25
-rw-r--r--activerecord/lib/active_record/model_schema.rb44
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb12
-rw-r--r--activerecord/lib/active_record/persistence.rb62
-rw-r--r--activerecord/lib/active_record/query_cache.rb15
-rw-r--r--activerecord/lib/active_record/railtie.rb18
-rw-r--r--activerecord/lib/active_record/railties/databases.rake67
-rw-r--r--activerecord/lib/active_record/reflection.rb23
-rw-r--r--activerecord/lib/active_record/relation.rb105
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb48
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb84
-rw-r--r--activerecord/lib/active_record/relation/merger.rb32
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb12
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb50
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb18
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb3
-rw-r--r--activerecord/lib/active_record/sanitization.rb241
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb15
-rw-r--r--activerecord/lib/active_record/scoping.rb10
-rw-r--r--activerecord/lib/active_record/scoping/default.rb4
-rw-r--r--activerecord/lib/active_record/scoping/named.rb6
-rw-r--r--activerecord/lib/active_record/store.rb2
-rw-r--r--activerecord/lib/active_record/table_metadata.rb13
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb20
-rw-r--r--activerecord/lib/active_record/type_caster/map.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt (renamed from activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb)0
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt (renamed from activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb)0
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt (renamed from activerecord/lib/rails/generators/active_record/migration/templates/migration.rb)0
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt (renamed from activerecord/lib/rails/generators/active_record/model/templates/model.rb)0
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/module.rb.tt (renamed from activerecord/lib/rails/generators/active_record/model/templates/module.rb)0
-rw-r--r--activerecord/test/cases/adapter_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb16
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb22
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/table_options_test.rb75
-rw-r--r--activerecord/test/cases/adapters/mysql2/transaction_test.rb62
-rw-r--r--activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/array_test.rb18
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/change_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/citext_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/composite_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/domain_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/enum_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/foreign_table_test.rb109
-rw-r--r--activerecord/test/cases/adapters/postgresql/full_text_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/geometric_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/ltree_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/money_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/network_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/range_test.rb26
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb64
-rw-r--r--activerecord/test/cases/adapters/postgresql/serial_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/transaction_test.rb61
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb8
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb22
-rw-r--r--activerecord/test/cases/adapters/sqlite3/json_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb118
-rw-r--r--activerecord/test/cases/aggregations_test.rb2
-rw-r--r--activerecord/test/cases/ar_schema_test.rb4
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb48
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb16
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb2
-rw-r--r--activerecord/test/cases/associations/eager_test.rb38
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb88
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb157
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb119
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb30
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb30
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb12
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb45
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb36
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb10
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb26
-rw-r--r--activerecord/test/cases/associations_test.rb32
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb59
-rw-r--r--activerecord/test/cases/attributes_test.rb8
-rw-r--r--activerecord/test/cases/autosave_association_test.rb288
-rw-r--r--activerecord/test/cases/base_test.rb155
-rw-r--r--activerecord/test/cases/batches_test.rb18
-rw-r--r--activerecord/test/cases/cache_key_test.rb4
-rw-r--r--activerecord/test/cases/calculations_test.rb39
-rw-r--r--activerecord/test/cases/callbacks_test.rb10
-rw-r--r--activerecord/test/cases/clone_test.rb2
-rw-r--r--activerecord/test/cases/collection_cache_key_test.rb26
-rw-r--r--activerecord/test/cases/connection_adapters/adapter_leasing_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb92
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb8
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb6
-rw-r--r--activerecord/test/cases/connection_management_test.rb14
-rw-r--r--activerecord/test/cases/connection_pool_test.rb86
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb173
-rw-r--r--activerecord/test/cases/dup_test.rb32
-rw-r--r--activerecord/test/cases/enum_test.rb158
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb10
-rw-r--r--activerecord/test/cases/finder_respond_to_test.rb12
-rw-r--r--activerecord/test/cases/finder_test.rb100
-rw-r--r--activerecord/test/cases/fixtures_test.rb157
-rw-r--r--activerecord/test/cases/habtm_destroy_order_test.rb6
-rw-r--r--activerecord/test/cases/inheritance_test.rb67
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb27
-rw-r--r--activerecord/test/cases/json_attribute_test.rb6
-rw-r--r--activerecord/test/cases/json_serialization_test.rb2
-rw-r--r--activerecord/test/cases/json_shared_test_cases.rb55
-rw-r--r--activerecord/test/cases/locking_test.rb58
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb21
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb17
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb6
-rw-r--r--activerecord/test/cases/migration/columns_test.rb14
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb4
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb118
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb2
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb68
-rw-r--r--activerecord/test/cases/migration/pending_migrations_test.rb50
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb124
-rw-r--r--activerecord/test/cases/migrator_test.rb132
-rw-r--r--activerecord/test/cases/multiparameter_attributes_test.rb6
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb36
-rw-r--r--activerecord/test/cases/nested_attributes_with_callbacks_test.rb10
-rw-r--r--activerecord/test/cases/persistence_test.rb224
-rw-r--r--activerecord/test/cases/primary_keys_test.rb20
-rw-r--r--activerecord/test/cases/query_cache_test.rb29
-rw-r--r--activerecord/test/cases/quoting_test.rb2
-rw-r--r--activerecord/test/cases/readonly_test.rb44
-rw-r--r--activerecord/test/cases/reaper_test.rb10
-rw-r--r--activerecord/test/cases/reflection_test.rb98
-rw-r--r--activerecord/test/cases/relation/merging_test.rb12
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb6
-rw-r--r--activerecord/test/cases/relation/where_clause_test.rb4
-rw-r--r--activerecord/test/cases/relation/where_test.rb2
-rw-r--r--activerecord/test/cases/relation_test.rb74
-rw-r--r--activerecord/test/cases/relations_test.rb205
-rw-r--r--activerecord/test/cases/reserved_word_test.rb11
-rw-r--r--activerecord/test/cases/sanitize_test.rb67
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb24
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb46
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb78
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb20
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb4
-rw-r--r--activerecord/test/cases/statement_cache_test.rb3
-rw-r--r--activerecord/test/cases/store_test.rb6
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb142
-rw-r--r--activerecord/test/cases/test_case.rb6
-rw-r--r--activerecord/test/cases/timestamp_test.rb24
-rw-r--r--activerecord/test/cases/touch_later_test.rb4
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb8
-rw-r--r--activerecord/test/cases/transactions_test.rb58
-rw-r--r--activerecord/test/cases/type/string_test.rb6
-rw-r--r--activerecord/test/cases/unsafe_raw_sql_test.rb299
-rw-r--r--activerecord/test/cases/validations/absence_validation_test.rb10
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb24
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb26
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb18
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb40
-rw-r--r--activerecord/test/cases/validations_test.rb14
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb4
-rw-r--r--activerecord/test/fixtures/customers.yml11
-rw-r--r--activerecord/test/fixtures/minimalistics.yml3
-rw-r--r--activerecord/test/fixtures/reserved_words/values.yml4
-rw-r--r--activerecord/test/fixtures/teapots.yml3
-rw-r--r--activerecord/test/models/author.rb3
-rw-r--r--activerecord/test/models/company.rb2
-rw-r--r--activerecord/test/models/contract.rb2
-rw-r--r--activerecord/test/models/customer.rb2
-rw-r--r--activerecord/test/models/face.rb4
-rw-r--r--activerecord/test/models/post.rb15
-rw-r--r--activerecord/test/models/tag.rb4
-rw-r--r--activerecord/test/models/tagging.rb4
-rw-r--r--activerecord/test/models/topic.rb7
-rw-r--r--activerecord/test/models/wheel.rb2
-rw-r--r--activerecord/test/schema/schema.rb7
-rw-r--r--activestorage/.gitignore1
-rw-r--r--activestorage/CHANGELOG.md9
-rw-r--r--activestorage/MIT-LICENSE2
-rw-r--r--activestorage/README.md16
-rw-r--r--activestorage/activestorage.gemspec6
-rw-r--r--activestorage/app/assets/javascripts/activestorage.js2
-rw-r--r--activestorage/app/controllers/active_storage/blobs_controller.rb10
-rw-r--r--activestorage/app/controllers/active_storage/disk_controller.rb2
-rw-r--r--activestorage/app/controllers/active_storage/previews_controller.rb10
-rw-r--r--activestorage/app/controllers/active_storage/variants_controller.rb10
-rw-r--r--activestorage/app/controllers/concerns/active_storage/set_blob.rb16
-rw-r--r--activestorage/app/javascript/activestorage/blob_record.js20
-rw-r--r--activestorage/app/javascript/activestorage/helpers.js11
-rw-r--r--activestorage/app/models/active_storage/attachment.rb6
-rw-r--r--activestorage/app/models/active_storage/blob.rb152
-rw-r--r--activestorage/app/models/active_storage/blob/analyzable.rb57
-rw-r--r--activestorage/app/models/active_storage/blob/identifiable.rb11
-rw-r--r--activestorage/app/models/active_storage/blob/representable.rb93
-rw-r--r--activestorage/app/models/active_storage/filename.rb10
-rw-r--r--activestorage/app/models/active_storage/filename/parameters.rb2
-rw-r--r--activestorage/app/models/active_storage/identification.rb38
-rw-r--r--activestorage/app/models/active_storage/variant.rb56
-rw-r--r--activestorage/app/models/active_storage/variation.rb32
-rw-r--r--activestorage/config/routes.rb32
-rw-r--r--activestorage/lib/active_storage.rb9
-rw-r--r--activestorage/lib/active_storage/analyzer.rb2
-rw-r--r--activestorage/lib/active_storage/analyzer/image_analyzer.rb15
-rw-r--r--activestorage/lib/active_storage/analyzer/video_analyzer.rb61
-rw-r--r--activestorage/lib/active_storage/attached/macros.rb16
-rw-r--r--activestorage/lib/active_storage/attached/many.rb12
-rw-r--r--activestorage/lib/active_storage/attached/one.rb23
-rw-r--r--activestorage/lib/active_storage/downloading.rb20
-rw-r--r--activestorage/lib/active_storage/engine.rb19
-rw-r--r--activestorage/lib/active_storage/errors.rb7
-rw-r--r--activestorage/lib/active_storage/gem_version.rb4
-rw-r--r--activestorage/lib/active_storage/log_subscriber.rb4
-rw-r--r--activestorage/lib/active_storage/previewer.rb26
-rw-r--r--activestorage/lib/active_storage/previewer/pdf_previewer.rb11
-rw-r--r--activestorage/lib/active_storage/previewer/video_previewer.rb6
-rw-r--r--activestorage/lib/active_storage/service.rb11
-rw-r--r--activestorage/lib/active_storage/service/azure_storage_service.rb32
-rw-r--r--activestorage/lib/active_storage/service/disk_service.rb50
-rw-r--r--activestorage/lib/active_storage/service/gcs_service.rb45
-rw-r--r--activestorage/lib/active_storage/service/mirror_service.rb5
-rw-r--r--activestorage/lib/active_storage/service/s3_service.rb20
-rw-r--r--activestorage/lib/tasks/activestorage.rake6
-rw-r--r--activestorage/package.json12
-rw-r--r--activestorage/test/analyzer/image_analyzer_test.rb25
-rw-r--r--activestorage/test/analyzer/video_analyzer_test.rb38
-rw-r--r--activestorage/test/controllers/blobs_controller_test.rb5
-rw-r--r--activestorage/test/controllers/previews_controller_test.rb11
-rw-r--r--activestorage/test/controllers/variants_controller_test.rb9
-rw-r--r--activestorage/test/database/setup.rb2
-rw-r--r--activestorage/test/dummy/config/application.rb6
-rw-r--r--activestorage/test/dummy/config/environments/test.rb3
-rw-r--r--activestorage/test/fixtures/files/icon.psdbin0 -> 37441 bytes
-rw-r--r--activestorage/test/fixtures/files/icon.svg13
-rw-r--r--activestorage/test/fixtures/files/image.gifbin0 -> 2032 bytes
-rw-r--r--activestorage/test/fixtures/files/racecar_rotated.jpgbin0 -> 1124060 bytes
-rw-r--r--activestorage/test/fixtures/files/video_with_rectangular_samples.mp4bin0 -> 361535 bytes
-rw-r--r--activestorage/test/fixtures/files/video_with_undefined_display_aspect_ratio.mp4bin0 -> 128737 bytes
-rw-r--r--activestorage/test/models/attachments_test.rb160
-rw-r--r--activestorage/test/models/blob_test.rb84
-rw-r--r--activestorage/test/models/preview_test.rb6
-rw-r--r--activestorage/test/models/representation_test.rb2
-rw-r--r--activestorage/test/models/variant_test.rb58
-rw-r--r--activestorage/test/service/configurations.example.yml2
-rw-r--r--activestorage/test/service/configurator_test.rb3
-rw-r--r--activestorage/test/service/gcs_service_test.rb19
-rw-r--r--activestorage/test/service/mirror_service_test.rb36
-rw-r--r--activestorage/test/service/shared_service_tests.rb17
-rw-r--r--activestorage/test/template/image_tag_test.rb2
-rw-r--r--activestorage/test/test_helper.rb8
-rw-r--r--activestorage/webpack.config.js1
-rw-r--r--activestorage/yarn.lock10
-rw-r--r--activesupport/CHANGELOG.md364
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/README.rdoc2
-rw-r--r--activesupport/Rakefile20
-rw-r--r--activesupport/lib/active_support.rb3
-rw-r--r--activesupport/lib/active_support/cache.rb157
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb2
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb75
-rw-r--r--activesupport/lib/active_support/cache/redis_cache_store.rb436
-rw-r--r--activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/concerning.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/name_error.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.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.rb2
-rw-r--r--activesupport/lib/active_support/dependencies.rb3
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/constant_accessor.rb6
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb21
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb5
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb2
-rw-r--r--activesupport/lib/active_support/digest.rb20
-rw-r--r--activesupport/lib/active_support/duration.rb12
-rw-r--r--activesupport/lib/active_support/encrypted_configuration.rb13
-rw-r--r--activesupport/lib/active_support/encrypted_file.rb10
-rw-r--r--activesupport/lib/active_support/gem_version.rb4
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb22
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb6
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb4
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb8
-rw-r--r--activesupport/lib/active_support/message_verifier.rb6
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/rounding_helper.rb2
-rw-r--r--activesupport/lib/active_support/railtie.rb16
-rw-r--r--activesupport/lib/active_support/reloader.rb3
-rw-r--r--activesupport/lib/active_support/security_utils.rb22
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb39
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb6
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb3
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb18
-rw-r--r--activesupport/test/abstract_unit.rb4
-rw-r--r--activesupport/test/array_inquirer_test.rb6
-rw-r--r--activesupport/test/benchmarkable_test.rb2
-rw-r--r--activesupport/test/cache/behaviors.rb3
-rw-r--r--activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb8
-rw-r--r--activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb (renamed from activesupport/test/cache/cache_store_write_multi_test.rb)42
-rw-r--r--activesupport/test/cache/behaviors/cache_store_behavior.rb35
-rw-r--r--activesupport/test/cache/behaviors/cache_store_version_behavior.rb2
-rw-r--r--activesupport/test/cache/behaviors/connection_pool_behavior.rb57
-rw-r--r--activesupport/test/cache/behaviors/failure_safety_behavior.rb91
-rw-r--r--activesupport/test/cache/behaviors/local_cache_behavior.rb6
-rw-r--r--activesupport/test/cache/cache_entry_test.rb13
-rw-r--r--activesupport/test/cache/cache_store_logger_test.rb4
-rw-r--r--activesupport/test/cache/stores/file_store_test.rb5
-rw-r--r--activesupport/test/cache/stores/mem_cache_store_test.rb63
-rw-r--r--activesupport/test/cache/stores/memory_store_test.rb1
-rw-r--r--activesupport/test/cache/stores/redis_cache_store_test.rb173
-rw-r--r--activesupport/test/callback_inheritance_test.rb4
-rw-r--r--activesupport/test/callbacks_test.rb2
-rw-r--r--activesupport/test/class_cache_test.rb20
-rw-r--r--activesupport/test/concern_test.rb2
-rw-r--r--activesupport/test/concurrency/load_interlock_aware_monitor_test.rb55
-rw-r--r--activesupport/test/configurable_test.rb8
-rw-r--r--activesupport/test/core_ext/array/conversions_test.rb6
-rw-r--r--activesupport/test/core_ext/array/grouping_test.rb2
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb2
-rw-r--r--activesupport/test/core_ext/class_test.rb4
-rw-r--r--activesupport/test/core_ext/date_and_time_behavior.rb68
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb10
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb58
-rw-r--r--activesupport/test/core_ext/duration_test.rb11
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/module/anonymous_test.rb8
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb8
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb8
-rw-r--r--activesupport/test/core_ext/module/attribute_aliasing_test.rb14
-rw-r--r--activesupport/test/core_ext/module/concerning_test.rb8
-rw-r--r--activesupport/test/core_ext/module/reachable_test.rb20
-rw-r--r--activesupport/test/core_ext/module/remove_method_test.rb4
-rw-r--r--activesupport/test/core_ext/module_test.rb20
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb12
-rw-r--r--activesupport/test/core_ext/object/blank_test.rb4
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb8
-rw-r--r--activesupport/test/core_ext/object/instance_variables_test.rb4
-rw-r--r--activesupport/test/core_ext/object/try_test.rb8
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb62
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb30
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb86
-rw-r--r--activesupport/test/dependencies_test.rb57
-rw-r--r--activesupport/test/deprecation/method_wrappers_test.rb22
-rw-r--r--activesupport/test/deprecation_test.rb2
-rw-r--r--activesupport/test/descendants_tracker_test_cases.rb2
-rw-r--r--activesupport/test/descendants_tracker_with_autoloading_test.rb2
-rw-r--r--activesupport/test/digest_test.rb27
-rw-r--r--activesupport/test/encrypted_configuration_test.rb14
-rw-r--r--activesupport/test/encrypted_file_test.rb13
-rw-r--r--activesupport/test/evented_file_update_checker_test.rb10
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb18
-rw-r--r--activesupport/test/inflector_test.rb34
-rw-r--r--activesupport/test/json/encoding_test.rb8
-rw-r--r--activesupport/test/multibyte_chars_test.rb10
-rw-r--r--activesupport/test/notifications/instrumenter_test.rb4
-rw-r--r--activesupport/test/notifications_test.rb4
-rw-r--r--activesupport/test/number_helper_i18n_test.rb18
-rw-r--r--activesupport/test/number_helper_test.rb14
-rw-r--r--activesupport/test/ordered_options_test.rb6
-rw-r--r--activesupport/test/safe_buffer_test.rb14
-rw-r--r--activesupport/test/security_utils_test.rb12
-rw-r--r--activesupport/test/string_inquirer_test.rb4
-rw-r--r--activesupport/test/tagged_logging_test.rb5
-rw-r--r--activesupport/test/test_case_test.rb40
-rw-r--r--activesupport/test/time_travel_test.rb2
-rw-r--r--activesupport/test/time_zone_test.rb33
-rw-r--r--activesupport/test/xml_mini/libxml_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini_test.rb2
-rw-r--r--ci/qunit-selenium-runner.rb1
-rwxr-xr-xci/travis.rb4
-rw-r--r--guides/CHANGELOG.md4
-rw-r--r--guides/assets/images/belongs_to.pngbin22147 -> 35041 bytes
-rw-r--r--guides/assets/images/habtm.pngbin47284 -> 61435 bytes
-rw-r--r--guides/assets/images/has_many.pngbin24300 -> 36233 bytes
-rw-r--r--guides/assets/images/has_many_through.pngbin78099 -> 98834 bytes
-rw-r--r--guides/assets/images/has_one.pngbin27547 -> 38222 bytes
-rw-r--r--guides/assets/images/has_one_through.pngbin70130 -> 92535 bytes
-rw-r--r--guides/assets/images/polymorphic.pngbin65417 -> 84739 bytes
-rw-r--r--guides/bug_report_templates/action_controller_master.rb3
-rw-r--r--guides/bug_report_templates/active_job_master.rb3
-rw-r--r--guides/bug_report_templates/active_record_master.rb3
-rw-r--r--guides/bug_report_templates/active_record_migrations_master.rb3
-rw-r--r--guides/bug_report_templates/benchmark.rb3
-rw-r--r--guides/bug_report_templates/generic_master.rb3
-rw-r--r--guides/rails_guides/markdown/renderer.rb2
-rw-r--r--guides/source/2_2_release_notes.md4
-rw-r--r--guides/source/2_3_release_notes.md10
-rw-r--r--guides/source/3_0_release_notes.md2
-rw-r--r--guides/source/3_2_release_notes.md4
-rw-r--r--guides/source/4_1_release_notes.md2
-rw-r--r--guides/source/4_2_release_notes.md2
-rw-r--r--guides/source/5_1_release_notes.md3
-rw-r--r--guides/source/5_2_release_notes.md226
-rw-r--r--guides/source/action_cable_overview.md20
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/action_mailer_basics.md2
-rw-r--r--guides/source/action_view_overview.md18
-rw-r--r--guides/source/active_record_basics.md11
-rw-r--r--guides/source/active_record_callbacks.md2
-rw-r--r--guides/source/active_record_migrations.md3
-rw-r--r--guides/source/active_record_querying.md18
-rw-r--r--guides/source/active_record_validations.md2
-rw-r--r--guides/source/active_storage_overview.md565
-rw-r--r--guides/source/active_support_core_extensions.md6
-rw-r--r--guides/source/active_support_instrumentation.md7
-rw-r--r--guides/source/api_app.md7
-rw-r--r--guides/source/asset_pipeline.md25
-rw-r--r--guides/source/association_basics.md61
-rw-r--r--guides/source/autoloading_and_reloading_constants.md41
-rw-r--r--guides/source/caching_with_rails.md59
-rw-r--r--guides/source/configuring.md65
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md7
-rw-r--r--guides/source/documents.yaml21
-rw-r--r--guides/source/engines.md18
-rw-r--r--guides/source/form_helpers.md6
-rw-r--r--guides/source/getting_started.md3
-rw-r--r--guides/source/i18n.md2
-rw-r--r--guides/source/initialization.md2
-rw-r--r--guides/source/layout.html.erb6
-rw-r--r--guides/source/layouts_and_rendering.md6
-rw-r--r--guides/source/rails_on_rack.md14
-rw-r--r--guides/source/routing.md43
-rw-r--r--guides/source/security.md31
-rw-r--r--guides/source/testing.md51
-rw-r--r--guides/source/threading_and_code_execution.md324
-rw-r--r--guides/source/upgrading_ruby_on_rails.md45
-rw-r--r--guides/source/working_with_javascript_in_rails.md64
-rw-r--r--railties/CHANGELOG.md152
-rw-r--r--railties/MIT-LICENSE2
-rw-r--r--railties/lib/rails/application.rb60
-rw-r--r--railties/lib/rails/application/configuration.rb83
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb5
-rw-r--r--railties/lib/rails/application/finisher.rb2
-rw-r--r--railties/lib/rails/command/helpers/editor.rb35
-rw-r--r--railties/lib/rails/commands/credentials/credentials_command.rb43
-rw-r--r--railties/lib/rails/commands/encrypted/encrypted_command.rb86
-rw-r--r--railties/lib/rails/commands/secrets/secrets_command.rb19
-rw-r--r--railties/lib/rails/commands/server/server_command.rb2
-rw-r--r--railties/lib/rails/gem_version.rb4
-rw-r--r--railties/lib/rails/generators.rb6
-rw-r--r--railties/lib/rails/generators/app_base.rb40
-rw-r--r--railties/lib/rails/generators/erb/controller/templates/view.html.erb.tt (renamed from railties/lib/rails/generators/erb/controller/templates/view.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/view.html.erb.tt (renamed from railties/lib/rails/generators/erb/mailer/templates/view.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/mailer/templates/view.text.erb.tt (renamed from railties/lib/rails/generators/erb/mailer/templates/view.text.erb)0
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt (renamed from railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb)6
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb.tt (renamed from railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt (renamed from railties/lib/rails/generators/erb/scaffold/templates/index.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/new.html.erb.tt (renamed from railties/lib/rails/generators/erb/scaffold/templates/new.html.erb)0
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt (renamed from railties/lib/rails/generators/erb/scaffold/templates/show.html.erb)0
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb4
-rw-r--r--railties/lib/rails/generators/named_base.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb39
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile.tt (renamed from railties/lib/rails/generators/rails/app/templates/Gemfile)19
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README.md.tt (renamed from railties/lib/rails/generators/rails/app/templates/README.md)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Rakefile.tt (renamed from railties/lib/rails/generators/rails/app/templates/Rakefile)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/bundle.tt (renamed from railties/lib/rails/generators/rails/app/templates/bin/bundle)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/rails.tt (renamed from railties/lib/rails/generators/rails/app/templates/bin/rails)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/rake.tt (renamed from railties/lib/rails/generators/rails/app/templates/bin/rake)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/yarn.tt (renamed from railties/lib/rails/generators/rails/app/templates/bin/yarn)2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config.ru.tt (renamed from railties/lib/rails/generators/rails/app/templates/config.ru)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/application.rb)5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/boot.rb)2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/cable.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/cable.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environment.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/environment.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt22
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/puma.rb)24
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/routes.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/spring.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/spring.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt (renamed from railties/lib/rails/generators/rails/app/templates/config/storage.yml)2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore.tt (renamed from railties/lib/rails/generators/rails/app/templates/gitignore)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/package.json.tt (renamed from railties/lib/rails/generators/rails/app/templates/package.json)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/ruby-version.tt (renamed from railties/lib/rails/generators/rails/app/templates/ruby-version)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb)0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt (renamed from railties/lib/rails/generators/rails/app/templates/test/test_helper.rb)1
-rw-r--r--railties/lib/rails/generators/rails/assets/templates/stylesheet.css2
-rw-r--r--railties/lib/rails/generators/rails/controller/templates/controller.rb.tt (renamed from railties/lib/rails/generators/rails/controller/templates/controller.rb)0
-rw-r--r--railties/lib/rails/generators/rails/credentials/credentials_generator.rb35
-rw-r--r--railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb28
-rw-r--r--railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb72
-rw-r--r--railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb57
-rw-r--r--railties/lib/rails/generators/rails/helper/templates/helper.rb.tt (renamed from railties/lib/rails/generators/rails/helper/templates/helper.rb)0
-rw-r--r--railties/lib/rails/generators/rails/master_key/master_key_generator.rb36
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb7
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Gemfile.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/Gemfile)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/README.md.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/README.md)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/Rakefile)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/config/routes.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/config/routes.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/gitignore.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/gitignore)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/application.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/application.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb)0
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt (renamed from railties/lib/rails/generators/rails/plugin/templates/test/test_helper.rb)3
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt (renamed from railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb)0
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt (renamed from railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb)0
-rw-r--r--railties/lib/rails/generators/rails/task/templates/task.rb.tt (renamed from railties/lib/rails/generators/rails/task/templates/task.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/generator/templates/generator_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/job/job_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb)0
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/mailer/templates/preview.rb.tt (renamed from railties/lib/rails/generators/test_unit/mailer/templates/preview.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt (renamed from railties/lib/rails/generators/test_unit/model/templates/fixtures.yml)0
-rw-r--r--railties/lib/rails/generators/test_unit/model/templates/unit_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/model/templates/unit_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/scaffold/templates/system_test.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb.tt (renamed from railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb)0
-rw-r--r--railties/lib/rails/generators/test_unit/system/templates/system_test.rb.tt (renamed from railties/lib/rails/generators/test_unit/system/templates/system_test.rb)0
-rw-r--r--railties/lib/rails/info.rb2
-rw-r--r--railties/lib/rails/mailers_controller.rb12
-rw-r--r--railties/lib/rails/railtie/configuration.rb2
-rw-r--r--railties/lib/rails/secrets.rb17
-rw-r--r--railties/lib/rails/tasks/engine.rake7
-rw-r--r--railties/lib/rails/templates/rails/mailers/email.html.erb46
-rw-r--r--railties/lib/rails/test_help.rb4
-rw-r--r--railties/lib/rails/test_unit/reporter.rb10
-rw-r--r--railties/lib/rails/test_unit/runner.rb2
-rw-r--r--railties/test/application/configuration/custom_test.rb2
-rw-r--r--railties/test/application/configuration_test.rb156
-rw-r--r--railties/test/application/console_test.rb6
-rw-r--r--railties/test/application/content_security_policy_test.rb197
-rw-r--r--railties/test/application/initializers/frameworks_test.rb2
-rw-r--r--railties/test/application/initializers/notifications_test.rb1
-rw-r--r--railties/test/application/loading_test.rb8
-rw-r--r--railties/test/application/mailer_previews_test.rb74
-rw-r--r--railties/test/application/middleware/cache_test.rb4
-rw-r--r--railties/test/application/middleware_test.rb9
-rw-r--r--railties/test/application/per_request_digest_cache_test.rb2
-rw-r--r--railties/test/application/rake/dbs_test.rb38
-rw-r--r--railties/test/application/rake/migrations_test.rb20
-rw-r--r--railties/test/application/rake_test.rb45
-rw-r--r--railties/test/application/runner_test.rb4
-rw-r--r--railties/test/application/server_test.rb7
-rw-r--r--railties/test/application/test_runner_test.rb73
-rw-r--r--railties/test/commands/console_test.rb10
-rw-r--r--railties/test/commands/credentials_test.rb51
-rw-r--r--railties/test/commands/encrypted_test.rb106
-rw-r--r--railties/test/commands/secrets_test.rb49
-rw-r--r--railties/test/engine/test_test.rb31
-rw-r--r--railties/test/engine_test.rb2
-rw-r--r--railties/test/generators/api_app_generator_test.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb113
-rw-r--r--railties/test/generators/create_migration_test.rb2
-rw-r--r--railties/test/generators/encrypted_secrets_generator_test.rb44
-rw-r--r--railties/test/generators/generated_attribute_test.rb12
-rw-r--r--railties/test/generators/generators_test_helper.rb4
-rw-r--r--railties/test/generators/plugin_generator_test.rb37
-rw-r--r--railties/test/generators/scaffold_generator_test.rb8
-rw-r--r--railties/test/generators/shared_generator_tests.rb15
-rw-r--r--railties/test/generators_test.rb15
-rw-r--r--railties/test/isolation/abstract_unit.rb41
-rw-r--r--railties/test/paths_test.rb28
-rw-r--r--railties/test/railties/engine_test.rb29
-rw-r--r--railties/test/secrets_test.rb16
-rw-r--r--railties/test/test_unit/reporter_test.rb8
-rw-r--r--version.rb4
917 files changed, 14734 insertions, 7229 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 1f0f067c5b..d59a0780d1 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,3 +1,25 @@
+checks:
+ argument-count:
+ enabled: false
+ complex-logic:
+ enabled: false
+ file-lines:
+ enabled: false
+ method-complexity:
+ enabled: false
+ method-count:
+ enabled: false
+ method-lines:
+ enabled: false
+ nested-control-flow:
+ enabled: false
+ return-statements:
+ enabled: false
+ similar-code:
+ enabled: false
+ identical-code:
+ enabled: false
+
engines:
rubocop:
enabled: true
diff --git a/.rubocop.yml b/.rubocop.yml
index 399fc66730..3b4dd79e81 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -26,6 +26,9 @@ Layout/CaseIndentation:
Layout/CommentIndentation:
Enabled: true
+Layout/ElseAlignment:
+ Enabled: true
+
Layout/EmptyLineAfterMagicComment:
Enabled: true
@@ -58,6 +61,9 @@ Layout/IndentationConsistency:
Layout/IndentationWidth:
Enabled: true
+Layout/LeadingCommentSpace:
+ Enabled: true
+
Layout/SpaceAfterColon:
Enabled: true
@@ -73,9 +79,15 @@ Layout/SpaceAroundKeyword:
Layout/SpaceAroundOperators:
Enabled: true
+Layout/SpaceBeforeComma:
+ Enabled: true
+
Layout/SpaceBeforeFirstArg:
Enabled: true
+Style/DefWithParentheses:
+ Enabled: true
+
# Defining a method with parameters needs parentheses.
Style/MethodDefParentheses:
Enabled: true
@@ -131,6 +143,7 @@ Style/UnneededPercentQ:
Lint/EndAlignment:
Enabled: true
EnforcedStyleAlignWith: variable
+ AutoCorrect: true
# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
Lint/RequireParentheses:
diff --git a/.travis.yml b/.travis.yml
index e357a0ca3e..3a7f28134b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,8 +2,8 @@ language: ruby
sudo: false
cache:
- bundler: true
directories:
+ - vendor/bundle
- /tmp/cache/unicode_conformance
- /tmp/beanstalkd-1.10
- node_modules
@@ -11,6 +11,7 @@ cache:
services:
- memcached
+ - redis-server
addons:
postgresql: "9.6"
@@ -28,7 +29,7 @@ bundler_args: --without test --jobs 3 --retry 3
before_install:
- "rm ${BUNDLE_GEMFILE}.lock"
- "travis_retry gem update --system"
- - "travis_retry gem install bundler -v 1.15.4"
+ - "travis_retry gem install 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"
@@ -41,7 +42,6 @@ before_script:
# Decodes to e.g. `export VARIABLE=VALUE`
- $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX0FDQ0VTU19LRVk9YTAzNTM0M2YtZTkyMi00MGIzLWFhM2MtMDZiM2VhNjM1YzQ4")
- $(base64 --decode <<< "ZXhwb3J0IFNBVUNFX1VTRVJOQU1FPXJ1YnlvbnJhaWxz")
- - redis-server --bind 127.0.0.1 --port 6379 --requirepass 'password' --daemonize yes
script: 'ci/travis.rb'
@@ -60,59 +60,70 @@ env:
- "GEM=ac:integration"
rvm:
- - 2.2.8
- - 2.3.5
- - 2.4.2
+ - 2.2.9
+ - 2.3.6
+ - 2.4.3
+ - 2.5.0
- ruby-head
matrix:
include:
- - rvm: 2.4.2
+ - rvm: 2.5.0
env: "GEM=av:ujs"
- - rvm: 2.2.8
+ - rvm: 2.2.9
env: "GEM=aj:integration"
services:
- memcached
+ - redis-server
- rabbitmq
- - rvm: 2.3.5
+ - rvm: 2.3.6
env: "GEM=aj:integration"
services:
- memcached
+ - redis-server
- rabbitmq
- - rvm: 2.4.2
+ - rvm: 2.4.3
env: "GEM=aj:integration"
services:
- memcached
+ - redis-server
+ - rabbitmq
+ - rvm: 2.5.0
+ env: "GEM=aj:integration"
+ services:
+ - memcached
+ - redis-server
- rabbitmq
- rvm: ruby-head
env: "GEM=aj:integration"
services:
- memcached
+ - redis-server
- rabbitmq
- - rvm: 2.3.5
+ - rvm: 2.3.6
env:
- "GEM=ar:mysql2 MYSQL=mariadb"
addons:
mariadb: 10.2
- - rvm: 2.3.5
+ - rvm: 2.3.6
env:
- "GEM=ar:sqlite3_mem"
- - rvm: 2.3.5
+ - rvm: 2.3.6
env:
- "GEM=ar:postgresql POSTGRES=9.2"
addons:
postgresql: "9.2"
- - rvm: jruby-9.1.13.0
+ - rvm: jruby-9.1.15.0
jdk: oraclejdk8
env:
- "GEM=ap"
- - rvm: jruby-9.1.13.0
+ - rvm: jruby-9.1.15.0
jdk: oraclejdk8
env:
- "GEM=am,amo,aj"
allow_failures:
- rvm: ruby-head
- - rvm: jruby-9.1.13.0
+ - rvm: jruby-9.1.15.0
- env: "GEM=ac:integration"
fast_finish: true
diff --git a/Gemfile b/Gemfile
index 3f8cd57853..8c5f31ef99 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,8 +6,6 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gemspec
-gem "arel", github: "rails/arel"
-
# We need a newish Rake since Active Job sets its test tasks' descriptions.
gem "rake", ">= 11.1"
@@ -19,8 +17,9 @@ gem "capybara", "~> 2.15"
gem "rack-cache", "~> 1.2"
gem "coffee-rails"
-gem "sass-rails", github: "rails/sass-rails", branch: "5-0-stable"
+gem "sass-rails"
gem "turbolinks", "~> 5"
+gem "webmock"
# require: false so bcrypt is loaded only when has_secure_password is used.
# This is to avoid Active Model (and by extension the entire framework)
@@ -39,8 +38,11 @@ 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 RUBY_VERSION == "2.2.8"
+
group :doc do
- gem "sdoc", github: "robin850/sdoc", branch: "upgrade"
+ gem "sdoc", "~> 1.0"
gem "redcarpet", "~> 3.2.3", platforms: :ruby
gem "w3c_validators"
gem "kindlerb", "~> 1.2.0"
@@ -50,6 +52,7 @@ end
gem "dalli", ">= 2.2.1"
gem "listen", ">= 3.0.5", "< 3.2", require: false
gem "libxml-ruby", platforms: :ruby
+gem "connection_pool"
# for railties app_generator_test
gem "bootsnap", ">= 1.1.0", require: false
@@ -57,15 +60,15 @@ gem "bootsnap", ">= 1.1.0", require: false
# Active Job.
group :job do
gem "resque", require: false
- gem "resque-scheduler", github: "jeremy/resque-scheduler", branch: "redis-rb-4.0", require: false
+ gem "resque-scheduler", require: false
gem "sidekiq", require: false
gem "sucker_punch", require: false
gem "delayed_job", require: false
- gem "queue_classic", github: "QueueClassic/queue_classic", branch: "master", require: false, platforms: :ruby
+ gem "queue_classic", github: "rafaelfranca/queue_classic", branch: "update-pg", require: false, platforms: :ruby
gem "sneakers", require: false
gem "que", require: false
gem "backburner", require: false
- #TODO: add qu after it support Rails 5.1
+ # 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 "delayed_job_active_record", require: false
@@ -76,7 +79,6 @@ end
group :cable do
gem "puma", require: false
- gem "em-hiredis", require: false
gem "hiredis", require: false
gem "redis", "~> 4.0", require: false
@@ -92,7 +94,7 @@ end
# Active Storage
group :storage do
gem "aws-sdk-s3", require: false
- gem "google-cloud-storage", "~> 1.3", require: false
+ gem "google-cloud-storage", "~> 1.8", require: false
gem "azure-storage", require: false
gem "mini_magick"
@@ -119,7 +121,7 @@ group :test do
end
platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do
- gem "nokogiri", ">= 1.6.8"
+ gem "nokogiri", ">= 1.8.1"
# Needed for compiling the ActionDispatch::Journey parser.
gem "racc", ">=1.4.6", require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 86f6ed3baf..0c9b58febc 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,23 +1,4 @@
GIT
- remote: https://github.com/QueueClassic/queue_classic.git
- revision: cde82d17ded2799ed726dd7b0df6ce1fd4c1b7da
- branch: master
- specs:
- queue_classic (3.2.0.RC1)
- 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
@@ -35,90 +16,73 @@ GIT
websocket
GIT
- remote: https://github.com/rails/arel.git
- revision: 42510bf71472e2e35d9becb546edd05562672344
- specs:
- arel (9.0.0.alpha)
-
-GIT
- remote: https://github.com/rails/sass-rails.git
- revision: bb5c1d34e8acad2e2960cc785184ffe17d7b3bca
- branch: 5-0-stable
- specs:
- sass-rails (5.0.6)
- railties (>= 4.0.0, < 6)
- sass (~> 3.1)
- sprockets (>= 2.8, < 4.0)
- sprockets-rails (>= 2.0, < 4.0)
- tilt (>= 1.1, < 3)
-
-GIT
- remote: https://github.com/robin850/sdoc.git
- revision: 0e340352f3ab2f196c8a8743f83c2ee286e4f71c
- branch: upgrade
+ remote: https://github.com/rafaelfranca/queue_classic.git
+ revision: dee64b361355d56700ad7aa3b151bf653a617526
+ branch: update-pg
specs:
- sdoc (1.0.0.rc2)
- rdoc (~> 5.0)
+ queue_classic (3.2.0.RC1)
+ pg (>= 0.17, < 2.0)
PATH
remote: .
specs:
- actioncable (5.2.0.alpha)
- actionpack (= 5.2.0.alpha)
+ actioncable (6.0.0.alpha)
+ actionpack (= 6.0.0.alpha)
nio4r (~> 2.0)
- websocket-driver (~> 0.6.1)
- actionmailer (5.2.0.alpha)
- actionpack (= 5.2.0.alpha)
- actionview (= 5.2.0.alpha)
- activejob (= 5.2.0.alpha)
+ websocket-driver (>= 0.6.1)
+ actionmailer (6.0.0.alpha)
+ actionpack (= 6.0.0.alpha)
+ actionview (= 6.0.0.alpha)
+ activejob (= 6.0.0.alpha)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (5.2.0.alpha)
- actionview (= 5.2.0.alpha)
- activesupport (= 5.2.0.alpha)
+ actionpack (6.0.0.alpha)
+ actionview (= 6.0.0.alpha)
+ activesupport (= 6.0.0.alpha)
rack (~> 2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.2.0.alpha)
- activesupport (= 5.2.0.alpha)
+ actionview (6.0.0.alpha)
+ activesupport (= 6.0.0.alpha)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (5.2.0.alpha)
- activesupport (= 5.2.0.alpha)
+ activejob (6.0.0.alpha)
+ activesupport (= 6.0.0.alpha)
globalid (>= 0.3.6)
- activemodel (5.2.0.alpha)
- activesupport (= 5.2.0.alpha)
- activerecord (5.2.0.alpha)
- activemodel (= 5.2.0.alpha)
- activesupport (= 5.2.0.alpha)
- arel (= 9.0.0.alpha)
- activestorage (5.2.0.alpha)
- actionpack (= 5.2.0.alpha)
- activerecord (= 5.2.0.alpha)
- activesupport (5.2.0.alpha)
+ activemodel (6.0.0.alpha)
+ activesupport (= 6.0.0.alpha)
+ activerecord (6.0.0.alpha)
+ activemodel (= 6.0.0.alpha)
+ activesupport (= 6.0.0.alpha)
+ arel (>= 9.0)
+ activestorage (6.0.0.alpha)
+ actionpack (= 6.0.0.alpha)
+ activerecord (= 6.0.0.alpha)
+ marcel (~> 0.3.1)
+ activesupport (6.0.0.alpha)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
minitest (~> 5.1)
tzinfo (~> 1.1)
- rails (5.2.0.alpha)
- actioncable (= 5.2.0.alpha)
- actionmailer (= 5.2.0.alpha)
- actionpack (= 5.2.0.alpha)
- actionview (= 5.2.0.alpha)
- activejob (= 5.2.0.alpha)
- activemodel (= 5.2.0.alpha)
- activerecord (= 5.2.0.alpha)
- activestorage (= 5.2.0.alpha)
- activesupport (= 5.2.0.alpha)
+ rails (6.0.0.alpha)
+ actioncable (= 6.0.0.alpha)
+ actionmailer (= 6.0.0.alpha)
+ actionpack (= 6.0.0.alpha)
+ actionview (= 6.0.0.alpha)
+ activejob (= 6.0.0.alpha)
+ activemodel (= 6.0.0.alpha)
+ activerecord (= 6.0.0.alpha)
+ activestorage (= 6.0.0.alpha)
+ activesupport (= 6.0.0.alpha)
bundler (>= 1.3.0)
- railties (= 5.2.0.alpha)
+ railties (= 6.0.0.alpha)
sprockets-rails (>= 2.0.0)
- railties (5.2.0.alpha)
- actionpack (= 5.2.0.alpha)
- activesupport (= 5.2.0.alpha)
+ railties (6.0.0.alpha)
+ actionpack (= 6.0.0.alpha)
+ activesupport (= 6.0.0.alpha)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
@@ -137,11 +101,12 @@ GEM
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)
+ addressable (2.5.2)
+ public_suffix (>= 2.0.2, < 4.0)
amq-protocol (2.2.0)
archive-zip (0.7.0)
io-like (~> 0.3.0)
+ arel (9.0.0)
ast (2.3.0)
aws-partitions (1.20.0)
aws-sdk-core (3.3.0)
@@ -222,37 +187,34 @@ GEM
concurrent-ruby (1.0.5-java)
connection_pool (2.2.1)
cookiejar (0.3.3)
+ crack (0.4.3)
+ safe_yaml (~> 1.0.0)
+ crass (1.0.3)
curses (1.0.2)
daemons (1.2.4)
dalli (2.7.6)
dante (0.2.0)
- declarative (0.0.9)
+ declarative (0.0.10)
declarative-option (0.1.0)
- delayed_job (4.1.3)
+ delayed_job (4.1.4)
activesupport (>= 3.0, < 5.2)
delayed_job_active_record (4.1.2)
activerecord (>= 3.0, < 5.2)
delayed_job (>= 3.0, < 5)
digest-crc (0.4.1)
- em-hiredis (0.3.1)
- eventmachine (~> 1.0)
- hiredis (~> 0.6.0)
em-http-request (1.1.5)
addressable (>= 2.3.4)
cookiejar (!= 0.3.1)
em-socksify (>= 0.3)
eventmachine (>= 1.0.3)
http_parser.rb (>= 0.6.0)
- em-socksify (0.3.1)
+ em-socksify (0.3.2)
eventmachine (>= 1.0.0.beta.4)
- erubi (1.6.1)
- et-orbi (1.0.5)
+ erubi (1.7.0)
+ et-orbi (1.0.8)
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)
faraday (0.13.1)
multipart-post (>= 1.2, < 3)
@@ -273,37 +235,39 @@ GEM
ffi (1.9.18-java)
ffi (1.9.18-x64-mingw32)
ffi (1.9.18-x86-mingw32)
- globalid (0.4.0)
+ globalid (0.4.1)
activesupport (>= 4.2.0)
- google-api-client (0.13.1)
+ google-api-client (0.17.3)
addressable (~> 2.5, >= 2.5.1)
- googleauth (~> 0.5)
+ googleauth (>= 0.5, < 0.7.0)
httpclient (>= 2.8.1, < 3.0)
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- google-cloud-core (1.0.0)
+ google-cloud-core (1.1.0)
google-cloud-env (~> 1.0)
- googleauth (~> 0.5.1)
google-cloud-env (1.0.1)
faraday (~> 0.11)
- google-cloud-storage (1.4.0)
+ google-cloud-storage (1.9.0)
digest-crc (~> 0.4)
- google-api-client (~> 0.13.0)
- google-cloud-core (~> 1.0)
- googleauth (0.5.3)
+ google-api-client (~> 0.17.0)
+ google-cloud-core (~> 1.1)
+ googleauth (~> 0.6.2)
+ googleauth (0.6.2)
faraday (~> 0.12)
- jwt (~> 1.4)
+ jwt (>= 1.4, < 3.0)
logging (~> 2.0)
memoist (~> 0.12)
multi_json (~> 1.11)
os (~> 0.9)
signet (~> 0.7)
+ hashdiff (0.3.7)
hiredis (0.6.1)
hiredis (0.6.1-java)
http_parser.rb (0.6.0)
httpclient (2.8.3)
- i18n (0.8.6)
+ i18n (0.9.3)
+ concurrent-ruby (~> 1.0)
io-like (0.3.0)
jdbc-mysql (5.1.44)
jdbc-postgres (9.4.1206)
@@ -311,7 +275,7 @@ GEM
jmespath (1.3.1)
json (2.1.0)
json (2.1.0-java)
- jwt (1.5.6)
+ jwt (2.1.0)
kindlerb (1.2.0)
mustache
nokogiri
@@ -324,24 +288,28 @@ GEM
logging (2.2.2)
little-plugger (~> 1.1)
multi_json (~> 1.10)
- loofah (2.0.3)
+ loofah (2.1.1)
+ crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.0)
mini_mime (>= 0.1.1)
+ marcel (0.3.1)
+ mimemagic (~> 0.3.2)
memoist (0.16.0)
metaclass (0.0.4)
- method_source (0.8.2)
+ method_source (0.9.0)
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
+ mimemagic (0.3.2)
mini_magick (4.8.0)
mini_mime (0.1.4)
- mini_portile2 (2.2.0)
- minitest (5.10.3)
+ mini_portile2 (2.3.0)
+ minitest (5.11.3)
minitest-bisect (1.4.0)
minitest-server (~> 1.0)
path_expander (~> 1.0)
- minitest-server (1.0.4)
+ minitest-server (1.0.5)
minitest (~> 5.0)
mocha (1.3.0)
metaclass (~> 0.0.1)
@@ -350,32 +318,33 @@ GEM
msgpack (1.1.0-java)
msgpack (1.1.0-x64-mingw32)
msgpack (1.1.0-x86-mingw32)
- multi_json (1.12.1)
+ multi_json (1.12.2)
multipart-post (2.0.0)
mustache (1.0.5)
mustermann (1.0.0)
mysql2 (0.4.9)
mysql2 (0.4.9-x64-mingw32)
mysql2 (0.4.9-x86-mingw32)
- 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)
- mini_portile2 (~> 2.2.0)
+ nio4r (2.2.0)
+ nio4r (2.2.0-java)
+ nokogiri (1.8.1)
+ mini_portile2 (~> 2.3.0)
+ nokogiri (1.8.1-java)
+ nokogiri (1.8.1-x64-mingw32)
+ mini_portile2 (~> 2.3.0)
+ nokogiri (1.8.1-x86-mingw32)
+ mini_portile2 (~> 2.3.0)
os (0.9.6)
parallel (1.12.0)
parser (2.4.0.0)
ast (~> 2.2)
path_expander (1.0.2)
- pg (0.19.0)
- pg (0.19.0-x64-mingw32)
- pg (0.19.0-x86-mingw32)
+ pg (1.0.0)
+ pg (1.0.0-x64-mingw32)
+ pg (1.0.0-x86-mingw32)
powerpack (0.1.1)
psych (2.2.4)
- public_suffix (2.0.5)
+ public_suffix (3.0.1)
puma (3.9.1)
puma (3.9.1-java)
que (0.14.0)
@@ -388,7 +357,7 @@ GEM
rack (>= 0.4)
rack-protection (2.0.0)
rack
- rack-test (0.7.0)
+ rack-test (0.8.0)
rack (>= 1.0, < 3)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
@@ -399,7 +368,7 @@ GEM
rake
rake (12.2.1)
rb-fsevent (0.10.2)
- rdoc (5.1.0)
+ rdoc (6.0.1)
redcarpet (3.2.3)
redis (4.0.1)
redis-namespace (1.6.0)
@@ -414,6 +383,11 @@ GEM
redis-namespace (~> 1.3)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
+ resque-scheduler (4.3.1)
+ mono_logger (~> 1.0)
+ redis (>= 3.3, < 5)
+ resque (~> 1.26)
+ rufus-scheduler (~> 3.2)
retriable (3.1.1)
rubocop (0.51.0)
parallel (~> 1.10)
@@ -427,11 +401,20 @@ GEM
rubyzip (1.2.1)
rufus-scheduler (3.4.2)
et-orbi (~> 1.0)
- sass (3.5.1)
+ safe_yaml (1.0.4)
+ sass (3.5.3)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
+ sass-rails (5.0.7)
+ railties (>= 4.0.0, < 6)
+ sass (~> 3.1)
+ sprockets (>= 2.8, < 4.0)
+ sprockets-rails (>= 2.0, < 4.0)
+ tilt (>= 1.1, < 3)
+ sdoc (1.0.0)
+ rdoc (>= 5.0)
selenium-webdriver (3.5.1)
childprocess (~> 0.5)
rubyzip (~> 1.0)
@@ -444,10 +427,10 @@ GEM
rack-protection (>= 1.5.0)
redis (>= 3.3.4, < 5)
sigdump (0.2.4)
- signet (0.7.3)
+ signet (0.8.1)
addressable (~> 2.3)
faraday (~> 0.9)
- jwt (~> 1.5)
+ jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
sinatra (2.0.0)
mustermann (~> 1.0)
@@ -500,9 +483,15 @@ GEM
json (>= 1.8)
nokogiri (~> 1.6)
wdm (0.1.1)
+ webmock (3.2.1)
+ addressable (>= 2.3.6)
+ crack (>= 0.3.2)
+ hashdiff
websocket (1.2.4)
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
+ websocket-driver (0.6.5-java)
+ websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
xpath (2.1.0)
nokogiri (~> 1.3)
@@ -517,7 +506,6 @@ DEPENDENCIES
activerecord-jdbcmysql-adapter (>= 1.3.0)
activerecord-jdbcpostgresql-adapter (>= 1.3.0)
activerecord-jdbcsqlite3-adapter (>= 1.3.0)
- arel!
aws-sdk-s3
azure-storage
backburner
@@ -530,11 +518,11 @@ DEPENDENCIES
capybara (~> 2.15)
chromedriver-helper
coffee-rails
+ connection_pool
dalli (>= 2.2.1)
delayed_job
delayed_job_active_record
- em-hiredis
- google-cloud-storage (~> 1.3)
+ google-cloud-storage (~> 1.8)
hiredis
json (>= 2.0.0)
kindlerb (~> 1.2.0)
@@ -544,7 +532,7 @@ DEPENDENCIES
minitest-bisect
mocha
mysql2 (>= 0.4.4)
- nokogiri (>= 1.6.8)
+ nokogiri (>= 1.8.1)
pg (>= 0.18.0)
psych (~> 2.0)
puma
@@ -560,10 +548,10 @@ DEPENDENCIES
redis (~> 4.0)
redis-namespace
resque
- resque-scheduler!
+ resque-scheduler
rubocop (>= 0.47)
- sass-rails!
- sdoc!
+ sass-rails
+ sdoc (~> 1.0)
sequel
sidekiq
sneakers
@@ -576,7 +564,8 @@ DEPENDENCIES
uglifier (>= 1.3.0)
w3c_validators
wdm (>= 0.1.0)
+ webmock
websocket-client-simple!
BUNDLED WITH
- 1.15.4
+ 1.16.1
diff --git a/MIT-LICENSE b/MIT-LICENSE
index 6b3cead1a7..8f769c0767 100644
--- a/MIT-LICENSE
+++ b/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2017 David Heinemeier Hansson
+Copyright (c) 2005-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/RAILS_VERSION b/RAILS_VERSION
index d5ebf861d3..747766c587 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-5.2.0.alpha
+6.0.0.alpha
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index 5b9cc84c09..72f5c5de35 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,27 +1,3 @@
-* 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)).
- Provided fix minifies identifiers longer than 63 chars by hashing them with SHA1.
-
- Fixes #28751.
-
- *Vladimir Dementyev*
-
-* Action Cable's `redis` adapter allows for other common redis-rb options (`host`, `port`, `db`, `password`) in cable.yml.
-
- Previously, it accepts only a [redis:// url](https://www.iana.org/assignments/uri-schemes/prov/redis) as an option.
- While we can add all of these options to the `url` itself, it is not explicitly documented. This alternative setup
- is shown as the first example in the [Redis rubygem](https://github.com/redis/redis-rb#getting-started), which
- makes this set of options as sensible as using just the `url`.
-
- *Marc Rendl Ignacio*
-
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actioncable/CHANGELOG.md) for previous changes.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actioncable/CHANGELOG.md) for previous changes.
diff --git a/actioncable/MIT-LICENSE b/actioncable/MIT-LICENSE
index 1a0e653b69..a42759f024 100644
--- a/actioncable/MIT-LICENSE
+++ b/actioncable/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2015-2017 Basecamp, LLC
+Copyright (c) 2015-2018 Basecamp, LLC
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actioncable/README.md b/actioncable/README.md
index 44fb81478d..a05ef1dd20 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -558,7 +558,7 @@ API documentation is at:
* http://api.rubyonrails.org
-Bug reports can be filed for the Ruby on Rails project here:
+Bug reports for the Ruby on Rails project can be filed here:
* https://github.com/rails/rails/issues
diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec
index b5b98f1a6b..51db4dda3a 100644
--- a/actioncable/actioncable.gemspec
+++ b/actioncable/actioncable.gemspec
@@ -28,5 +28,5 @@ Gem::Specification.new do |s|
s.add_dependency "actionpack", version
s.add_dependency "nio4r", "~> 2.0"
- s.add_dependency "websocket-driver", "~> 0.6.1"
+ s.add_dependency "websocket-driver", ">= 0.6.1"
end
diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb
index 001a043409..e7456e3c1b 100644
--- a/actioncable/lib/action_cable.rb
+++ b/actioncable/lib/action_cable.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2015-2017 Basecamp, LLC
+# Copyright (c) 2015-2018 Basecamp, LLC
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb
index 10289ab55c..4b1964c4ae 100644
--- a/actioncable/lib/action_cable/connection/client_socket.rb
+++ b/actioncable/lib/action_cable/connection/client_socket.rb
@@ -83,7 +83,7 @@ module ActionCable
when Numeric then @driver.text(message.to_s)
when String then @driver.text(message)
when Array then @driver.binary(message)
- else false
+ else false
end
end
diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb
index af8277d06e..cd1d9bccef 100644
--- a/actioncable/lib/action_cable/gem_version.rb
+++ b/actioncable/lib/action_cable/gem_version.rb
@@ -7,8 +7,8 @@ module ActionCable
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb
index a9c0949950..e384ea4afb 100644
--- a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-gem "pg", "~> 0.18"
+gem "pg", ">= 0.18", "< 2.0"
require "pg"
require "thread"
require "digest/sha1"
diff --git a/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb b/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb.tt
index d672697283..d672697283 100644
--- a/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb
+++ b/actioncable/lib/rails/generators/channel/templates/application_cable/channel.rb.tt
diff --git a/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb b/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb.tt
index 0ff5442f47..0ff5442f47 100644
--- a/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb
+++ b/actioncable/lib/rails/generators/channel/templates/application_cable/connection.rb.tt
diff --git a/actioncable/lib/rails/generators/channel/templates/assets/cable.js b/actioncable/lib/rails/generators/channel/templates/assets/cable.js.tt
index 739aa5f022..739aa5f022 100644
--- a/actioncable/lib/rails/generators/channel/templates/assets/cable.js
+++ b/actioncable/lib/rails/generators/channel/templates/assets/cable.js.tt
diff --git a/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee b/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee.tt
index 5467811aba..5467811aba 100644
--- a/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee
+++ b/actioncable/lib/rails/generators/channel/templates/assets/channel.coffee.tt
diff --git a/actioncable/lib/rails/generators/channel/templates/assets/channel.js b/actioncable/lib/rails/generators/channel/templates/assets/channel.js.tt
index ab0e68b11a..ab0e68b11a 100644
--- a/actioncable/lib/rails/generators/channel/templates/assets/channel.js
+++ b/actioncable/lib/rails/generators/channel/templates/assets/channel.js.tt
diff --git a/actioncable/lib/rails/generators/channel/templates/channel.rb b/actioncable/lib/rails/generators/channel/templates/channel.rb.tt
index 4bcfb2be4d..4bcfb2be4d 100644
--- a/actioncable/lib/rails/generators/channel/templates/channel.rb
+++ b/actioncable/lib/rails/generators/channel/templates/channel.rb.tt
diff --git a/actioncable/package.json b/actioncable/package.json
index acec1e2e9c..d9df46043d 100644
--- a/actioncable/package.json
+++ b/actioncable/package.json
@@ -1,6 +1,6 @@
{
"name": "actioncable",
- "version": "5.2.0-alpha",
+ "version": "6.0.0-alpha",
"description": "WebSocket framework for Ruby on Rails.",
"main": "lib/assets/compiled/action_cable.js",
"files": [
diff --git a/actioncable/test/channel/base_test.rb b/actioncable/test/channel/base_test.rb
index 866bd7c21b..3b8eb63975 100644
--- a/actioncable/test/channel/base_test.rb
+++ b/actioncable/test/channel/base_test.rb
@@ -97,12 +97,12 @@ class ActionCable::Channel::BaseTest < ActiveSupport::TestCase
@channel.subscribe_to_channel
assert @channel.room
- assert @channel.subscribed?
+ assert_predicate @channel, :subscribed?
@channel.unsubscribe_from_channel
- assert ! @channel.room
- assert ! @channel.subscribed?
+ assert_not @channel.room
+ assert_not_predicate @channel, :subscribed?
end
test "connection identifiers" do
diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb
index eca06fe365..e9e8849637 100644
--- a/actioncable/test/channel/stream_test.rb
+++ b/actioncable/test/channel/stream_test.rb
@@ -167,7 +167,7 @@ module ActionCable::StreamTests
@server.broadcast "channel", {}
wait_for_async
- refute Thread.current[:ran_callback], "User callback was not run through the worker pool"
+ assert_not Thread.current[:ran_callback], "User callback was not run through the worker pool"
end
end
@@ -192,10 +192,10 @@ module ActionCable::StreamTests
Connection.new(@server, env).tap do |connection|
connection.process
- assert connection.websocket.possible?
+ assert_predicate connection.websocket, :possible?
wait_for_async
- assert connection.websocket.alive?
+ assert_predicate connection.websocket, :alive?
end
end
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
index 56b3ef143b..ec672a5828 100644
--- a/actioncable/test/client_test.rb
+++ b/actioncable/test/client_test.rb
@@ -307,7 +307,7 @@ class ClientTest < ActionCable::TestCase
ActionCable.server.restart
c.wait_for_close
- assert c.closed?
+ assert_predicate c, :closed?
end
end
end
diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb
index 99488e38c8..c69d4d0b36 100644
--- a/actioncable/test/connection/base_test.rb
+++ b/actioncable/test/connection/base_test.rb
@@ -39,10 +39,10 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
connection = open_connection
connection.process
- assert connection.websocket.possible?
+ assert_predicate connection.websocket, :possible?
wait_for_async
- assert connection.websocket.alive?
+ assert_predicate connection.websocket, :alive?
end
end
@@ -95,7 +95,7 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
statistics = connection.statistics
- assert statistics[:identifier].blank?
+ assert_predicate statistics[:identifier], :blank?
assert_kind_of Time, statistics[:started_at]
assert_equal [], statistics[:subscriptions]
end
diff --git a/actioncable/test/connection/client_socket_test.rb b/actioncable/test/connection/client_socket_test.rb
index 2051216010..5c31690c8b 100644
--- a/actioncable/test/connection/client_socket_test.rb
+++ b/actioncable/test/connection/client_socket_test.rb
@@ -67,9 +67,9 @@ class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase
env = Rack::MockRequest.env_for "/test",
"HTTP_CONNECTION" => "upgrade", "HTTP_UPGRADE" => "websocket",
"HTTP_HOST" => "localhost", "HTTP_ORIGIN" => "http://rubyonrails.com"
- io = \
+ io, client_io = \
begin
- Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0).first
+ Socket.pair(Socket::AF_UNIX, Socket::SOCK_STREAM, 0)
rescue
StringIO.new
end
@@ -77,6 +77,14 @@ class ActionCable::Connection::ClientSocketTest < ActionCable::TestCase
Connection.new(@server, env).tap do |connection|
connection.process
+ if client_io
+ # Make sure server returns handshake response
+ Timeout.timeout(1) do
+ loop do
+ break if client_io.readline == "\r\n"
+ end
+ end
+ end
connection.send :handle_open
assert connection.connected
end
diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb
index 149a40604a..3323785494 100644
--- a/actioncable/test/connection/subscriptions_test.rb
+++ b/actioncable/test/connection/subscriptions_test.rb
@@ -45,7 +45,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
setup_connection
@subscriptions.execute_command "command" => "subscribe"
- assert @subscriptions.identifiers.empty?
+ assert_empty @subscriptions.identifiers
end
end
@@ -58,7 +58,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
channel.expects(:unsubscribe_from_channel)
@subscriptions.execute_command "command" => "unsubscribe", "identifier" => @chat_identifier
- assert @subscriptions.identifiers.empty?
+ assert_empty @subscriptions.identifiers
end
end
@@ -67,7 +67,7 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
setup_connection
@subscriptions.execute_command "command" => "unsubscribe"
- assert @subscriptions.identifiers.empty?
+ assert_empty @subscriptions.identifiers
end
end
diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb
index c533a9f3eb..b3e9ae9d5c 100644
--- a/actioncable/test/subscription_adapter/common.rb
+++ b/actioncable/test/subscription_adapter/common.rb
@@ -32,7 +32,7 @@ module CommonSubscriptionAdapterTest
subscribed = Concurrent::Event.new
adapter.subscribe(channel, callback, Proc.new { subscribed.set })
subscribed.wait(WAIT_WHEN_EXPECTING_EVENT)
- assert subscribed.set?
+ assert_predicate subscribed, :set?
yield queue
diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb
index 69120d5753..63823d6ef0 100644
--- a/actioncable/test/subscription_adapter/redis_test.rb
+++ b/actioncable/test/subscription_adapter/redis_test.rb
@@ -4,12 +4,15 @@ require "test_helper"
require_relative "common"
require_relative "channel_prefix"
+require "active_support/testing/method_call_assertions"
+require "action_cable/subscription_adapter/redis"
+
class RedisAdapterTest < ActionCable::TestCase
include CommonSubscriptionAdapterTest
include ChannelPrefixTest
def cable_config
- { adapter: "redis", driver: "ruby", url: "redis://:password@127.0.0.1:6379/12" }
+ { adapter: "redis", driver: "ruby" }
end
end
@@ -23,6 +26,22 @@ class RedisAdapterTest::AlternateConfiguration < RedisAdapterTest
def cable_config
alt_cable_config = super.dup
alt_cable_config.delete(:url)
- alt_cable_config.merge(host: "127.0.0.1", port: 6379, db: 12, password: "password")
+ alt_cable_config.merge(host: "127.0.0.1", port: 6379, db: 12)
+ end
+end
+
+class RedisAdapterTest::Connector < ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+
+ test "slices url, host, port, db, and password from config" do
+ config = { url: 1, host: 2, port: 3, db: 4, password: 5 }
+
+ assert_called_with ::Redis, :new, [ config ] do
+ connect config.merge(other: "unrelated", stuff: "here")
+ end
+ end
+
+ def connect(config)
+ ActionCable::SubscriptionAdapter::Redis.redis_connector.call(config)
end
end
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 49afec9a12..b2a1c570e5 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,20 +1,3 @@
-* 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
- self.delivery_job = MyCustomDeliveryJob
-
- ...
- end
-
- *Matthew Mongeau*
-
-
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionmailer/CHANGELOG.md) for previous changes.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionmailer/CHANGELOG.md) for previous changes.
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index ac810e86d0..1cb3add0fc 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2017 David Heinemeier Hansson
+Copyright (c) 2004-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index 9993d3777d..14dfb82234 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -148,7 +148,7 @@ The latest version of Action Mailer can be installed with RubyGems:
$ gem install actionmailer
-Source code can be downloaded as part of the Rails project on GitHub
+Source code can be downloaded as part of the Rails project on GitHub:
* https://github.com/rails/rails/tree/master/actionmailer
@@ -166,7 +166,7 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports can be filed for the Ruby on Rails project here:
+Bug reports for the Ruby on Rails project can be filed here:
* https://github.com/rails/rails/issues
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index ade3fe39a2..fabbdd1b25 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2017 David Heinemeier Hansson
+# Copyright (c) 2004-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index eb8ae59533..3af95081ee 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -889,7 +889,7 @@ module ActionMailer
default_values = self.class.default.map do |key, value|
[
key,
- value.is_a?(Proc) ? instance_exec(&value) : value
+ compute_default(value)
]
end.to_h
@@ -898,6 +898,16 @@ module ActionMailer
headers_with_defaults
end
+ def compute_default(value)
+ return value unless value.is_a?(Proc)
+
+ if value.arity == 1
+ instance_exec(self, &value)
+ else
+ instance_exec(&value)
+ end
+ end
+
def assign_headers_to_message(message, headers)
assignable = headers.except(:parts_order, :content_type, :body, :template_name,
:template_path, :delivery_method, :delivery_method_options)
diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb
index 063d4580d8..72eb5d61e8 100644
--- a/actionmailer/lib/action_mailer/gem_version.rb
+++ b/actionmailer/lib/action_mailer/gem_version.rb
@@ -7,8 +7,8 @@ module ActionMailer
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
index 4bef4a58d3..8a12f805cc 100644
--- a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
+++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
@@ -4,7 +4,7 @@ require "base64"
module ActionMailer
# Implements a mailer preview interceptor that converts image tag src attributes
- # that use inline cid: style urls to data: style urls so that they are visible
+ # that use inline cid: style URLs to data: style URLs so that they are visible
# when previewing an HTML email in a web browser.
#
# This interceptor is enabled by default. To disable it, delete it from the
diff --git a/actionmailer/lib/action_mailer/message_delivery.rb b/actionmailer/lib/action_mailer/message_delivery.rb
index a2ea45dc7b..2377aeb9a5 100644
--- a/actionmailer/lib/action_mailer/message_delivery.rb
+++ b/actionmailer/lib/action_mailer/message_delivery.rb
@@ -53,6 +53,12 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_later!(wait: 1.hour)
# Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now)
#
+ # Options:
+ #
+ # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
+ # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
+ # * <tt>:queue</tt> - Enqueue the email on the specified queue
+ #
# By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
# <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
# +delivery_job+.
@@ -60,12 +66,6 @@ module ActionMailer
# class AccountRegistrationMailer < ApplicationMailer
# self.delivery_job = RegistrationDeliveryJob
# end
- #
- # Options:
- #
- # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay
- # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time
- # * <tt>:queue</tt> - Enqueue the email on the specified queue
def deliver_later!(options = {})
enqueue_delivery :deliver_now!, options
end
@@ -77,6 +77,12 @@ module ActionMailer
# Notifier.welcome(User.first).deliver_later(wait: 1.hour)
# Notifier.welcome(User.first).deliver_later(wait_until: 10.hours.from_now)
#
+ # Options:
+ #
+ # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay.
+ # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time.
+ # * <tt>:queue</tt> - Enqueue the email on the specified queue.
+ #
# By default, the email will be enqueued using <tt>ActionMailer::DeliveryJob</tt>. Each
# <tt>ActionMailer::Base</tt> class can specify the job to use by setting the class variable
# +delivery_job+.
@@ -84,12 +90,6 @@ module ActionMailer
# class AccountRegistrationMailer < ApplicationMailer
# self.delivery_job = RegistrationDeliveryJob
# end
- #
- # Options:
- #
- # * <tt>:wait</tt> - Enqueue the email to be delivered with a delay.
- # * <tt>:wait_until</tt> - Enqueue the email to be delivered at (after) a specific date / time.
- # * <tt>:queue</tt> - Enqueue the email on the specified queue.
def deliver_later(options = {})
enqueue_delivery :deliver_now, options
end
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index 730ac89c94..0aea84fd2b 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -104,7 +104,7 @@ module ActionMailer
private
def load_previews
if preview_path
- Dir["#{preview_path}/**/*_preview.rb"].each { |file| require_dependency file }
+ Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
end
end
diff --git a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb.tt
index 00fb9bd48f..00fb9bd48f 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/application_mailer.rb.tt
diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb.tt
index 348d314758..348d314758 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb.tt
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 977e0e201e..4124aa00bd 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -506,8 +506,8 @@ class BaseTest < ActiveSupport::TestCase
test "should respond to action methods" do
assert_respond_to BaseMailer, :welcome
assert_respond_to BaseMailer, :implicit_multipart
- assert !BaseMailer.respond_to?(:mail)
- assert !BaseMailer.respond_to?(:headers)
+ assert_not_respond_to BaseMailer, :mail
+ assert_not_respond_to BaseMailer, :headers
end
test "calling just the action should return the generated mail object" do
@@ -725,6 +725,15 @@ class BaseTest < ActiveSupport::TestCase
assert(ProcMailer.welcome["x-has-to-proc"].to_s == "symbol")
end
+ test "proc default values can have arity of 1 where arg is a mailer instance" do
+ assert_equal(ProcMailer.welcome["X-Lambda-Arity-1-arg"].to_s, "complex_value")
+ assert_equal(ProcMailer.welcome["X-Lambda-Arity-1-self"].to_s, "complex_value")
+ end
+
+ test "proc default values with fixed arity of 0 can be called" do
+ assert_equal("0", ProcMailer.welcome["X-Lambda-Arity-0"].to_s)
+ end
+
test "we can call other defined methods on the class as needed" do
mail = ProcMailer.welcome
assert_equal("Thanks for signing up this afternoon", mail.subject)
diff --git a/actionmailer/test/caching_test.rb b/actionmailer/test/caching_test.rb
index ae4e7bf57e..574840c30e 100644
--- a/actionmailer/test/caching_test.rb
+++ b/actionmailer/test/caching_test.rb
@@ -105,7 +105,7 @@ class FragmentCachingTest < BaseCachingTest
html_safe = @mailer.read_fragment("name")
assert_equal content, html_safe
- assert html_safe.html_safe?
+ assert_predicate html_safe, :html_safe?
end
end
@@ -262,7 +262,7 @@ class ViewCacheDependencyTest < BaseCachingTest
end
def test_view_cache_dependencies_are_empty_by_default
- assert NoDependenciesMailer.new.view_cache_dependencies.empty?
+ assert_empty NoDependenciesMailer.new.view_cache_dependencies
end
def test_view_cache_dependencies_are_listed_in_declaration_order
diff --git a/actionmailer/test/mailers/proc_mailer.rb b/actionmailer/test/mailers/proc_mailer.rb
index b7cf53eb4a..76e730bb79 100644
--- a/actionmailer/test/mailers/proc_mailer.rb
+++ b/actionmailer/test/mailers/proc_mailer.rb
@@ -4,12 +4,19 @@ class ProcMailer < ActionMailer::Base
default to: "system@test.lindsaar.net",
"X-Proc-Method" => Proc.new { Time.now.to_i.to_s },
subject: Proc.new { give_a_greeting },
- "x-has-to-proc" => :symbol
+ "x-has-to-proc" => :symbol,
+ "X-Lambda-Arity-0" => ->() { "0" },
+ "X-Lambda-Arity-1-arg" => ->(arg) { arg.computed_value },
+ "X-Lambda-Arity-1-self" => ->(_) { self.computed_value }
def welcome
mail
end
+ def computed_value
+ "complex_value"
+ end
+
private
def give_a_greeting
diff --git a/actionmailer/test/message_delivery_test.rb b/actionmailer/test/message_delivery_test.rb
index 03e8d4fb66..f8dcb3f4ba 100644
--- a/actionmailer/test/message_delivery_test.rb
+++ b/actionmailer/test/message_delivery_test.rb
@@ -39,7 +39,7 @@ class MessageDeliveryTest < ActiveSupport::TestCase
end
test "its message should be a Mail::Message" do
- assert_equal Mail::Message , @mail.message.class
+ assert_equal Mail::Message, @mail.message.class
end
test "should respond to .deliver_later" do
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index 82035689ad..3c940bc969 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -105,7 +105,7 @@ class ActionMailerUrlTest < ActionMailer::TestCase
assert_url_for "/dummy_model", DummyModel
# array
- assert_url_for "/dummy_model" , [DummyModel]
+ assert_url_for "/dummy_model", [DummyModel]
end
def test_signed_up_with_url
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index e01f88e902..0d10a9a3f2 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,139 +1,7 @@
-* Make `assert_recognizes` to traverse mounted engines
+* Add alias method `to_hash` to `to_h` for `cookies`.
+ Add alias method `to_h` to `to_hash` for `session`.
- *Yuichiro Kaneko*
+ *Igor Kasyanchuk*
-* 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.
-
- *Yuji Yaginuma*
-
-* Deprecate `ActionDispatch::TestResponse` response aliases
-
- `#success?`, `#missing?` & `#error?` are not supported by the actual
- `ActionDispatch::Response` object and can produce false-positives. Instead,
- use the response helpers provided by `Rack::Response`.
-
- *Trevor Wistaff*
-
-* Protect from forgery by default
-
- Rather than protecting from forgery in the generated `ApplicationController`,
- add it to `ActionController::Base` depending on
- `config.action_controller.default_protect_from_forgery`. This configuration
- defaults to false to support older versions which have removed it from their
- `ApplicationController`, but is set to true for Rails 5.2.
-
- *Lisa Ugray*
-
-* Fallback `ActionController::Parameters#to_s` to `Hash#to_s`.
-
- *Kir Shatrov*
-
-* `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
- the `:options` parameter.
-
- Refer to the respective driver's documentation to see what options can be passed.
-
- *Mario Chavez*
-
-* 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
- encrypted using AES in CBC HMAC mode will be seamlessly upgraded when
- this new mode is enabled via the
- `action_dispatch.use_authenticated_cookie_encryption` configuration value.
-
- *Michael J Coyne*
-
-* Change the cache key format for fragments to make it easier to debug key churn. The new format is:
-
- views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123
- ^template path ^template tree digest ^class ^id
-
- *DHH*
-
-* Add support for recyclable cache keys with fragment caching. This uses the new versioned entries in the
- `ActiveSupport::Cache` stores and relies on the fact that Active Record has split `#cache_key` and `#cache_version`
- to support it.
-
- *DHH*
-
-* Add `action_controller_api` and `action_controller_base` load hooks to be called in `ActiveSupport.on_load`
-
- `ActionController::Base` and `ActionController::API` have differing implementations. This means that
- the one umbrella hook `action_controller` is not able to address certain situations where a method
- may not exist in a certain implementation.
-
- This is fixed by adding two new hooks so you can target `ActionController::Base` vs `ActionController::API`
-
- Fixes #27013.
-
- *Julian Nadeau*
-
-
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionpack/CHANGELOG.md) for previous changes.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index ac810e86d0..1cb3add0fc 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2017 David Heinemeier Hansson
+Copyright (c) 2004-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 93b2a0932a..f56230ffa0 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -30,7 +30,7 @@ The latest version of Action Pack can be installed with RubyGems:
$ gem install actionpack
-Source code can be downloaded as part of the Rails project on GitHub
+Source code can be downloaded as part of the Rails project on GitHub:
* https://github.com/rails/rails/tree/master/actionpack
@@ -44,11 +44,11 @@ Action Pack is released under the MIT license:
== Support
-API documentation is at
+API documentation is at:
* http://api.rubyonrails.org
-Bug reports can be filed for the Ruby on Rails project here:
+Bug reports for the Ruby on Rails project can be filed here:
* https://github.com/rails/rails/issues
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index bd19b8cd5d..f43784f9f2 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -22,6 +22,7 @@ module ActionController
autoload_under "metal" do
autoload :ConditionalGet
+ autoload :ContentSecurityPolicy
autoload :Cookies
autoload :DataStreaming
autoload :EtagWithTemplateDigest
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index b73269871b..204a3d400c 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -225,6 +225,7 @@ module ActionController
Flash,
FormBuilder,
RequestForgeryProtection,
+ ContentSecurityPolicy,
ForceSSL,
Streaming,
DataStreaming,
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 457884ea08..f875aa5e6b 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -230,18 +230,16 @@ module ActionController
# Returns a Rack endpoint for the given action name.
def self.action(name)
+ app = lambda { |env|
+ req = ActionDispatch::Request.new(env)
+ res = make_response! req
+ new.dispatch(name, req, res)
+ }
+
if middleware_stack.any?
- middleware_stack.build(name) do |env|
- req = ActionDispatch::Request.new(env)
- res = make_response! req
- new.dispatch(name, req, res)
- end
+ middleware_stack.build(name, app)
else
- lambda { |env|
- req = ActionDispatch::Request.new(env)
- res = make_response! req
- new.dispatch(name, req, res)
- }
+ app
end
end
diff --git a/actionpack/lib/action_controller/metal/content_security_policy.rb b/actionpack/lib/action_controller/metal/content_security_policy.rb
new file mode 100644
index 0000000000..48a7109bea
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/content_security_policy.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module ActionController #:nodoc:
+ module ContentSecurityPolicy
+ # TODO: Documentation
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def content_security_policy(**options, &block)
+ before_action(options) do
+ if block_given?
+ policy = request.content_security_policy.clone
+ yield policy
+ request.content_security_policy = policy
+ end
+ end
+ end
+
+ def content_security_policy_report_only(report_only = true, **options)
+ before_action(options) do
+ request.content_security_policy_report_only = report_only
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index 0ba1f9f783..7de500d119 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -39,7 +39,7 @@ module ActionController
# end
#
# ==== URL Options
- # You can pass any of the following options to affect the redirect url
+ # You can pass any of the following options to affect the redirect URL
# * <tt>host</tt> - Redirect to a different host name
# * <tt>subdomain</tt> - Redirect to a different subdomain
# * <tt>domain</tt> - Redirect to a different domain
@@ -73,7 +73,7 @@ module ActionController
# Redirect the existing request to use the HTTPS protocol.
#
# ==== Parameters
- # * <tt>host_or_options</tt> - Either a host name or any of the url and
+ # * <tt>host_or_options</tt> - Either a host name or any of the URL and
# redirect options available to the <tt>force_ssl</tt> method.
def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 0c8132684a..01676f3237 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -72,10 +72,10 @@ module ActionController
before_action(options.except(:name, :password, :realm)) do
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
# This comparison uses & so that it doesn't short circuit and
- # uses `variable_size_secure_compare` so that length information
+ # uses `secure_compare` so that length information
# isn't leaked.
- ActiveSupport::SecurityUtils.variable_size_secure_compare(name, options[:name]) &
- ActiveSupport::SecurityUtils.variable_size_secure_compare(password, options[:password])
+ ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) &
+ ActiveSupport::SecurityUtils.secure_compare(password, options[:password])
end
end
end
@@ -350,10 +350,7 @@ module ActionController
# authenticate_or_request_with_http_token do |token, options|
# # Compare the tokens in a time-constant manner, to mitigate
# # timing attacks.
- # ActiveSupport::SecurityUtils.secure_compare(
- # ::Digest::SHA256.hexdigest(token),
- # ::Digest::SHA256.hexdigest(TOKEN)
- # )
+ # ActiveSupport::SecurityUtils.secure_compare(token, TOKEN)
# end
# end
# end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 8de57f9199..4c2b5120eb 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -68,7 +68,7 @@ module ActionController
# if possible, otherwise redirects to the provided default fallback
# location.
#
- # The referrer information is pulled from the HTTP `Referer` (sic) header on
+ # The referrer information is pulled from the HTTP +Referer+ (sic) header on
# the request. This is an optional header and its presence on the request is
# subject to browser security settings and user preferences. If the request
# is missing this header, the <tt>fallback_location</tt> will be used.
@@ -82,8 +82,8 @@ module ActionController
# redirect_back fallback_location: '/', allow_other_host: false
#
# ==== 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
+ # * <tt>:fallback_location</tt> - The default fallback location that will be used on missing +Referer+ header.
+ # * <tt>:allow_other_host</tt> - Allow or disallow redirection to the host that is different to the current host, defaults to true.
#
# All other options that can be passed to <tt>redirect_to</tt> are accepted as
# options and the behavior is identical.
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index bd133f24a1..0ab313e398 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -3,6 +3,7 @@
require "rack/session/abstract/id"
require "action_controller/metal/exceptions"
require "active_support/security_utils"
+require "active_support/core_ext/string/strip"
module ActionController #:nodoc:
class InvalidAuthenticityToken < ActionControllerError #:nodoc:
@@ -216,7 +217,7 @@ module ActionController #:nodoc:
# The actual before_action that is used to verify the CSRF token.
# Don't override this directly. Provide your own forgery protection
# strategy instead. If you override, you'll disable same-origin
- # `<script>` verification.
+ # <tt><script></tt> verification.
#
# Lean on the protect_from_forgery declaration to mark which actions are
# due for same-origin request verification. If protect_from_forgery is
@@ -250,7 +251,7 @@ module ActionController #:nodoc:
private_constant :CROSS_ORIGIN_JAVASCRIPT_WARNING
# :startdoc:
- # If `verify_authenticity_token` was run (indicating that we have
+ # If +verify_authenticity_token+ was run (indicating that we have
# forgery protection enabled for this request) then also verify that
# we aren't serving an unauthorized cross-origin response.
def verify_same_origin_request # :doc:
@@ -267,7 +268,7 @@ module ActionController #:nodoc:
@marked_for_same_origin_verification = request.get?
end
- # If the `verify_authenticity_token` before_action ran, verify that
+ # If the +verify_authenticity_token+ before_action ran, verify that
# JavaScript responses are only served to same-origin GET requests.
def marked_for_same_origin_verification? # :doc:
@marked_for_same_origin_verification ||= false
@@ -369,7 +370,7 @@ module ActionController #:nodoc:
end
def compare_with_real_token(token, session) # :doc:
- ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session))
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, real_csrf_token(session))
end
def valid_per_form_csrf_token?(token, session) # :doc:
@@ -380,7 +381,7 @@ module ActionController #:nodoc:
request.request_method
)
- ActiveSupport::SecurityUtils.secure_compare(token, correct_token)
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare(token, correct_token)
else
false
end
@@ -415,11 +416,21 @@ module ActionController #:nodoc:
allow_forgery_protection
end
+ NULL_ORIGIN_MESSAGE = <<-MSG.strip_heredoc
+ The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually
+ means you have the 'no-referrer' Referrer-Policy header enabled, or that you the request came from a site that
+ refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the
+ best solution is to change your referrer policy to something less strict like same-origin or strict-same-origin.
+ If you cannot change the referrer policy, you can disable origin checking with the
+ Rails.application.config.action_controller.forgery_protection_origin_check setting.
+ MSG
+
# Checks if the request originated from the same origin by looking at the
# Origin header.
def valid_request_origin? # :doc:
if forgery_protection_origin_check
# We accept blank origin headers because some user agents don't send it.
+ raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
request.origin.nil? || request.origin == request.base_url
else
true
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 0b1598bf1b..8dc01a5eb9 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -183,7 +183,7 @@ module ActionController #:nodoc:
# unicorn_rails --config-file unicorn.config.rb
#
# You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
- # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
+ # Please check its documentation for more information: https://bogomips.org/unicorn/Unicorn/Configurator.html#method-i-listen
#
# If you are using Unicorn with NGINX, you may need to tweak NGINX.
# Streaming should work out of the box on Rainbows.
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index ef7c4c4c16..a56ac749f8 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -335,7 +335,7 @@ module ActionController
# the same way as <tt>Hash#each_pair</tt>.
def each_pair(&block)
@parameters.each_pair do |key, value|
- yield key, convert_hashes_to_parameters(key, value)
+ yield [key, convert_hashes_to_parameters(key, value)]
end
end
alias_method :each, :each_pair
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 4b408750a4..798d142755 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -256,7 +256,7 @@ module ActionController
#
# def test_create
# json = {book: { title: "Love Hina" }}.to_json
- # post :create, json
+ # post :create, body: json
# end
#
# == Special instance variables
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 34937f3229..0822cdc0a6 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2017 David Heinemeier Hansson
+# Copyright (c) 2004-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -42,6 +42,7 @@ module ActionDispatch
eager_autoload do
autoload_under "http" do
+ autoload :ContentSecurityPolicy
autoload :Request
autoload :Response
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 3328ce17a0..a8febc32b3 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -133,7 +133,7 @@ module ActionDispatch
end
def generate_strong_etag(validators)
- %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
+ %("#{ActiveSupport::Digest.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}")
end
def cache_control_segments
diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb
new file mode 100644
index 0000000000..4883e23d24
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb
@@ -0,0 +1,231 @@
+# frozen_string_literal: true
+
+require "active_support/core_ext/object/deep_dup"
+
+module ActionDispatch #:nodoc:
+ class ContentSecurityPolicy
+ class Middleware
+ CONTENT_TYPE = "Content-Type".freeze
+ POLICY = "Content-Security-Policy".freeze
+ POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only".freeze
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ request = ActionDispatch::Request.new env
+ _, headers, _ = response = @app.call(env)
+
+ return response unless html_response?(headers)
+ return response if policy_present?(headers)
+
+ if policy = request.content_security_policy
+ headers[header_name(request)] = policy.build(request.controller_instance)
+ end
+
+ response
+ end
+
+ private
+
+ def html_response?(headers)
+ if content_type = headers[CONTENT_TYPE]
+ content_type =~ /html/
+ end
+ end
+
+ def header_name(request)
+ if request.content_security_policy_report_only
+ POLICY_REPORT_ONLY
+ else
+ POLICY
+ end
+ end
+
+ def policy_present?(headers)
+ headers[POLICY] || headers[POLICY_REPORT_ONLY]
+ end
+ end
+
+ module Request
+ POLICY = "action_dispatch.content_security_policy".freeze
+ POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
+
+ def content_security_policy
+ get_header(POLICY)
+ end
+
+ def content_security_policy=(policy)
+ set_header(POLICY, policy)
+ end
+
+ def content_security_policy_report_only
+ get_header(POLICY_REPORT_ONLY)
+ end
+
+ def content_security_policy_report_only=(value)
+ set_header(POLICY_REPORT_ONLY, value)
+ end
+ end
+
+ MAPPINGS = {
+ self: "'self'",
+ unsafe_eval: "'unsafe-eval'",
+ unsafe_inline: "'unsafe-inline'",
+ none: "'none'",
+ http: "http:",
+ https: "https:",
+ data: "data:",
+ mediastream: "mediastream:",
+ blob: "blob:",
+ filesystem: "filesystem:",
+ report_sample: "'report-sample'",
+ strict_dynamic: "'strict-dynamic'"
+ }.freeze
+
+ DIRECTIVES = {
+ base_uri: "base-uri",
+ child_src: "child-src",
+ connect_src: "connect-src",
+ default_src: "default-src",
+ font_src: "font-src",
+ form_action: "form-action",
+ frame_ancestors: "frame-ancestors",
+ frame_src: "frame-src",
+ img_src: "img-src",
+ manifest_src: "manifest-src",
+ media_src: "media-src",
+ object_src: "object-src",
+ script_src: "script-src",
+ style_src: "style-src",
+ worker_src: "worker-src"
+ }.freeze
+
+ private_constant :MAPPINGS, :DIRECTIVES
+
+ attr_reader :directives
+
+ def initialize
+ @directives = {}
+ yield self if block_given?
+ end
+
+ def initialize_copy(other)
+ @directives = other.directives.deep_dup
+ end
+
+ DIRECTIVES.each do |name, directive|
+ define_method(name) do |*sources|
+ if sources.first
+ @directives[directive] = apply_mappings(sources)
+ else
+ @directives.delete(directive)
+ end
+ end
+ end
+
+ def block_all_mixed_content(enabled = true)
+ if enabled
+ @directives["block-all-mixed-content"] = true
+ else
+ @directives.delete("block-all-mixed-content")
+ end
+ end
+
+ def plugin_types(*types)
+ if types.first
+ @directives["plugin-types"] = types
+ else
+ @directives.delete("plugin-types")
+ end
+ end
+
+ def report_uri(uri)
+ @directives["report-uri"] = [uri]
+ end
+
+ def require_sri_for(*types)
+ if types.first
+ @directives["require-sri-for"] = types
+ else
+ @directives.delete("require-sri-for")
+ end
+ end
+
+ def sandbox(*values)
+ if values.empty?
+ @directives["sandbox"] = true
+ elsif values.first
+ @directives["sandbox"] = values
+ else
+ @directives.delete("sandbox")
+ end
+ end
+
+ def upgrade_insecure_requests(enabled = true)
+ if enabled
+ @directives["upgrade-insecure-requests"] = true
+ else
+ @directives.delete("upgrade-insecure-requests")
+ end
+ end
+
+ def build(context = nil)
+ build_directives(context).compact.join("; ") + ";"
+ end
+
+ private
+ def apply_mappings(sources)
+ sources.map do |source|
+ case source
+ when Symbol
+ apply_mapping(source)
+ when String, Proc
+ source
+ else
+ raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
+ end
+ end
+ end
+
+ def apply_mapping(source)
+ MAPPINGS.fetch(source) do
+ raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}"
+ end
+ end
+
+ def build_directives(context)
+ @directives.map do |directive, sources|
+ if sources.is_a?(Array)
+ "#{directive} #{build_directive(sources, context).join(' ')}"
+ elsif sources
+ directive
+ else
+ nil
+ end
+ end
+ end
+
+ def build_directive(sources, context)
+ sources.map { |source| resolve_source(source, context) }
+ end
+
+ def resolve_source(source, context)
+ case source
+ when String
+ source
+ when Symbol
+ source.to_s
+ when Proc
+ if context.nil?
+ raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
+ else
+ context.instance_exec(&source)
+ end
+ else
+ raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index f8e6fca36d..342e6de312 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -10,6 +10,7 @@ Mime::Type.register "text/css", :css
Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
Mime::Type.register "text/vcard", :vcf
+Mime::Type.register "text/vtt", :vtt, %w(vtt)
Mime::Type.register "image/png", :png, [], %w(png)
Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
@@ -20,6 +21,18 @@ Mime::Type.register "image/svg+xml", :svg
Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe)
+Mime::Type.register "audio/mpeg", :mp3, [], %w(mp1 mp2 mp3)
+Mime::Type.register "audio/ogg", :ogg, [], %w(oga ogg spx opus)
+Mime::Type.register "audio/aac", :m4a, %w( audio/mp4 ), %w(m4a mpg4 aac)
+
+Mime::Type.register "video/webm", :webm, [], %w(webm)
+Mime::Type.register "video/mp4", :mp4, [], %w(mp4 m4v)
+
+Mime::Type.register "font/otf", :otf, [], %w(otf)
+Mime::Type.register "font/ttf", :ttf, [], %w(ttf)
+Mime::Type.register "font/woff", :woff, [], %w(woff)
+Mime::Type.register "font/woff2", :woff2, [], %w(woff2)
+
Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml )
Mime::Type.register "application/rss+xml", :rss
Mime::Type.register "application/atom+xml", :atom
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index d631281e4b..3838b84a7a 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -22,6 +22,7 @@ module ActionDispatch
include ActionDispatch::Http::Parameters
include ActionDispatch::Http::FilterParameters
include ActionDispatch::Http::URL
+ include ActionDispatch::ContentSecurityPolicy::Request
include Rack::Request::Env
autoload :Session, "action_dispatch/request/session"
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index f0344fd927..35ba44005a 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -274,7 +274,7 @@ module ActionDispatch
def standard_port
case protocol
when "https://" then 443
- else 80
+ else 80
end
end
diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
index bdb78d8d48..56e9e3c83d 100644
--- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb
+++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb
@@ -9,16 +9,16 @@ module ActionDispatch
" #{from} -> #{to} [label=\"#{sym || 'ε'}\"];"
}
- #memo_nodes = memos.values.flatten.map { |n|
- # label = n
- # if Journey::Route === n
- # label = "#{n.verb.source} #{n.path.spec}"
- # end
- # " #{n.object_id} [label=\"#{label}\", shape=box];"
- #}
- #memo_edges = memos.flat_map { |k, memos|
- # (memos || []).map { |v| " #{k} -> #{v.object_id};" }
- #}.uniq
+ # memo_nodes = memos.values.flatten.map { |n|
+ # label = n
+ # if Journey::Route === n
+ # label = "#{n.verb.source} #{n.path.spec}"
+ # end
+ # " #{n.object_id} [label=\"#{label}\", shape=box];"
+ # }
+ # memo_edges = memos.flat_map { |k, memos|
+ # (memos || []).map { |v| " #{k} -> #{v.object_id};" }
+ # }.uniq
<<-eodot
digraph nfa {
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 86a070c6ad..c45d947904 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -161,7 +161,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.
+ # For example, to share cookies between user1.lvh.me and user2.lvh.me, set <tt>:tld_length</tt> to 2.
# * <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+.
@@ -338,6 +338,9 @@ module ActionDispatch
end
alias :has_key? :key?
+ # Returns the cookies as Hash.
+ alias :to_hash :to_h
+
def update(other_hash)
@cookies.update other_hash.stringify_keys
self
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 4f69abfa6f..d1b4508378 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -25,6 +25,7 @@ module ActionDispatch
"ActionView::MissingTemplate" => "missing_template",
"ActionController::RoutingError" => "routing_error",
"AbstractController::ActionNotFound" => "unknown_action",
+ "ActiveRecord::StatementInvalid" => "invalid_statement",
"ActionView::Template::Error" => "template_error"
)
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
index 805d3f2148..da2871b551 100644
--- a/actionpack/lib/action_dispatch/middleware/request_id.rb
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -30,7 +30,7 @@ module ActionDispatch
private
def make_request_id(request_id)
if request_id.presence
- request_id.gsub(/[^\w\-]/, "".freeze).first(255)
+ request_id.gsub(/[^\w\-@]/, "".freeze).first(255)
else
internal_request_id
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
new file mode 100644
index 0000000000..e1b129ccc5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb
@@ -0,0 +1,21 @@
+<header>
+ <h1>
+ <%= @exception.class.to_s %>
+ <% if @request.parameters['controller'] %>
+ in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
+ <% end %>
+ </h1>
+</header>
+
+<div id="container">
+ <h2>
+ <%= h @exception.message %>
+ <% if @exception.message.match? %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}} %>
+ <br />To resolve this issue run: bin/rails active_storage:install
+ <% end %>
+ </h2>
+
+ <%= render template: "rescues/_source" %>
+ <%= render template: "rescues/_trace" %>
+ <%= render template: "rescues/_request_and_response" %>
+</div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb
new file mode 100644
index 0000000000..033518cf8a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb
@@ -0,0 +1,13 @@
+<%= @exception.class.to_s %><%
+ if @request.parameters['controller']
+%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
+<% end %>
+
+<%= @exception.message %>
+<% if @exception.message.match? %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}} %>
+To resolve this issue run: bin/rails active_storage:install
+<% end %>
+
+<%= render template: "rescues/_source" %>
+<%= render template: "rescues/_trace" %>
+<%= render template: "rescues/_request_and_response" %>
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 855f2ffa47..eb6fbca6ba 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -26,7 +26,10 @@ module ActionDispatch
config.action_dispatch.default_headers = {
"X-Frame-Options" => "SAMEORIGIN",
"X-XSS-Protection" => "1; mode=block",
- "X-Content-Type-Options" => "nosniff"
+ "X-Content-Type-Options" => "nosniff",
+ "X-Download-Options" => "noopen",
+ "X-Permitted-Cross-Domain-Policies" => "none",
+ "Referrer-Policy" => "strict-origin-when-cross-origin"
}
config.action_dispatch.cookies_rotations = ActiveSupport::Messages::RotationConfiguration.new
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index d86d0b10c2..000847e193 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -130,6 +130,7 @@ module ActionDispatch
load_for_read!
@delegate.dup.delete_if { |_, v| v.nil? }
end
+ alias :to_h :to_hash
# Updates the session with given Hash.
#
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index ded42adee9..31eb6104fe 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1573,7 +1573,7 @@ module ActionDispatch
# Matches a URL pattern to one or more routes.
# For more information, see match[rdoc-ref:Base#match].
#
- # match 'path' => 'controller#action', via: patch
+ # match 'path' => 'controller#action', via: :patch
# match 'path', to: 'controller#action', via: :post
# match 'path', 'otherpath', on: :member, via: :get
def match(path, *rest, &block)
@@ -2046,7 +2046,7 @@ module ActionDispatch
end
module CustomUrls
- # Define custom url helpers that will be added to the application's
+ # Define custom URL helpers that will be added to the application's
# routes. This allows you to override and/or replace the default behavior
# of routing helpers, e.g:
#
@@ -2066,11 +2066,11 @@ module ActionDispatch
# arguments for +url_for+ which will actually build the URL string. This can
# be one of the following:
#
- # * A string, which is treated as a generated URL
- # * A hash, e.g. { controller: "pages", action: "index" }
- # * An array, which is passed to `polymorphic_url`
- # * An Active Model instance
- # * An Active Model class
+ # * A string, which is treated as a generated URL
+ # * A hash, e.g. <tt>{ controller: "pages", action: "index" }</tt>
+ # * An array, which is passed to +polymorphic_url+
+ # * An Active Model instance
+ # * An Active Model class
#
# NOTE: Other URL helpers can be called in the block but be careful not to invoke
# your custom URL helper again otherwise it will result in a stack overflow error.
@@ -2082,9 +2082,9 @@ module ActionDispatch
# [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
# end
#
- # In this instance the +params+ object comes from the context in which the the
+ # In this instance the +params+ object comes from the context in which the
# block is executed, e.g. generating a URL inside a controller action or a view.
- # If the block is executed where there isn't a params object such as this:
+ # If the block is executed where there isn't a +params+ object such as this:
#
# Rails.application.routes.url_helpers.browse_path
#
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 987e709f6f..ff6998ae31 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -2,7 +2,6 @@
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"
@@ -199,6 +198,16 @@ module ActionDispatch
if args.size == arg_size && !inner_options && optimize_routes_generation?(t)
options = t.url_options.merge @options
options[:path] = optimized_helper(args)
+
+ original_script_name = options.delete(:original_script_name)
+ script_name = t._routes.find_script_name(options)
+
+ if original_script_name
+ script_name = original_script_name + script_name
+ end
+
+ options[:script_name] = script_name
+
url_strategy.call options
else
super
@@ -845,7 +854,7 @@ module ActionDispatch
recognize_path_with_request(req, path, extras)
end
- def recognize_path_with_request(req, path, extras)
+ def recognize_path_with_request(req, path, extras, raise_on_missing: true)
@router.recognize(req) do |route, params|
params.merge!(extras)
params.each do |key, value|
@@ -865,12 +874,14 @@ module ActionDispatch
return req.path_parameters
elsif app.matches?(req) && app.engine?
- path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras)
- return path_parameters
+ path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras, raise_on_missing: false)
+ return path_parameters if path_parameters
end
end
- raise ActionController::RoutingError, "No route matches #{path.inspect}"
+ if raise_on_missing
+ raise ActionController::RoutingError, "No route matches #{path.inspect}"
+ end
end
end
# :startdoc:
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 3ae533dd37..fa345dccdf 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -155,7 +155,7 @@ module ActionDispatch
# Missing routes keys may be filled in from the current request's parameters
# (e.g. +:controller+, +:action+, +:id+ and any other parameters that are
# placed in the path). Given that the current action has been reached
- # through `GET /users/1`:
+ # through <tt>GET /users/1</tt>:
#
# url_for(only_path: true) # => '/users/1'
# url_for(only_path: true, action: 'edit') # => '/users/1/edit'
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index 7246e01cff..f85f816bb9 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -6,6 +6,7 @@ require "capybara/dsl"
require "capybara/minitest"
require "action_controller"
require "action_dispatch/system_testing/driver"
+require "action_dispatch/system_testing/browser"
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"
@@ -69,6 +70,9 @@ module ActionDispatch
# size of the browser screen. These two options are not applicable for
# headless drivers and will be silently ignored if passed.
#
+ # Headless browsers such as headless Chrome and headless Firefox are also supported.
+ # You can use these browsers by setting the +:using+ argument to +:headless_chrome+ or +:headless_firefox+.
+ #
# To use a headless driver, like Poltergeist, update your Gemfile to use
# Poltergeist instead of Selenium and then declare the driver name in the
# +application_system_test_case.rb+ file. In this case, you would leave out
@@ -121,11 +125,15 @@ module ActionDispatch
#
# driven_by :poltergeist
#
- # driven_by :selenium, using: :firefox
+ # driven_by :selenium, screen_size: [800, 800]
+ #
+ # driven_by :selenium, using: :chrome
#
# driven_by :selenium, using: :headless_chrome
#
- # driven_by :selenium, screen_size: [800, 800]
+ # driven_by :selenium, using: :firefox
+ #
+ # driven_by :selenium, using: :headless_firefox
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)
end
diff --git a/actionpack/lib/action_dispatch/system_testing/browser.rb b/actionpack/lib/action_dispatch/system_testing/browser.rb
new file mode 100644
index 0000000000..10e6888ab3
--- /dev/null
+++ b/actionpack/lib/action_dispatch/system_testing/browser.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module ActionDispatch
+ module SystemTesting
+ class Browser # :nodoc:
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def type
+ case name
+ when :headless_chrome
+ :chrome
+ when :headless_firefox
+ :firefox
+ else
+ name
+ end
+ end
+
+ def options
+ case name
+ when :headless_chrome
+ headless_chrome_browser_options
+ when :headless_firefox
+ headless_firefox_browser_options
+ end
+ end
+
+ private
+ def headless_chrome_browser_options
+ options = Selenium::WebDriver::Chrome::Options.new
+ options.args << "--headless"
+ options.args << "--disable-gpu"
+
+ options
+ end
+
+ def headless_firefox_browser_options
+ options = Selenium::WebDriver::Firefox::Options.new
+ options.args << "-headless"
+
+ options
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb
index 2687772b4b..5252ff6746 100644
--- a/actionpack/lib/action_dispatch/system_testing/driver.rb
+++ b/actionpack/lib/action_dispatch/system_testing/driver.rb
@@ -5,7 +5,7 @@ module ActionDispatch
class Driver # :nodoc:
def initialize(name, **options)
@name = name
- @browser = options[:using]
+ @browser = Browser.new(options[:using])
@screen_size = options[:screen_size]
@options = options[:options]
end
@@ -32,23 +32,11 @@ module ActionDispatch
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
+ @options.merge(options: @browser.options).compact
end
def register_selenium(app)
- Capybara::Selenium::Driver.new(app, { browser: browser }.merge(browser_options)).tap do |driver|
+ Capybara::Selenium::Driver.new(app, { browser: @browser.type }.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 8f1b6725b1..4fc1f33767 100644
--- a/actionpack/lib/action_dispatch/system_testing/server.rb
+++ b/actionpack/lib/action_dispatch/system_testing/server.rb
@@ -20,7 +20,7 @@ module ActionDispatch
end
def set_server
- Capybara.server = :puma, { Silent: self.class.silence_puma }
+ Capybara.server = :puma, { Silent: self.class.silence_puma } if Capybara.server == Capybara.servers[:default]
end
def set_port
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
index 6c337cdc31..df0c5d3f0e 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
@@ -15,12 +15,11 @@ module ActionDispatch
#
# You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to
# control the output. Possible values are:
- # * [+inline+ (default)] display the screenshot in the terminal using the
+ # * [+simple+ (default)] Only displays the screenshot path.
+ # This is the default value.
+ # * [+inline+] Display the screenshot in the terminal using the
# iTerm image protocol (https://iterm2.com/documentation-images.html).
- # * [+simple+] only display the screenshot path.
- # This is the default value if the +CI+ environment variables
- # is defined.
- # * [+artifact+] display the screenshot in the terminal, using the terminal
+ # * [+artifact+] Display the screenshot in the terminal, using the terminal
# artifact format (https://buildkite.github.io/terminal/inline-images/).
def take_screenshot
save_image
@@ -59,11 +58,8 @@ module ActionDispatch
# Environment variables have priority
output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] || ENV["CAPYBARA_INLINE_SCREENSHOT"]
- # If running in a CI environment, default to simple
- output_type ||= "simple" if ENV["CI"]
-
- # Default
- output_type ||= "inline"
+ # Default to outputting a path to the screenshot
+ output_type ||= "simple"
output_type
end
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index 95fdd3affb..3f69109633 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2017 David Heinemeier Hansson
+# Copyright (c) 2004-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb
index 28bc153f4d..37969fcb57 100644
--- a/actionpack/lib/action_pack/gem_version.rb
+++ b/actionpack/lib/action_pack/gem_version.rb
@@ -7,8 +7,8 @@ module ActionPack
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 5262e85a28..f4787ed27a 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -380,10 +380,8 @@ class ForkingExecutor
def initialize(size)
@size = size
@queue = Server.new
- file = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname("rails-tests", "fd")
- @url = "drbunix://#{file}"
@pool = nil
- DRb.start_service @url, @queue
+ @url = DRb.start_service("drbunix:", @queue).uri
end
def <<(work); @queue << work; end
@@ -453,3 +451,7 @@ end
class DrivenBySeleniumWithHeadlessChrome < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_chrome
end
+
+class DrivenBySeleniumWithHeadlessFirefox < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :headless_firefox
+end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index f9a037e3cc..504c77b8ef 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -301,18 +301,18 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_empty_flash
process :flash_me_naked
- assert flash.empty?
+ assert_empty flash
end
def test_flash_exist
process :flash_me
- assert flash.any?
- assert flash["hello"].present?
+ assert_predicate flash, :any?
+ assert_predicate flash["hello"], :present?
end
def test_flash_does_not_exist
process :nothing
- assert flash.empty?
+ assert_empty flash
end
def test_session_exist
@@ -322,7 +322,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def session_does_not_exist
process :nothing
- assert session.empty?
+ assert_empty session
end
def test_redirection_location
@@ -343,46 +343,46 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_server_error_response_code
process :response500
- assert @response.server_error?
+ assert_predicate @response, :server_error?
process :response599
- assert @response.server_error?
+ assert_predicate @response, :server_error?
process :response404
- assert !@response.server_error?
+ assert_not_predicate @response, :server_error?
end
def test_missing_response_code
process :response404
- assert @response.not_found?
+ assert_predicate @response, :not_found?
end
def test_client_error_response_code
process :response404
- assert @response.client_error?
+ assert_predicate @response, :client_error?
end
def test_redirect_url_match
process :redirect_external
- assert @response.redirect?
+ assert_predicate @response, :redirect?
assert_match(/rubyonrails/, @response.redirect_url)
assert !/perloffrails/.match(@response.redirect_url)
end
def test_redirection
process :redirect_internal
- assert @response.redirect?
+ assert_predicate @response, :redirect?
process :redirect_external
- assert @response.redirect?
+ assert_predicate @response, :redirect?
process :nothing
- assert !@response.redirect?
+ assert_not_predicate @response, :redirect?
end
def test_successful_response_code
process :nothing
- assert @response.successful?
+ assert_predicate @response, :successful?
end
def test_response_object
diff --git a/actionpack/test/controller/api/conditional_get_test.rb b/actionpack/test/controller/api/conditional_get_test.rb
index fd1997f26c..e366ce9532 100644
--- a/actionpack/test/controller/api/conditional_get_test.rb
+++ b/actionpack/test/controller/api/conditional_get_test.rb
@@ -53,7 +53,7 @@ class ConditionalGetApiTest < ActionController::TestCase
@request.if_modified_since = @last_modified
get :one
assert_equal 304, @response.status.to_i
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_equal @last_modified, @response.headers["Last-Modified"]
end
end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 9ac82c0d65..a672ede1a9 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -107,9 +107,9 @@ class ControllerInstanceTests < ActiveSupport::TestCase
end
def test_performed?
- assert !@empty.performed?
+ assert_not_predicate @empty, :performed?
@empty.response_body = ["sweet"]
- assert @empty.performed?
+ assert_predicate @empty, :performed?
end
def test_action_methods
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 3557f9f888..8b596083d5 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -159,7 +159,7 @@ class FragmentCachingTest < ActionController::TestCase
html_safe = @controller.read_fragment("name")
assert_equal content, html_safe
- assert html_safe.html_safe?
+ assert_predicate html_safe, :html_safe?
end
end
@@ -382,7 +382,7 @@ class ViewCacheDependencyTest < ActionController::TestCase
end
def test_view_cache_dependencies_are_empty_by_default
- assert NoDependenciesController.new.view_cache_dependencies.empty?
+ assert_empty NoDependenciesController.new.view_cache_dependencies
end
def test_view_cache_dependencies_are_listed_in_declaration_order
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index 9f0a9dec7a..2b16a555bb 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -819,7 +819,7 @@ class FilterTest < ActionController::TestCase
response = test_process(RescuedController)
end
- assert response.successful?
+ assert_predicate response, :successful?
assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)
end
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
index f31a4d9329..6c3ac26de1 100644
--- a/actionpack/test/controller/flash_hash_test.rb
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -92,11 +92,11 @@ module ActionDispatch
end
def test_empty?
- assert @hash.empty?
+ assert_empty @hash
@hash["zomg"] = "bears"
- assert !@hash.empty?
+ assert_not_empty @hash
@hash.clear
- assert @hash.empty?
+ assert_empty @hash
end
def test_each
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index fd1c5e693f..a685f5868e 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -14,11 +14,11 @@ class SessionTest < ActiveSupport::TestCase
end
def test_https_bang_works_and_sets_truth_by_default
- assert !@session.https?
+ assert_not_predicate @session, :https?
@session.https!
- assert @session.https?
+ assert_predicate @session, :https?
@session.https! false
- assert !@session.https?
+ assert_not_predicate @session, :https?
end
def test_host!
@@ -412,11 +412,11 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
get "/get_with_params", params: { foo: "bar" }
- assert request.env["rack.input"].string.empty?
+ assert_empty request.env["rack.input"].string
assert_equal "foo=bar", request.env["QUERY_STRING"]
assert_equal "foo=bar", request.query_string
assert_equal "bar", request.parameters["foo"]
- assert request.parameters["leaks"].nil?
+ assert_predicate request.parameters["leaks"], :nil?
end
end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index 8cfb43a6bc..431fe90b23 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -464,7 +464,7 @@ module ActionController
end
def test_stale_with_etag
- @request.if_none_match = %(W/"#{Digest::MD5.hexdigest('123')}")
+ @request.if_none_match = %(W/"#{ActiveSupport::Digest.hexdigest('123')}")
get :with_stale
assert_equal 304, response.status.to_i
end
diff --git a/actionpack/test/controller/metal_test.rb b/actionpack/test/controller/metal_test.rb
index c235c9df86..248ef36b7c 100644
--- a/actionpack/test/controller/metal_test.rb
+++ b/actionpack/test/controller/metal_test.rb
@@ -9,7 +9,7 @@ class MetalControllerInstanceTests < ActiveSupport::TestCase
end
end
- def test_response_has_default_headers
+ def test_response_does_not_have_default_headers
original_default_headers = ActionDispatch::Response.default_headers
ActionDispatch::Response.default_headers = {
@@ -23,9 +23,9 @@ class MetalControllerInstanceTests < ActiveSupport::TestCase
"rack.input" => -> {}
)[1]
- refute response_headers.key?("X-Frame-Options")
- refute response_headers.key?("X-Content-Type-Options")
- refute response_headers.key?("X-XSS-Protection")
+ assert_not response_headers.key?("X-Frame-Options")
+ assert_not response_headers.key?("X-Content-Type-Options")
+ assert_not response_headers.key?("X-XSS-Protection")
ensure
ActionDispatch::Response.default_headers = original_default_headers
end
diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb
index e33a99068f..d683bc73e6 100644
--- a/actionpack/test/controller/output_escaping_test.rb
+++ b/actionpack/test/controller/output_escaping_test.rb
@@ -4,7 +4,7 @@ require "abstract_unit"
class OutputEscapingTest < ActiveSupport::TestCase
test "escape_html shouldn't die when passed nil" do
- assert ERB::Util.h(nil).blank?
+ assert_predicate ERB::Util.h(nil), :blank?
end
test "escapeHTML should escape strings" do
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index 43cabae7d2..7bcf8c380d 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -22,13 +22,13 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
test "[] retains permitted status" do
@params.permit!
- assert @params[:person].permitted?
- assert @params[:person][:name].permitted?
+ assert_predicate @params[:person], :permitted?
+ assert_predicate @params[:person][:name], :permitted?
end
test "[] retains unpermitted status" do
- assert_not @params[:person].permitted?
- assert_not @params[:person][:name].permitted?
+ assert_not_predicate @params[:person], :permitted?
+ assert_not_predicate @params[:person][:name], :permitted?
end
test "as_json returns the JSON representation of the parameters hash" do
@@ -51,6 +51,14 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
@params.each { |key, value| assert_not(value.permitted?) if key == "person" }
end
+ test "each returns key,value array for block with arity 1" do
+ @params.each do |arg|
+ assert_kind_of Array, arg
+ assert_equal "person", arg[0]
+ assert_kind_of ActionController::Parameters, arg[1]
+ end
+ end
+
test "each_pair carries permitted status" do
@params.permit!
@params.each_pair { |key, value| assert(value.permitted?) if key == "person" }
@@ -60,35 +68,43 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
@params.each_pair { |key, value| assert_not(value.permitted?) if key == "person" }
end
+ test "each_pair returns key,value array for block with arity 1" do
+ @params.each_pair do |arg|
+ assert_kind_of Array, arg
+ assert_equal "person", arg[0]
+ assert_kind_of ActionController::Parameters, arg[1]
+ end
+ end
+
test "empty? returns true when params contains no key/value pairs" do
params = ActionController::Parameters.new
- assert params.empty?
+ assert_empty params
end
test "empty? returns false when any params are present" do
- refute @params.empty?
+ assert_not_empty @params
end
test "except retains permitted status" do
@params.permit!
- assert @params.except(:person).permitted?
- assert @params[:person].except(:name).permitted?
+ assert_predicate @params.except(:person), :permitted?
+ assert_predicate @params[:person].except(:name), :permitted?
end
test "except retains unpermitted status" do
- assert_not @params.except(:person).permitted?
- assert_not @params[:person].except(:name).permitted?
+ assert_not_predicate @params.except(:person), :permitted?
+ assert_not_predicate @params[:person].except(:name), :permitted?
end
test "fetch retains permitted status" do
@params.permit!
- assert @params.fetch(:person).permitted?
- assert @params[:person].fetch(:name).permitted?
+ assert_predicate @params.fetch(:person), :permitted?
+ assert_predicate @params[:person].fetch(:name), :permitted?
end
test "fetch retains unpermitted status" do
- assert_not @params.fetch(:person).permitted?
- assert_not @params[:person].fetch(:name).permitted?
+ assert_not_predicate @params.fetch(:person), :permitted?
+ assert_not_predicate @params[:person].fetch(:name), :permitted?
end
test "has_key? returns true if the given key is present in the params" do
@@ -96,7 +112,7 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
end
test "has_key? returns false if the given key is not present in the params" do
- refute @params.has_key?(:address)
+ assert_not @params.has_key?(:address)
end
test "has_value? returns true if the given value is present in the params" do
@@ -106,7 +122,7 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
test "has_value? returns false if the given value is not present in the params" do
params = ActionController::Parameters.new(city: "Chicago", state: "Illinois")
- refute params.has_value?("New York")
+ assert_not params.has_value?("New York")
end
test "include? returns true if the given key is present in the params" do
@@ -114,7 +130,7 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
end
test "include? returns false if the given key is not present in the params" do
- refute @params.include?(:address)
+ assert_not @params.include?(:address)
end
test "key? returns true if the given key is present in the params" do
@@ -122,7 +138,7 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
end
test "key? returns false if the given key is not present in the params" do
- refute @params.key?(:address)
+ assert_not @params.key?(:address)
end
test "keys returns an array of the keys of the params" do
@@ -131,48 +147,48 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
end
test "reject retains permitted status" do
- assert_not @params.reject { |k| k == "person" }.permitted?
+ assert_not_predicate @params.reject { |k| k == "person" }, :permitted?
end
test "reject retains unpermitted status" do
@params.permit!
- assert @params.reject { |k| k == "person" }.permitted?
+ assert_predicate @params.reject { |k| k == "person" }, :permitted?
end
test "select retains permitted status" do
@params.permit!
- assert @params.select { |k| k == "person" }.permitted?
+ assert_predicate @params.select { |k| k == "person" }, :permitted?
end
test "select retains unpermitted status" do
- assert_not @params.select { |k| k == "person" }.permitted?
+ assert_not_predicate @params.select { |k| k == "person" }, :permitted?
end
test "slice retains permitted status" do
@params.permit!
- assert @params.slice(:person).permitted?
+ assert_predicate @params.slice(:person), :permitted?
end
test "slice retains unpermitted status" do
- assert_not @params.slice(:person).permitted?
+ assert_not_predicate @params.slice(:person), :permitted?
end
test "transform_keys retains permitted status" do
@params.permit!
- assert @params.transform_keys { |k| k }.permitted?
+ assert_predicate @params.transform_keys { |k| k }, :permitted?
end
test "transform_keys retains unpermitted status" do
- assert_not @params.transform_keys { |k| k }.permitted?
+ assert_not_predicate @params.transform_keys { |k| k }, :permitted?
end
test "transform_values retains permitted status" do
@params.permit!
- assert @params.transform_values { |v| v }.permitted?
+ assert_predicate @params.transform_values { |v| v }, :permitted?
end
test "transform_values retains unpermitted status" do
- assert_not @params.transform_values { |v| v }.permitted?
+ assert_not_predicate @params.transform_values { |v| v }, :permitted?
end
test "value? returns true if the given value is present in the params" do
@@ -182,7 +198,7 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
test "value? returns false if the given value is not present in the params" do
params = ActionController::Parameters.new(city: "Chicago", state: "Illinois")
- refute params.value?("New York")
+ assert_not params.value?("New York")
end
test "values returns an array of the values of the params" do
@@ -192,13 +208,13 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
test "values_at retains permitted status" do
@params.permit!
- assert @params.values_at(:person).first.permitted?
- assert @params[:person].values_at(:name).first.permitted?
+ assert_predicate @params.values_at(:person).first, :permitted?
+ assert_predicate @params[:person].values_at(:name).first, :permitted?
end
test "values_at retains unpermitted status" do
- assert_not @params.values_at(:person).first.permitted?
- assert_not @params[:person].values_at(:name).first.permitted?
+ assert_not_predicate @params.values_at(:person).first, :permitted?
+ assert_not_predicate @params[:person].values_at(:name).first, :permitted?
end
test "is equal to Parameters instance with same params" do
@@ -273,7 +289,7 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
else
test "ActionController::Parameters does not respond to #dig on Ruby 2.2" do
assert_not ActionController::Parameters.method_defined?(:dig)
- assert_not @params.respond_to?(:dig)
+ assert_not_respond_to @params, :dig
end
end
end
diff --git a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
index 1e8b71d789..fe0e5e368d 100644
--- a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
+++ b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
@@ -25,6 +25,6 @@ class AlwaysPermittedParametersTest < ActiveSupport::TestCase
book: { pages: 65 },
format: "json")
permitted = params.permit book: [:pages]
- assert permitted.permitted?
+ assert_predicate permitted, :permitted?
end
end
diff --git a/actionpack/test/controller/parameters/dup_test.rb b/actionpack/test/controller/parameters/dup_test.rb
index f5833aff46..5403fc6d93 100644
--- a/actionpack/test/controller/parameters/dup_test.rb
+++ b/actionpack/test/controller/parameters/dup_test.rb
@@ -23,7 +23,7 @@ class ParametersDupTest < ActiveSupport::TestCase
test "a duplicate maintains the original's permitted status" do
@params.permit!
dupped_params = @params.dup
- assert dupped_params.permitted?
+ assert_predicate dupped_params, :permitted?
end
test "a duplicate maintains the original's parameters" do
@@ -57,11 +57,11 @@ class ParametersDupTest < ActiveSupport::TestCase
dupped_params = @params.deep_dup
dupped_params.permit!
- assert_not @params.permitted?
+ assert_not_predicate @params, :permitted?
end
test "deep_dup @permitted is being copied" do
@params.permit!
- assert @params.deep_dup.permitted?
+ assert_predicate @params.deep_dup, :permitted?
end
end
diff --git a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb
index dcf848a620..c890839727 100644
--- a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb
+++ b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb
@@ -21,7 +21,7 @@ class MultiParameterAttributesTest < ActiveSupport::TestCase
permitted = params.permit book: [ :shipped_at, :price ]
- assert permitted.permitted?
+ assert_predicate permitted, :permitted?
assert_equal "2012", permitted[:book]["shipped_at(1i)"]
assert_equal "3", permitted[:book]["shipped_at(2i)"]
diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb
index 49dede03c2..4d9fea7188 100644
--- a/actionpack/test/controller/parameters/mutators_test.rb
+++ b/actionpack/test/controller/parameters/mutators_test.rb
@@ -20,11 +20,11 @@ class ParametersMutatorsTest < ActiveSupport::TestCase
test "delete retains permitted status" do
@params.permit!
- assert @params.delete(:person).permitted?
+ assert_predicate @params.delete(:person), :permitted?
end
test "delete retains unpermitted status" do
- assert_not @params.delete(:person).permitted?
+ assert_not_predicate @params.delete(:person), :permitted?
end
test "delete returns the value when the key is present" do
@@ -50,73 +50,73 @@ class ParametersMutatorsTest < ActiveSupport::TestCase
test "delete_if retains permitted status" do
@params.permit!
- assert @params.delete_if { |k| k == "person" }.permitted?
+ assert_predicate @params.delete_if { |k| k == "person" }, :permitted?
end
test "delete_if retains unpermitted status" do
- assert_not @params.delete_if { |k| k == "person" }.permitted?
+ assert_not_predicate @params.delete_if { |k| k == "person" }, :permitted?
end
test "extract! retains permitted status" do
@params.permit!
- assert @params.extract!(:person).permitted?
+ assert_predicate @params.extract!(:person), :permitted?
end
test "extract! retains unpermitted status" do
- assert_not @params.extract!(:person).permitted?
+ assert_not_predicate @params.extract!(:person), :permitted?
end
test "keep_if retains permitted status" do
@params.permit!
- assert @params.keep_if { |k, v| k == "person" }.permitted?
+ assert_predicate @params.keep_if { |k, v| k == "person" }, :permitted?
end
test "keep_if retains unpermitted status" do
- assert_not @params.keep_if { |k, v| k == "person" }.permitted?
+ assert_not_predicate @params.keep_if { |k, v| k == "person" }, :permitted?
end
test "reject! retains permitted status" do
@params.permit!
- assert @params.reject! { |k| k == "person" }.permitted?
+ assert_predicate @params.reject! { |k| k == "person" }, :permitted?
end
test "reject! retains unpermitted status" do
- assert_not @params.reject! { |k| k == "person" }.permitted?
+ assert_not_predicate @params.reject! { |k| k == "person" }, :permitted?
end
test "select! retains permitted status" do
@params.permit!
- assert @params.select! { |k| k != "person" }.permitted?
+ assert_predicate @params.select! { |k| k != "person" }, :permitted?
end
test "select! retains unpermitted status" do
- assert_not @params.select! { |k| k != "person" }.permitted?
+ assert_not_predicate @params.select! { |k| k != "person" }, :permitted?
end
test "slice! retains permitted status" do
@params.permit!
- assert @params.slice!(:person).permitted?
+ assert_predicate @params.slice!(:person), :permitted?
end
test "slice! retains unpermitted status" do
- assert_not @params.slice!(:person).permitted?
+ assert_not_predicate @params.slice!(:person), :permitted?
end
test "transform_keys! retains permitted status" do
@params.permit!
- assert @params.transform_keys! { |k| k }.permitted?
+ assert_predicate @params.transform_keys! { |k| k }, :permitted?
end
test "transform_keys! retains unpermitted status" do
- assert_not @params.transform_keys! { |k| k }.permitted?
+ assert_not_predicate @params.transform_keys! { |k| k }, :permitted?
end
test "transform_values! retains permitted status" do
@params.permit!
- assert @params.transform_values! { |v| v }.permitted?
+ assert_predicate @params.transform_values! { |v| v }, :permitted?
end
test "transform_values! retains unpermitted status" do
- assert_not @params.transform_values! { |v| v }.permitted?
+ assert_not_predicate @params.transform_values! { |v| v }, :permitted?
end
end
diff --git a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb
index c9fcc483ee..ccc6bf9807 100644
--- a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb
@@ -32,7 +32,7 @@ class NestedParametersPermitTest < ActiveSupport::TestCase
permitted = params.permit book: [ :title, { authors: [ :name ] }, { details: :pages }, :id ]
- assert permitted.permitted?
+ assert_predicate permitted, :permitted?
assert_equal "Romeo and Juliet", permitted[:book][:title]
assert_equal "William Shakespeare", permitted[:book][:authors][0][:name]
assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name]
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index ebdaca0162..295f3a03ef 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -53,13 +53,13 @@ class ParametersPermitTest < ActiveSupport::TestCase
test "if nothing is permitted, the hash becomes empty" do
params = ActionController::Parameters.new(id: "1234")
permitted = params.permit
- assert permitted.permitted?
- assert permitted.empty?
+ assert_predicate permitted, :permitted?
+ assert_empty permitted
end
test "key: permitted scalar values" do
values = ["a", :a, nil]
- values += [0, 1.0, 2**128, BigDecimal.new(1)]
+ values += [0, 1.0, 2**128, BigDecimal(1)]
values += [true, false]
values += [Date.today, Time.now, DateTime.now]
values += [STDOUT, StringIO.new, ActionDispatch::Http::UploadedFile.new(tempfile: __FILE__),
@@ -227,7 +227,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
test "hashes in array values get wrapped" do
params = ActionController::Parameters.new(foo: [{}, {}])
params[:foo].each do |hash|
- assert !hash.permitted?
+ assert_not_predicate hash, :permitted?
end
end
@@ -250,7 +250,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
permitted = params.permit(users: [:id])
permitted[:users] << { injected: 1 }
- assert_not permitted[:users].last.permitted?
+ assert_not_predicate permitted[:users].last, :permitted?
end
test "fetch doesnt raise ParameterMissing exception if there is a default" do
@@ -272,12 +272,12 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "not permitted is sticky beyond merges" do
- assert !@params.merge(a: "b").permitted?
+ assert_not_predicate @params.merge(a: "b"), :permitted?
end
test "permitted is sticky beyond merges" do
@params.permit!
- assert @params.merge(a: "b").permitted?
+ assert_predicate @params.merge(a: "b"), :permitted?
end
test "merge with parameters" do
@@ -288,12 +288,12 @@ class ParametersPermitTest < ActiveSupport::TestCase
end
test "not permitted is sticky beyond merge!" do
- assert_not @params.merge!(a: "b").permitted?
+ assert_not_predicate @params.merge!(a: "b"), :permitted?
end
test "permitted is sticky beyond merge!" do
@params.permit!
- assert @params.merge!(a: "b").permitted?
+ assert_predicate @params.merge!(a: "b"), :permitted?
end
test "merge! with parameters" do
@@ -355,10 +355,10 @@ class ParametersPermitTest < ActiveSupport::TestCase
test "permit is recursive" do
@params.permit!
- assert @params.permitted?
- assert @params[:person].permitted?
- assert @params[:person][:name].permitted?
- assert @params[:person][:addresses][0].permitted?
+ assert_predicate @params, :permitted?
+ assert_predicate @params[:person], :permitted?
+ assert_predicate @params[:person][:name], :permitted?
+ assert_predicate @params[:person][:addresses][0], :permitted?
end
test "permitted takes a default value when Parameters.permit_all_parameters is set" do
@@ -368,8 +368,8 @@ class ParametersPermitTest < ActiveSupport::TestCase
age: "32", name: { first: "David", last: "Heinemeier Hansson" }
})
- assert params.slice(:person).permitted?
- assert params[:person][:name].permitted?
+ assert_predicate params.slice(:person), :permitted?
+ assert_predicate params[:person][:name], :permitted?
ensure
ActionController::Parameters.permit_all_parameters = false
end
@@ -500,9 +500,9 @@ class ParametersPermitTest < ActiveSupport::TestCase
params = ActionController::Parameters.new(foo: "bar")
assert params.permit(:foo).has_key?(:foo)
- refute params.permit(foo: []).has_key?(:foo)
- refute params.permit(foo: [:bar]).has_key?(:foo)
- refute params.permit(foo: :bar).has_key?(:foo)
+ assert_not params.permit(foo: []).has_key?(:foo)
+ assert_not params.permit(foo: [:bar]).has_key?(:foo)
+ assert_not params.permit(foo: :bar).has_key?(:foo)
end
test "#permitted? is false by default" do
diff --git a/actionpack/test/controller/parameters/serialization_test.rb b/actionpack/test/controller/parameters/serialization_test.rb
index 823f01d82a..7809d0f357 100644
--- a/actionpack/test/controller/parameters/serialization_test.rb
+++ b/actionpack/test/controller/parameters/serialization_test.rb
@@ -27,7 +27,7 @@ class ParametersSerializationTest < ActiveSupport::TestCase
roundtripped = YAML.load(YAML.dump(params))
assert_equal params, roundtripped
- assert_not roundtripped.permitted?
+ assert_not_predicate roundtripped, :permitted?
end
test "yaml backwardscompatible with psych 2.0.8 format" do
@@ -37,7 +37,7 @@ class ParametersSerializationTest < ActiveSupport::TestCase
end_of_yaml
assert_equal :value, params[:key]
- assert_not params.permitted?
+ assert_not_predicate params, :permitted?
end
test "yaml backwardscompatible with psych 2.0.9+ format" do
@@ -50,6 +50,6 @@ class ParametersSerializationTest < ActiveSupport::TestCase
end_of_yaml
assert_equal :value, params[:key]
- assert_not params.permitted?
+ assert_not_predicate params, :permitted?
end
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 37a62edc15..fc21543049 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -415,7 +415,7 @@ class LastModifiedRenderTest < ActionController::TestCase
@request.if_modified_since = @last_modified
get :conditional_hello
assert_equal 304, @response.status.to_i
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_equal @last_modified, @response.headers["Last-Modified"]
end
@@ -430,7 +430,7 @@ class LastModifiedRenderTest < ActionController::TestCase
@request.if_modified_since = "Thu, 16 Jul 2008 00:00:00 GMT"
get :conditional_hello
assert_equal 200, @response.status.to_i
- assert @response.body.present?
+ assert_predicate @response.body, :present?
assert_equal @last_modified, @response.headers["Last-Modified"]
end
@@ -443,7 +443,7 @@ class LastModifiedRenderTest < ActionController::TestCase
@request.if_modified_since = @last_modified
get :conditional_hello_with_record
assert_equal 304, @response.status.to_i
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_not_nil @response.etag
assert_equal @last_modified, @response.headers["Last-Modified"]
end
@@ -459,7 +459,7 @@ class LastModifiedRenderTest < ActionController::TestCase
@request.if_modified_since = "Thu, 16 Jul 2008 00:00:00 GMT"
get :conditional_hello_with_record
assert_equal 200, @response.status.to_i
- assert @response.body.present?
+ assert_predicate @response.body, :present?
assert_equal @last_modified, @response.headers["Last-Modified"]
end
@@ -472,7 +472,7 @@ class LastModifiedRenderTest < ActionController::TestCase
@request.if_modified_since = @last_modified
get :conditional_hello_with_collection_of_records
assert_equal 304, @response.status.to_i
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_equal @last_modified, @response.headers["Last-Modified"]
end
@@ -487,7 +487,7 @@ class LastModifiedRenderTest < ActionController::TestCase
@request.if_modified_since = "Thu, 16 Jul 2008 00:00:00 GMT"
get :conditional_hello_with_collection_of_records
assert_equal 200, @response.status.to_i
- assert @response.body.present?
+ assert_predicate @response.body, :present?
assert_equal @last_modified, @response.headers["Last-Modified"]
end
@@ -592,7 +592,7 @@ class EtagRenderTest < ActionController::TestCase
end
def strong_etag(record)
- %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}")
+ %("#{ActiveSupport::Digest.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}")
end
end
@@ -682,27 +682,27 @@ class HeadRenderTest < ActionController::TestCase
def test_head_created
post :head_created
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_response :created
end
def test_head_created_with_application_json_content_type
post :head_created_with_application_json_content_type
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_equal "application/json", @response.header["Content-Type"]
assert_response :created
end
def test_head_ok_with_image_png_content_type
post :head_ok_with_image_png_content_type
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_equal "image/png", @response.header["Content-Type"]
assert_response :ok
end
def test_head_with_location_header
get :head_with_location_header
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_equal "/foo", @response.headers["Location"]
assert_response :ok
end
@@ -718,7 +718,7 @@ class HeadRenderTest < ActionController::TestCase
end
get :head_with_location_object
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
assert_response :ok
end
@@ -726,14 +726,14 @@ class HeadRenderTest < ActionController::TestCase
def test_head_with_custom_header
get :head_with_custom_header
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_equal "something", @response.headers["X-Custom-Header"]
assert_response :ok
end
def test_head_with_www_authenticate_header
get :head_with_www_authenticate_header
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
assert_equal "something", @response.headers["WWW-Authenticate"]
assert_response :ok
end
@@ -812,7 +812,7 @@ class HttpCacheForeverTest < ActionController::TestCase
assert_response :ok
assert_equal "max-age=#{100.years}, public", @response.headers["Cache-Control"]
assert_not_nil @response.etag
- assert @response.weak_etag?
+ assert_predicate @response, :weak_etag?
end
def test_cache_with_private
@@ -820,7 +820,7 @@ class HttpCacheForeverTest < ActionController::TestCase
assert_response :ok
assert_equal "max-age=#{100.years}, private", @response.headers["Cache-Control"]
assert_not_nil @response.etag
- assert @response.weak_etag?
+ assert_predicate @response, :weak_etag?
end
def test_cache_response_code_with_if_modified_since
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index eb3d2f34a8..7a02c27c99 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -446,6 +446,19 @@ module RequestForgeryProtectionTests
end
end
+ def test_should_raise_for_post_with_null_origin
+ forgery_protection_origin_check do
+ session[:_csrf_token] = @token
+ @controller.stub :form_authenticity_token, @token do
+ exception = assert_raises(ActionController::InvalidAuthenticityToken) do
+ @request.set_header "HTTP_ORIGIN", "null"
+ post :index, params: { custom_authenticity_token: @token }
+ end
+ assert_match "The browser returned a 'null' origin for a request", exception.message
+ end
+ end
+ end
+
def test_should_block_post_with_origin_checking_and_wrong_origin
old_logger = ActionController::Base.logger
logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
@@ -733,7 +746,7 @@ class FreeCookieControllerTest < ActionController::TestCase
test "should not emit a csrf-token meta tag" do
SecureRandom.stub :base64, @token do
get :meta
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
end
end
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 3d98237003..30bea64c55 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -307,7 +307,7 @@ class ResourcesTest < ActionController::TestCase
set.draw do
resources :messages do
member do
- match :mark , via: method
+ match :mark, via: method
match :unmark, via: method
end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index f09051b306..ec939e946a 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -213,7 +213,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal expected, ActiveSupport::JSON.decode(get(u))
end
- def test_regexp_precidence
+ def test_regexp_precedence
rs.draw do
get "/whois/:domain", constraints: {
domain: /\w+\.[\w\.]+/ },
@@ -1687,7 +1687,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_routes_with_symbols
set.draw do
get "unnamed", controller: :pages, action: :show, name: :as_symbol
- get "named" , controller: :pages, action: :show, name: :as_symbol, as: :named
+ get "named", controller: :pages, action: :show, name: :as_symbol, as: :named
end
assert_equal({ controller: "pages", action: "show", name: :as_symbol }, set.recognize_path("/unnamed"))
assert_equal({ controller: "pages", action: "show", name: :as_symbol }, set.recognize_path("/named"))
@@ -1893,7 +1893,7 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal({ controller: "blog", action: "show_date", year: "2006", month: "07", day: "28" }, controller.request.path_parameters)
assert_equal("/blog/2006/07/25", controller.url_for(day: 25, only_path: true))
assert_equal("/blog/2005", controller.url_for(year: 2005, only_path: true))
- assert_equal("/blog/show/123", controller.url_for(action: "show" , id: 123, only_path: true))
+ assert_equal("/blog/show/123", controller.url_for(action: "show", id: 123, only_path: true))
assert_equal("/blog/2006", controller.url_for(year: 2006, only_path: true))
assert_equal("/blog/2006", controller.url_for(year: 2006, month: nil, only_path: true))
end
diff --git a/actionpack/test/controller/runner_test.rb b/actionpack/test/controller/runner_test.rb
index a96c9c519b..1709ab5f6d 100644
--- a/actionpack/test/controller/runner_test.rb
+++ b/actionpack/test/controller/runner_test.rb
@@ -17,8 +17,8 @@ module ActionDispatch
def test_respond_to?
runner = MyRunner.new(Class.new { def x; end }.new)
- assert runner.respond_to?(:hi)
- assert runner.respond_to?(:x)
+ assert_respond_to runner, :hi
+ assert_respond_to runner, :x
end
end
end
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index fd2399e433..7b1a52b277 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -178,7 +178,7 @@ class SendFileTest < ActionController::TestCase
"image.jpg" => "image/jpeg",
"image.tif" => "image/tiff",
"image.gif" => "image/gif",
- "movie.mpg" => "video/mpeg",
+ "movie.mp4" => "video/mp4",
"file.zip" => "application/zip",
"file.unk" => "application/octet-stream",
"zip" => "application/octet-stream"
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 536c5ed97a..7d4850294d 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -670,7 +670,7 @@ XML
assert_equal "bar", @request.params[:foo]
post :no_op
- assert @request.params[:foo].blank?
+ assert_predicate @request.params[:foo], :blank?
end
def test_filtered_parameters_reset_between_requests
@@ -838,7 +838,7 @@ XML
def test_fixture_file_upload_should_be_able_access_to_tempfile
file = fixture_file_upload(FILES_DIR + "/ruby_on_rails.jpg", "image/jpg")
- assert file.respond_to?(:tempfile), "expected tempfile should respond on fixture file object, got nothing"
+ assert_respond_to file, :tempfile
end
def test_fixture_file_upload
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
index a7c7356921..a1521da702 100644
--- a/actionpack/test/controller/url_for_integration_test.rb
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -35,7 +35,6 @@ module ActionPack
as: "blog"
resources :people
- #match 'legacy/people' => "people#index", :legacy => "true"
get "symbols", controller: :symbols, action: :show, name: :as_symbol
get "id_default(/:id)" => "foo#id_default", :id => 1
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index cf11227897..e381abee36 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -288,7 +288,7 @@ module AbstractController
kls = Class.new { include set.url_helpers }
controller = kls.new
- assert controller.respond_to?(:home_url)
+ assert_respond_to controller, :home_url
assert_equal "http://www.basecamphq.com/home/sweet/home/again",
controller.send(:home_url, host: "www.basecamphq.com", user: "again")
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index 0f79c83b6d..ca83b850d5 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -19,7 +19,7 @@ class UrlRewriterTests < ActionController::TestCase
def setup
@params = {}
- @rewriter = Rewriter.new(@request) #.new(@request, @params)
+ @rewriter = Rewriter.new(@request)
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
ActiveSupport::Deprecation.silence do
diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb
new file mode 100644
index 0000000000..7c4a65a633
--- /dev/null
+++ b/actionpack/test/dispatch/content_security_policy_test.rb
@@ -0,0 +1,368 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class ContentSecurityPolicyTest < ActiveSupport::TestCase
+ def setup
+ @policy = ActionDispatch::ContentSecurityPolicy.new
+ end
+
+ def test_build
+ assert_equal ";", @policy.build
+
+ @policy.script_src :self
+ assert_equal "script-src 'self';", @policy.build
+ end
+
+ def test_dup
+ @policy.img_src :self
+ @policy.block_all_mixed_content
+ @policy.upgrade_insecure_requests
+ @policy.sandbox
+ copied = @policy.dup
+ assert_equal copied.build, @policy.build
+ end
+
+ def test_mappings
+ @policy.script_src :data
+ assert_equal "script-src data:;", @policy.build
+
+ @policy.script_src :mediastream
+ assert_equal "script-src mediastream:;", @policy.build
+
+ @policy.script_src :blob
+ assert_equal "script-src blob:;", @policy.build
+
+ @policy.script_src :filesystem
+ assert_equal "script-src filesystem:;", @policy.build
+
+ @policy.script_src :self
+ assert_equal "script-src 'self';", @policy.build
+
+ @policy.script_src :unsafe_inline
+ assert_equal "script-src 'unsafe-inline';", @policy.build
+
+ @policy.script_src :unsafe_eval
+ assert_equal "script-src 'unsafe-eval';", @policy.build
+
+ @policy.script_src :none
+ assert_equal "script-src 'none';", @policy.build
+
+ @policy.script_src :strict_dynamic
+ assert_equal "script-src 'strict-dynamic';", @policy.build
+
+ @policy.script_src :none, :report_sample
+ assert_equal "script-src 'none' 'report-sample';", @policy.build
+ end
+
+ def test_fetch_directives
+ @policy.child_src :self
+ assert_match %r{child-src 'self'}, @policy.build
+
+ @policy.child_src false
+ assert_no_match %r{child-src}, @policy.build
+
+ @policy.connect_src :self
+ assert_match %r{connect-src 'self'}, @policy.build
+
+ @policy.connect_src false
+ assert_no_match %r{connect-src}, @policy.build
+
+ @policy.default_src :self
+ assert_match %r{default-src 'self'}, @policy.build
+
+ @policy.default_src false
+ assert_no_match %r{default-src}, @policy.build
+
+ @policy.font_src :self
+ assert_match %r{font-src 'self'}, @policy.build
+
+ @policy.font_src false
+ assert_no_match %r{font-src}, @policy.build
+
+ @policy.frame_src :self
+ assert_match %r{frame-src 'self'}, @policy.build
+
+ @policy.frame_src false
+ assert_no_match %r{frame-src}, @policy.build
+
+ @policy.img_src :self
+ assert_match %r{img-src 'self'}, @policy.build
+
+ @policy.img_src false
+ assert_no_match %r{img-src}, @policy.build
+
+ @policy.manifest_src :self
+ assert_match %r{manifest-src 'self'}, @policy.build
+
+ @policy.manifest_src false
+ assert_no_match %r{manifest-src}, @policy.build
+
+ @policy.media_src :self
+ assert_match %r{media-src 'self'}, @policy.build
+
+ @policy.media_src false
+ assert_no_match %r{media-src}, @policy.build
+
+ @policy.object_src :self
+ assert_match %r{object-src 'self'}, @policy.build
+
+ @policy.object_src false
+ assert_no_match %r{object-src}, @policy.build
+
+ @policy.script_src :self
+ assert_match %r{script-src 'self'}, @policy.build
+
+ @policy.script_src false
+ assert_no_match %r{script-src}, @policy.build
+
+ @policy.style_src :self
+ assert_match %r{style-src 'self'}, @policy.build
+
+ @policy.style_src false
+ assert_no_match %r{style-src}, @policy.build
+
+ @policy.worker_src :self
+ assert_match %r{worker-src 'self'}, @policy.build
+
+ @policy.worker_src false
+ assert_no_match %r{worker-src}, @policy.build
+ end
+
+ def test_document_directives
+ @policy.base_uri "https://example.com"
+ assert_match %r{base-uri https://example\.com;}, @policy.build
+
+ @policy.plugin_types "application/x-shockwave-flash"
+ assert_match %r{plugin-types application/x-shockwave-flash;}, @policy.build
+
+ @policy.sandbox
+ assert_match %r{sandbox;}, @policy.build
+
+ @policy.sandbox "allow-scripts", "allow-modals"
+ assert_match %r{sandbox allow-scripts allow-modals;}, @policy.build
+
+ @policy.sandbox false
+ assert_no_match %r{sandbox}, @policy.build
+ end
+
+ def test_navigation_directives
+ @policy.form_action :self
+ assert_match %r{form-action 'self';}, @policy.build
+
+ @policy.frame_ancestors :self
+ assert_match %r{frame-ancestors 'self';}, @policy.build
+ end
+
+ def test_reporting_directives
+ @policy.report_uri "/violations"
+ assert_match %r{report-uri /violations;}, @policy.build
+ end
+
+ def test_other_directives
+ @policy.block_all_mixed_content
+ assert_match %r{block-all-mixed-content;}, @policy.build
+
+ @policy.block_all_mixed_content false
+ assert_no_match %r{block-all-mixed-content}, @policy.build
+
+ @policy.require_sri_for :script, :style
+ assert_match %r{require-sri-for script style;}, @policy.build
+
+ @policy.require_sri_for "script", "style"
+ assert_match %r{require-sri-for script style;}, @policy.build
+
+ @policy.require_sri_for
+ assert_no_match %r{require-sri-for}, @policy.build
+
+ @policy.upgrade_insecure_requests
+ assert_match %r{upgrade-insecure-requests;}, @policy.build
+
+ @policy.upgrade_insecure_requests false
+ assert_no_match %r{upgrade-insecure-requests}, @policy.build
+ end
+
+ def test_multiple_sources
+ @policy.script_src :self, :https
+ assert_equal "script-src 'self' https:;", @policy.build
+ end
+
+ def test_multiple_directives
+ @policy.script_src :self, :https
+ @policy.style_src :self, :https
+ assert_equal "script-src 'self' https:; style-src 'self' https:;", @policy.build
+ end
+
+ def test_dynamic_directives
+ request = Struct.new(:host).new("www.example.com")
+ controller = Struct.new(:request).new(request)
+
+ @policy.script_src -> { request.host }
+ assert_equal "script-src www.example.com;", @policy.build(controller)
+ end
+
+ def test_mixed_static_and_dynamic_directives
+ @policy.script_src :self, -> { "foo.com" }, "bar.com"
+ assert_equal "script-src 'self' foo.com bar.com;", @policy.build(Object.new)
+ end
+
+ def test_invalid_directive_source
+ exception = assert_raises(ArgumentError) do
+ @policy.script_src [:self]
+ end
+
+ assert_equal "Invalid content security policy source: [:self]", exception.message
+ end
+
+ def test_missing_context_for_dynamic_source
+ @policy.script_src -> { request.host }
+
+ exception = assert_raises(RuntimeError) do
+ @policy.build
+ end
+
+ assert_match %r{\AMissing context for the dynamic content security policy source:}, exception.message
+ end
+
+ def test_raises_runtime_error_when_unexpected_source
+ @policy.plugin_types [:flash]
+
+ exception = assert_raises(RuntimeError) do
+ @policy.build
+ end
+
+ assert_match %r{\AUnexpected content security policy source:}, exception.message
+ end
+end
+
+class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest
+ class PolicyController < ActionController::Base
+ content_security_policy only: :inline do |p|
+ p.default_src "https://example.com"
+ end
+
+ content_security_policy only: :conditional, if: :condition? do |p|
+ p.default_src "https://true.example.com"
+ end
+
+ content_security_policy only: :conditional, unless: :condition? do |p|
+ p.default_src "https://false.example.com"
+ end
+
+ content_security_policy only: :report_only do |p|
+ p.report_uri "/violations"
+ end
+
+ content_security_policy_report_only only: :report_only
+
+ def index
+ head :ok
+ end
+
+ def inline
+ head :ok
+ end
+
+ def conditional
+ head :ok
+ end
+
+ def report_only
+ head :ok
+ end
+
+ private
+ def condition?
+ params[:condition] == "true"
+ end
+ end
+
+ ROUTES = ActionDispatch::Routing::RouteSet.new
+ ROUTES.draw do
+ scope module: "content_security_policy_integration_test" do
+ get "/", to: "policy#index"
+ get "/inline", to: "policy#inline"
+ get "/conditional", to: "policy#conditional"
+ get "/report-only", to: "policy#report_only"
+ end
+ end
+
+ POLICY = ActionDispatch::ContentSecurityPolicy.new do |p|
+ p.default_src :self
+ end
+
+ class PolicyConfigMiddleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env["action_dispatch.content_security_policy"] = POLICY
+ env["action_dispatch.content_security_policy_report_only"] = false
+ env["action_dispatch.show_exceptions"] = false
+
+ @app.call(env)
+ end
+ end
+
+ APP = build_app(ROUTES) do |middleware|
+ middleware.use PolicyConfigMiddleware
+ middleware.use ActionDispatch::ContentSecurityPolicy::Middleware
+ end
+
+ def app
+ APP
+ end
+
+ def test_generates_content_security_policy_header
+ get "/"
+ assert_policy "default-src 'self';"
+ end
+
+ def test_generates_inline_content_security_policy
+ get "/inline"
+ assert_policy "default-src https://example.com;"
+ end
+
+ def test_generates_conditional_content_security_policy
+ get "/conditional", params: { condition: "true" }
+ assert_policy "default-src https://true.example.com;"
+
+ get "/conditional", params: { condition: "false" }
+ assert_policy "default-src https://false.example.com;"
+ end
+
+ def test_generates_report_only_content_security_policy
+ get "/report-only"
+ assert_policy "default-src 'self'; report-uri /violations;", report_only: true
+ end
+
+ private
+
+ def env_config
+ Rails.application.env_config
+ end
+
+ def content_security_policy
+ env_config["action_dispatch.content_security_policy"]
+ end
+
+ def content_security_policy=(policy)
+ env_config["action_dispatch.content_security_policy"] = policy
+ end
+
+ def assert_policy(expected, report_only: false)
+ assert_response :success
+
+ if report_only
+ expected_header = "Content-Security-Policy-Report-Only"
+ unexpected_header = "Content-Security-Policy"
+ else
+ expected_header = "Content-Security-Policy"
+ unexpected_header = "Content-Security-Policy-Report-Only"
+ end
+
+ assert_nil response.headers[unexpected_header]
+ assert_equal expected, response.headers[expected_header]
+ end
+end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 40cbad3b0d..94cff10fe4 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -36,6 +36,12 @@ class CookieJarTest < ActiveSupport::TestCase
assert_equal "bar", request.cookie_jar.fetch(:foo)
end
+ def test_to_hash
+ request.cookie_jar["foo"] = "bar"
+ assert_equal({ "foo" => "bar" }, request.cookie_jar.to_hash)
+ assert_equal({ "foo" => "bar" }, request.cookie_jar.to_h)
+ end
+
def test_fetch_type_error
assert_raises(KeyError) do
request.cookie_jar.fetch(:omglolwut)
@@ -319,7 +325,7 @@ class CookiesTest < ActionController::TestCase
def test_setting_the_same_value_to_cookie
request.cookies[:user_name] = "david"
get :authenticate
- assert_predicate response.cookies, :empty?
+ assert_empty response.cookies
end
def test_setting_the_same_value_to_permanent_cookie
@@ -401,7 +407,7 @@ class CookiesTest < ActionController::TestCase
def test_delete_unexisting_cookie
request.cookies.clear
get :delete_cookie
- assert_predicate @response.cookies, :empty?
+ assert_empty @response.cookies
end
def test_deleted_cookie_predicate
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
index 2901148a9e..a9a56f205f 100644
--- a/actionpack/test/dispatch/live_response_test.rb
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -73,7 +73,7 @@ module ActionController
}
latch.wait
- assert @response.headers.frozen?
+ assert_predicate @response.headers, :frozen?
e = assert_raises(ActionDispatch::IllegalStateError) do
@response.headers["Content-Length"] = "zomg"
end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 90e95e972d..6167ea46df 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -30,21 +30,21 @@ class MimeTypeTest < ActiveSupport::TestCase
test "parse text with trailing star at the beginning" do
accept = "text/*, text/html, application/json, multipart/form-data"
- expect = [Mime[:html], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:xml], Mime[:yaml], Mime[:json], Mime[:multipart_form]]
+ expect = [Mime[:html], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:vtt], Mime[:xml], Mime[:yaml], Mime[:json], Mime[:multipart_form]]
parsed = Mime::Type.parse(accept)
- assert_equal expect, parsed
+ assert_equal expect.map(&:to_s), parsed.map(&:to_s)
end
test "parse text with trailing star in the end" do
accept = "text/html, application/json, multipart/form-data, text/*"
- expect = [Mime[:html], Mime[:json], Mime[:multipart_form], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:xml], Mime[:yaml]]
+ expect = [Mime[:html], Mime[:json], Mime[:multipart_form], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:vtt], Mime[:xml], Mime[:yaml]]
parsed = Mime::Type.parse(accept)
- assert_equal expect, parsed
+ assert_equal expect.map(&:to_s), parsed.map(&:to_s)
end
test "parse text with trailing star" do
accept = "text/*"
- expect = [Mime[:html], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:xml], Mime[:yaml], Mime[:json]]
+ expect = [Mime[:html], Mime[:text], Mime[:js], Mime[:css], Mime[:ics], Mime[:csv], Mime[:vcf], Mime[:vtt], Mime[:xml], Mime[:yaml], Mime[:json]]
parsed = Mime::Type.parse(accept)
assert_equal expect.map(&:to_s).sort!, parsed.map(&:to_s).sort!
end
@@ -159,7 +159,7 @@ class MimeTypeTest < ActiveSupport::TestCase
types.each do |type|
mime = Mime[type]
- assert mime.respond_to?("#{type}?"), "#{mime.inspect} does not respond to #{type}?"
+ assert_respond_to mime, "#{type}?"
assert_equal type, mime.symbol, "#{mime.inspect} is not #{type}?"
invalid_types = types - [type]
invalid_types.delete(:html)
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
index 7b6ce31f29..bf5a74e694 100644
--- a/actionpack/test/dispatch/request/session_test.rb
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -22,6 +22,7 @@ module ActionDispatch
s["foo"] = "bar"
assert_equal "bar", s["foo"]
assert_equal({ "foo" => "bar" }, s.to_hash)
+ assert_equal({ "foo" => "bar" }, s.to_h)
end
def test_create_merges_old
diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb
index aa3175c986..9df4712dab 100644
--- a/actionpack/test/dispatch/request_id_test.rb
+++ b/actionpack/test/dispatch/request_id_test.rb
@@ -11,6 +11,11 @@ class RequestIdTest < ActiveSupport::TestCase
assert_equal "X-Hacked-HeaderStuff", stub_request("HTTP_X_REQUEST_ID" => "; X-Hacked-Header: Stuff").request_id
end
+ test "accept Apache mod_unique_id format" do
+ mod_unique_id = "abcxyz@ABCXYZ-0123456789"
+ assert_equal mod_unique_id, stub_request("HTTP_X_REQUEST_ID" => mod_unique_id).request_id
+ end
+
test "ensure that 255 char limit on the request id is being enforced" do
assert_equal "X" * 255, stub_request("HTTP_X_REQUEST_ID" => "X" * 500).request_id
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 2a18395aac..84a2d1f69e 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -329,20 +329,20 @@ class RequestPort < BaseRequestTest
test "standard_port?" do
request = stub_request
- assert !request.ssl?
- assert request.standard_port?
+ assert_not_predicate request, :ssl?
+ assert_predicate request, :standard_port?
request = stub_request "HTTPS" => "on"
- assert request.ssl?
- assert request.standard_port?
+ assert_predicate request, :ssl?
+ assert_predicate request, :standard_port?
request = stub_request "HTTP_HOST" => "www.example.org:8080"
- assert !request.ssl?
- assert !request.standard_port?
+ assert_not_predicate request, :ssl?
+ assert_not_predicate request, :standard_port?
request = stub_request "HTTP_HOST" => "www.example.org:8443", "HTTPS" => "on"
- assert request.ssl?
- assert !request.standard_port?
+ assert_predicate request, :ssl?
+ assert_not_predicate request, :standard_port?
end
test "optional port" do
@@ -571,7 +571,7 @@ end
class LocalhostTest < BaseRequestTest
test "IPs that match localhost" do
request = stub_request("REMOTE_IP" => "127.1.1.1", "REMOTE_ADDR" => "127.1.1.1")
- assert request.local?
+ assert_predicate request, :local?
end
end
@@ -643,37 +643,37 @@ class RequestProtocol < BaseRequestTest
test "xml http request" do
request = stub_request
- assert !request.xml_http_request?
- assert !request.xhr?
+ assert_not_predicate request, :xml_http_request?
+ assert_not_predicate request, :xhr?
request = stub_request "HTTP_X_REQUESTED_WITH" => "DefinitelyNotAjax1.0"
- assert !request.xml_http_request?
- assert !request.xhr?
+ assert_not_predicate request, :xml_http_request?
+ assert_not_predicate request, :xhr?
request = stub_request "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"
- assert request.xml_http_request?
- assert request.xhr?
+ assert_predicate request, :xml_http_request?
+ assert_predicate request, :xhr?
end
test "reports ssl" do
- assert !stub_request.ssl?
- assert stub_request("HTTPS" => "on").ssl?
+ assert_not_predicate stub_request, :ssl?
+ assert_predicate stub_request("HTTPS" => "on"), :ssl?
end
test "reports ssl when proxied via lighttpd" do
- assert stub_request("HTTP_X_FORWARDED_PROTO" => "https").ssl?
+ assert_predicate stub_request("HTTP_X_FORWARDED_PROTO" => "https"), :ssl?
end
test "scheme returns https when proxied" do
request = stub_request "rack.url_scheme" => "http"
- assert !request.ssl?
+ assert_not_predicate request, :ssl?
assert_equal "http", request.scheme
request = stub_request(
"rack.url_scheme" => "http",
"HTTP_X_FORWARDED_PROTO" => "https"
)
- assert request.ssl?
+ assert_predicate request, :ssl?
assert_equal "https", request.scheme
end
end
@@ -700,7 +700,7 @@ class RequestMethod < BaseRequestTest
assert_equal "GET", request.request_method
assert_equal "GET", request.env["REQUEST_METHOD"]
- assert request.get?
+ assert_predicate request, :get?
end
test "invalid http method raises exception" do
@@ -748,7 +748,7 @@ class RequestMethod < BaseRequestTest
assert_equal "POST", request.method
assert_equal "PATCH", request.request_method
- assert request.patch?
+ assert_predicate request, :patch?
end
test "post masquerading as put" do
@@ -758,12 +758,12 @@ class RequestMethod < BaseRequestTest
)
assert_equal "POST", request.method
assert_equal "PUT", request.request_method
- assert request.put?
+ assert_predicate request, :put?
end
test "post uneffected by local inflections" do
existing_acronyms = ActiveSupport::Inflector.inflections.acronyms.dup
- existing_acronym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup
+ assert_deprecated { ActiveSupport::Inflector.inflections.acronym_regex.dup }
begin
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym "POS"
@@ -772,12 +772,12 @@ class RequestMethod < BaseRequestTest
request = stub_request "REQUEST_METHOD" => "POST"
assert_equal :post, ActionDispatch::Request::HTTP_METHOD_LOOKUP["POST"]
assert_equal :post, request.method_symbol
- assert request.post?
+ assert_predicate request, :post?
ensure
# Reset original acronym set
ActiveSupport::Inflector.inflections do |inflect|
inflect.send(:instance_variable_set, "@acronyms", existing_acronyms)
- inflect.send(:instance_variable_set, "@acronym_regex", existing_acronym_regex)
+ inflect.send(:define_acronym_regex_patterns)
end
end
end
@@ -785,50 +785,44 @@ end
class RequestFormat < BaseRequestTest
test "xml format" do
- request = stub_request
- assert_called(request, :parameters, times: 2, returns: { format: :xml }) do
- assert_equal Mime[:xml], request.format
- end
+ request = stub_request "QUERY_STRING" => "format=xml"
+
+ assert_equal Mime[:xml], request.format
end
test "xhtml format" do
- request = stub_request
- assert_called(request, :parameters, times: 2, returns: { format: :xhtml }) do
- assert_equal Mime[:html], request.format
- end
+ request = stub_request "QUERY_STRING" => "format=xhtml"
+
+ assert_equal Mime[:html], request.format
end
test "txt format" do
- request = stub_request
- assert_called(request, :parameters, times: 2, returns: { format: :txt }) do
- assert_equal Mime[:text], request.format
- end
+ request = stub_request "QUERY_STRING" => "format=txt"
+
+ assert_equal Mime[:text], request.format
end
test "XMLHttpRequest" do
request = stub_request(
"HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
- "HTTP_ACCEPT" => [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(",")
+ "HTTP_ACCEPT" => [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(","),
+ "QUERY_STRING" => ""
)
- assert_called(request, :parameters, times: 1, returns: {}) do
- assert request.xhr?
- assert_equal Mime[:js], request.format
- end
+ assert_predicate request, :xhr?
+ assert_equal Mime[:js], request.format
end
test "can override format with parameter negative" do
- request = stub_request
- assert_called(request, :parameters, times: 2, returns: { format: :txt }) do
- assert !request.format.xml?
- end
+ request = stub_request("QUERY_STRING" => "format=txt")
+
+ assert_not_predicate request.format, :xml?
end
test "can override format with parameter positive" do
- request = stub_request
- assert_called(request, :parameters, times: 2, returns: { format: :xml }) do
- assert request.format.xml?
- end
+ request = stub_request("QUERY_STRING" => "format=xml")
+
+ assert_predicate request.format, :xml?
end
test "formats text/html with accept header" do
@@ -853,40 +847,37 @@ class RequestFormat < BaseRequestTest
end
test "formats format:text with accept header" do
- request = stub_request
- assert_called(request, :parameters, times: 2, returns: { format: :txt }) do
- assert_equal [Mime[:text]], request.formats
- end
+ request = stub_request("QUERY_STRING" => "format=txt")
+
+ assert_equal [Mime[:text]], request.formats
end
test "formats format:unknown with accept header" do
- request = stub_request
- assert_called(request, :parameters, times: 2, returns: { format: :unknown }) do
- assert_instance_of Mime::NullType, request.format
- end
+ request = stub_request("QUERY_STRING" => "format=unknown")
+
+ assert_instance_of Mime::NullType, request.format
end
test "format is not nil with unknown format" do
- request = stub_request
- assert_called(request, :parameters, times: 2, returns: { format: :hello }) do
- assert request.format.nil?
- assert_not request.format.html?
- assert_not request.format.xml?
- assert_not request.format.json?
- end
+ request = stub_request("QUERY_STRING" => "format=hello")
+
+ assert_nil request.format
+ assert_not_predicate request.format, :html?
+ assert_not_predicate request.format, :xml?
+ assert_not_predicate request.format, :json?
end
test "format does not throw exceptions when malformed parameters" do
request = stub_request("QUERY_STRING" => "x[y]=1&x[y][][w]=2")
assert request.formats
- assert request.format.html?
+ assert_predicate request.format, :html?
end
test "formats with xhr request" do
- request = stub_request "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"
- assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [Mime[:js]], request.formats
- end
+ request = stub_request "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "QUERY_STRING" => ""
+
+ assert_equal [Mime[:js]], request.formats
end
test "ignore_accept_header" do
@@ -894,62 +885,58 @@ class RequestFormat < BaseRequestTest
ActionDispatch::Request.ignore_accept_header = true
begin
- request = stub_request "HTTP_ACCEPT" => "application/xml"
- assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime[:html] ], request.formats
- end
+ request = stub_request "HTTP_ACCEPT" => "application/xml",
+ "QUERY_STRING" => ""
- request = stub_request "HTTP_ACCEPT" => "koz-asked/something-crazy"
- assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime[:html] ], request.formats
- end
+ assert_equal [ Mime[:html] ], request.formats
- request = stub_request "HTTP_ACCEPT" => "*/*;q=0.1"
- assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime[:html] ], request.formats
- end
+ request = stub_request "HTTP_ACCEPT" => "koz-asked/something-crazy",
+ "QUERY_STRING" => ""
- request = stub_request "HTTP_ACCEPT" => "application/jxw"
- assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime[:html] ], request.formats
- end
+ assert_equal [ Mime[:html] ], request.formats
+
+ request = stub_request "HTTP_ACCEPT" => "*/*;q=0.1",
+ "QUERY_STRING" => ""
+
+ assert_equal [ Mime[:html] ], request.formats
+
+ request = stub_request "HTTP_ACCEPT" => "application/jxw",
+ "QUERY_STRING" => ""
+
+ assert_equal [ Mime[:html] ], request.formats
request = stub_request "HTTP_ACCEPT" => "application/xml",
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "QUERY_STRING" => ""
- assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [ Mime[:js] ], request.formats
- end
+ assert_equal [ Mime[:js] ], request.formats
request = stub_request "HTTP_ACCEPT" => "application/xml",
- "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"
- assert_called(request, :parameters, times: 2, returns: { format: :json }) do
- assert_equal [ Mime[:json] ], request.formats
- end
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "QUERY_STRING" => "format=json"
+
+ assert_equal [ Mime[:json] ], request.formats
ensure
ActionDispatch::Request.ignore_accept_header = old_ignore_accept_header
end
end
test "format taken from the path extension" do
- request = stub_request "PATH_INFO" => "/foo.xml"
- assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [Mime[:xml]], request.formats
- end
+ request = stub_request "PATH_INFO" => "/foo.xml", "QUERY_STRING" => ""
- request = stub_request "PATH_INFO" => "/foo.123"
- assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [Mime[:html]], request.formats
- end
+ assert_equal [Mime[:xml]], request.formats
+
+ request = stub_request "PATH_INFO" => "/foo.123", "QUERY_STRING" => ""
+
+ assert_equal [Mime[:html]], request.formats
end
test "formats from accept headers have higher precedence than path extension" do
request = stub_request "HTTP_ACCEPT" => "application/json",
- "PATH_INFO" => "/foo.xml"
+ "PATH_INFO" => "/foo.xml",
+ "QUERY_STRING" => ""
- assert_called(request, :parameters, times: 1, returns: {}) do
- assert_equal [Mime[:json]], request.formats
- end
+ assert_equal [Mime[:json]], request.formats
end
end
@@ -997,15 +984,14 @@ end
class RequestParameters < BaseRequestTest
test "parameters" do
- request = stub_request
+ request = stub_request "CONTENT_TYPE" => "application/json",
+ "CONTENT_LENGTH" => 9,
+ "RAW_POST_DATA" => '{"foo":1}',
+ "QUERY_STRING" => "bar=2"
- assert_called(request, :request_parameters, times: 2, returns: { "foo" => 1 }) do
- assert_called(request, :query_parameters, times: 2, returns: { "bar" => 2 }) do
- assert_equal({ "foo" => 1, "bar" => 2 }, request.parameters)
- assert_equal({ "foo" => 1 }, request.request_parameters)
- assert_equal({ "bar" => 2 }, request.query_parameters)
- end
- end
+ assert_equal({ "foo" => 1, "bar" => "2" }, request.parameters)
+ assert_equal({ "foo" => 1 }, request.request_parameters)
+ assert_equal({ "bar" => "2" }, request.query_parameters)
end
test "parameters not accessible after rack parse error" do
@@ -1248,8 +1234,8 @@ class RequestVariant < BaseRequestTest
test "setting variant to a symbol" do
@request.variant = :phone
- assert @request.variant.phone?
- assert_not @request.variant.tablet?
+ assert_predicate @request.variant, :phone?
+ assert_not_predicate @request.variant, :tablet?
assert @request.variant.any?(:phone, :tablet)
assert_not @request.variant.any?(:tablet, :desktop)
end
@@ -1257,9 +1243,9 @@ class RequestVariant < BaseRequestTest
test "setting variant to an array of symbols" do
@request.variant = [:phone, :tablet]
- assert @request.variant.phone?
- assert @request.variant.tablet?
- assert_not @request.variant.desktop?
+ assert_predicate @request.variant, :phone?
+ assert_predicate @request.variant, :tablet?
+ assert_not_predicate @request.variant, :desktop?
assert @request.variant.any?(:tablet, :desktop)
assert_not @request.variant.any?(:desktop, :watch)
end
@@ -1267,8 +1253,8 @@ class RequestVariant < BaseRequestTest
test "clearing variant" do
@request.variant = nil
- assert @request.variant.empty?
- assert_not @request.variant.phone?
+ assert_empty @request.variant
+ assert_not_predicate @request.variant, :phone?
assert_not @request.variant.any?(:phone, :tablet)
end
@@ -1287,13 +1273,13 @@ end
class RequestFormData < BaseRequestTest
test "media_type is from the FORM_DATA_MEDIA_TYPES array" do
- assert stub_request("CONTENT_TYPE" => "application/x-www-form-urlencoded").form_data?
- assert stub_request("CONTENT_TYPE" => "multipart/form-data").form_data?
+ assert_predicate stub_request("CONTENT_TYPE" => "application/x-www-form-urlencoded"), :form_data?
+ assert_predicate stub_request("CONTENT_TYPE" => "multipart/form-data"), :form_data?
end
test "media_type is not from the FORM_DATA_MEDIA_TYPES array" do
- assert !stub_request("CONTENT_TYPE" => "application/xml").form_data?
- assert !stub_request("CONTENT_TYPE" => "multipart/related").form_data?
+ assert_not_predicate stub_request("CONTENT_TYPE" => "application/xml"), :form_data?
+ assert_not_predicate stub_request("CONTENT_TYPE" => "multipart/related"), :form_data?
end
test "no Content-Type header is provided and the request_method is POST" do
@@ -1301,7 +1287,7 @@ class RequestFormData < BaseRequestTest
assert_equal "", request.media_type
assert_equal "POST", request.request_method
- assert !request.form_data?
+ assert_not_predicate request, :form_data?
end
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index c4ee3add2a..4c8d528507 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -15,13 +15,13 @@ class ResponseTest < ActiveSupport::TestCase
@response.await_commit
}
@response.commit!
- assert @response.committed?
+ assert_predicate @response, :committed?
assert t.join(0.5)
end
def test_stream_close
@response.stream.close
- assert @response.stream.closed?
+ assert_predicate @response.stream, :closed?
end
def test_stream_write
@@ -257,9 +257,9 @@ class ResponseTest < ActiveSupport::TestCase
}
resp.to_a
- assert resp.etag?
- assert resp.weak_etag?
- assert_not resp.strong_etag?
+ assert_predicate resp, :etag?
+ assert_predicate resp, :weak_etag?
+ assert_not_predicate resp, :strong_etag?
assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.etag)
assert_equal({ public: true }, resp.cache_control)
@@ -275,9 +275,9 @@ class ResponseTest < ActiveSupport::TestCase
}
resp.to_a
- assert resp.etag?
- assert_not resp.weak_etag?
- assert resp.strong_etag?
+ assert_predicate resp, :etag?
+ assert_not_predicate resp, :weak_etag?
+ assert_predicate resp, :strong_etag?
assert_equal('"202cb962ac59075b964b07152d234b70"', resp.etag)
end
@@ -311,13 +311,16 @@ class ResponseTest < ActiveSupport::TestCase
end
end
- test "read x_frame_options, x_content_type_options and x_xss_protection" do
+ test "read x_frame_options, x_content_type_options, x_xss_protection, x_download_options and x_permitted_cross_domain_policies, referrer_policy" do
original_default_headers = ActionDispatch::Response.default_headers
begin
ActionDispatch::Response.default_headers = {
"X-Frame-Options" => "DENY",
"X-Content-Type-Options" => "nosniff",
- "X-XSS-Protection" => "1;"
+ "X-XSS-Protection" => "1;",
+ "X-Download-Options" => "noopen",
+ "X-Permitted-Cross-Domain-Policies" => "none",
+ "Referrer-Policy" => "strict-origin-when-cross-origin"
}
resp = ActionDispatch::Response.create.tap { |response|
response.body = "Hello"
@@ -327,6 +330,9 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal("DENY", resp.headers["X-Frame-Options"])
assert_equal("nosniff", resp.headers["X-Content-Type-Options"])
assert_equal("1;", resp.headers["X-XSS-Protection"])
+ assert_equal("noopen", resp.headers["X-Download-Options"])
+ assert_equal("none", resp.headers["X-Permitted-Cross-Domain-Policies"])
+ assert_equal("strict-origin-when-cross-origin", resp.headers["Referrer-Policy"])
ensure
ActionDispatch::Response.default_headers = original_default_headers
end
@@ -350,7 +356,7 @@ class ResponseTest < ActiveSupport::TestCase
end
test "respond_to? accepts include_private" do
- assert_not @response.respond_to?(:method_missing)
+ assert_not_respond_to @response, :method_missing
assert @response.respond_to?(:method_missing, true)
end
diff --git a/actionpack/test/dispatch/routing_assertions_test.rb b/actionpack/test/dispatch/routing_assertions_test.rb
index a5198f2f13..009b6d9bc3 100644
--- a/actionpack/test/dispatch/routing_assertions_test.rb
+++ b/actionpack/test/dispatch/routing_assertions_test.rb
@@ -52,6 +52,8 @@ class RoutingAssertionsTest < ActionController::TestCase
end
mount engine => "/shelf"
+
+ get "/shelf/foo", controller: "query_articles", action: "index"
end
end
@@ -154,6 +156,10 @@ class RoutingAssertionsTest < ActionController::TestCase
assert_match err.message, "This is a really bad msg"
end
+ def test_assert_recognizes_continue_to_recoginize_after_it_tried_engines
+ assert_recognizes({ controller: "query_articles", action: "index" }, "/shelf/foo")
+ end
+
def test_assert_routing
assert_routing("/articles", controller: "articles", action: "index")
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 44f902c163..4222eb4eb7 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -3313,7 +3313,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
get "/search"
- assert !@request.params[:action].frozen?
+ assert_not_predicate @request.params[:action], :frozen?
end
def test_multiple_positional_args_with_the_same_name
@@ -4225,7 +4225,7 @@ class TestGlobRoutingMapper < ActionDispatch::IntegrationTest
end
end
- #include Routes.url_helpers
+ # include Routes.url_helpers
APP = build_app Routes
def app; APP end
@@ -4267,7 +4267,7 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
def app; APP end
test "enabled when not mounted and default_url_options is empty" do
- assert Routes.url_helpers.optimize_routes_generation?
+ assert_predicate Routes.url_helpers, :optimize_routes_generation?
end
test "named route called as singleton method" do
@@ -5057,3 +5057,40 @@ class TestRecognizePath < ActionDispatch::IntegrationTest
Routes.recognize_path(*args)
end
end
+
+class TestRelativeUrlRootGeneration < ActionDispatch::IntegrationTest
+ config = ActionDispatch::Routing::RouteSet::Config.new("/blog", false)
+
+ stub_controllers(config) do |routes|
+ Routes = routes
+
+ routes.draw do
+ get "/", to: "posts#index", as: :posts
+ get "/:id", to: "posts#show", as: :post
+ end
+ end
+
+ include Routes.url_helpers
+
+ APP = build_app Routes
+
+ def app
+ APP
+ end
+
+ def test_url_helpers
+ assert_equal "/blog/", posts_path({})
+ assert_equal "/blog/", Routes.url_helpers.posts_path({})
+
+ assert_equal "/blog/1", post_path(id: "1")
+ assert_equal "/blog/1", Routes.url_helpers.post_path(id: "1")
+ end
+
+ def test_optimized_url_helpers
+ assert_equal "/blog/", posts_path
+ assert_equal "/blog/", Routes.url_helpers.posts_path
+
+ assert_equal "/blog/1", post_path("1")
+ assert_equal "/blog/1", Routes.url_helpers.post_path("1")
+ end
+end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index cf51c47068..e34426a471 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -8,11 +8,14 @@ require "active_support/messages/rotation_configuration"
class CookieStoreTest < ActionDispatch::IntegrationTest
SessionKey = "_myapp_session"
SessionSecret = "b3c631c314c0bbca50c1b2843150fe33"
- Generator = ActiveSupport::LegacyKeyGenerator.new(SessionSecret)
+ SessionSalt = "authenticated encrypted cookie"
+
+ Generator = ActiveSupport::KeyGenerator.new(SessionSecret, iterations: 1000)
Rotations = ActiveSupport::Messages::RotationConfiguration.new
- Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, digest: "SHA1")
- SignedBar = Verifier.generate(foo: "bar", session_id: SecureRandom.hex(16))
+ Encryptor = ActiveSupport::MessageEncryptor.new(
+ Generator.generate_key(SessionSalt, 32), cipher: "aes-256-gcm", serializer: Marshal
+ )
class TestController < ActionController::Base
def no_session_access
@@ -25,12 +28,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def set_session_value
session[:foo] = "bar"
- render plain: Rack::Utils.escape(Verifier.generate(session.to_hash))
- end
-
- def set_session_value_expires_in_five_hours
- session[:foo] = "bar"
- render plain: Rack::Utils.escape(Verifier.generate(session.to_hash, expires_in: 5.hours))
+ render body: nil
end
def get_session_value
@@ -72,19 +70,35 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
end
end
+ def parse_cookie_from_header
+ cookie_matches = headers["Set-Cookie"].match(/#{SessionKey}=([^;]+)/)
+ cookie_matches && cookie_matches[1]
+ end
+
+ def assert_session_cookie(cookie_string, contents)
+ assert_includes headers["Set-Cookie"], cookie_string
+
+ session_value = parse_cookie_from_header
+ session_data = Encryptor.decrypt_and_verify(Rack::Utils.unescape(session_value)) rescue nil
+
+ assert_not_nil session_data, "session failed to decrypt"
+ assert_equal session_data.slice(*contents.keys), contents
+ end
+
def test_setting_session_value
with_test_route_set do
get "/set_session_value"
+
assert_response :success
- assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
- headers["Set-Cookie"]
+ assert_session_cookie "path=/; HttpOnly", "foo" => "bar"
end
end
def test_getting_session_value
with_test_route_set do
- cookies[SessionKey] = SignedBar
+ get "/set_session_value"
get "/get_session_value"
+
assert_response :success
assert_equal 'foo: "bar"', response.body
end
@@ -92,8 +106,9 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def test_getting_session_id
with_test_route_set do
- cookies[SessionKey] = SignedBar
+ get "/set_session_value"
get "/persistent_session_id"
+
assert_response :success
assert_equal 32, response.body.size
session_id = response.body
@@ -106,8 +121,12 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def test_disregards_tampered_sessions
with_test_route_set do
- cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--123456780"
+ encryptor = ActiveSupport::MessageEncryptor.new("A" * 32, cipher: "aes-256-gcm", serializer: Marshal)
+
+ cookies[SessionKey] = encryptor.encrypt_and_sign("foo" => "bar", "session_id" => "abc")
+
get "/get_session_value"
+
assert_response :success
assert_equal "foo: nil", response.body
end
@@ -135,19 +154,19 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def test_does_set_secure_cookies_over_https
with_test_route_set(secure: true) do
get "/set_session_value", headers: { "HTTPS" => "on" }
+
assert_response :success
- assert_equal "_myapp_session=#{response.body}; path=/; secure; HttpOnly",
- headers["Set-Cookie"]
+ assert_session_cookie "path=/; secure; HttpOnly", "foo" => "bar"
end
end
# {:foo=>#<SessionAutoloadTest::Foo bar:"baz">, :session_id=>"ce8b0752a6ab7c7af3cdb8a80e6b9e46"}
- SignedSerializedCookie = "BAh7BzoIZm9vbzodU2Vzc2lvbkF1dG9sb2FkVGVzdDo6Rm9vBjoJQGJhciIIYmF6Og9zZXNzaW9uX2lkIiVjZThiMDc1MmE2YWI3YzdhZjNjZGI4YTgwZTZiOWU0Ng==--2bf3af1ae8bd4e52b9ac2099258ace0c380e601c"
+ EncryptedSerializedCookie = "9RZ2Fij0qLveUwM4s+CCjGqhpjyUC8jiBIf/AiBr9M3TB8xh2vQZtvSOMfN3uf6oYbbpIDHAcOFIEl69FcW1ozQYeSrCLonYCazoh34ZdYskIQfGwCiSYleVXG1OD9Z4jFqeVArw4Ewm0paOOPLbN1rc6A==--I359v/KWdZ1ok0ey--JFFhuPOY7WUo6tB/eP05Aw=="
def test_deserializes_unloaded_classes_on_get_id
with_test_route_set do
with_autoload_path "session_autoload_test" do
- cookies[SessionKey] = SignedSerializedCookie
+ cookies[SessionKey] = EncryptedSerializedCookie
get "/get_session_id"
assert_response :success
assert_equal "id: ce8b0752a6ab7c7af3cdb8a80e6b9e46", response.body, "should auto-load unloaded class"
@@ -158,7 +177,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def test_deserializes_unloaded_classes_on_get_value
with_test_route_set do
with_autoload_path "session_autoload_test" do
- cookies[SessionKey] = SignedSerializedCookie
+ cookies[SessionKey] = EncryptedSerializedCookie
get "/get_session_value"
assert_response :success
assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
@@ -197,8 +216,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
get "/set_session_value"
assert_response :success
session_payload = response.body
- assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
- headers["Set-Cookie"]
+ assert_session_cookie "path=/; HttpOnly", "foo" => "bar"
get "/call_reset_session"
assert_response :success
@@ -216,8 +234,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
with_test_route_set do
get "/set_session_value"
assert_response :success
- assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
- headers["Set-Cookie"]
+ assert_session_cookie "path=/; HttpOnly", "foo" => "bar"
get "/get_class_after_reset_session"
assert_response :success
@@ -239,8 +256,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
with_test_route_set do
get "/set_session_value"
assert_response :success
- assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
- headers["Set-Cookie"]
+ assert_session_cookie "path=/; HttpOnly", "foo" => "bar"
get "/call_session_clear"
assert_response :success
@@ -253,7 +269,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def test_persistent_session_id
with_test_route_set do
- cookies[SessionKey] = SignedBar
+ get "/set_session_value"
get "/persistent_session_id"
assert_response :success
assert_equal 32, response.body.size
@@ -268,8 +284,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def test_setting_session_id_to_nil_is_respected
with_test_route_set do
- cookies[SessionKey] = SignedBar
-
+ get "/set_session_value"
get "/get_session_id"
sid = response.body
assert_equal 36, sid.size
@@ -283,31 +298,53 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
with_test_route_set(expire_after: 5.hours) do
# First request accesses the session
time = Time.local(2008, 4, 24)
- cookie_body = nil
Time.stub :now, time do
expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000")
- cookies[SessionKey] = SignedBar
+ get "/set_session_value"
- get "/set_session_value_expires_in_five_hours"
assert_response :success
-
- cookie_body = response.body
- assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly",
- headers["Set-Cookie"]
+ assert_session_cookie "path=/; expires=#{expected_expiry}; HttpOnly", "foo" => "bar"
end
# Second request does not access the session
- time = Time.local(2008, 4, 25)
+ time = time + 3.hours
Time.stub :now, time do
expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000")
get "/no_session_access"
+
+ assert_response :success
+ assert_session_cookie "path=/; expires=#{expected_expiry}; HttpOnly", "foo" => "bar"
+ end
+ end
+ end
+
+ def test_session_store_with_expire_after_does_not_accept_expired_session
+ with_test_route_set(expire_after: 5.hours) do
+ # First request accesses the session
+ time = Time.local(2017, 11, 12)
+
+ Time.stub :now, time do
+ expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000")
+
+ get "/set_session_value"
+ get "/get_session_value"
+
assert_response :success
+ assert_equal 'foo: "bar"', response.body
+ assert_session_cookie "path=/; expires=#{expected_expiry}; HttpOnly", "foo" => "bar"
+ end
+
+ # Second request is beyond the expiry time and the session is invalidated
+ time += 5.hours + 1.minute
- assert_equal "_myapp_session=#{cookies[SessionKey]}; path=/; expires=#{expected_expiry}; HttpOnly",
- headers["Set-Cookie"]
+ Time.stub :now, time do
+ get "/get_session_value"
+
+ assert_response :success
+ assert_equal "foo: nil", response.body
end
end
end
@@ -347,8 +384,14 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def get(path, *args)
args[0] ||= {}
args[0][:headers] ||= {}
- args[0][:headers]["action_dispatch.key_generator"] ||= Generator
- args[0][:headers]["action_dispatch.cookies_rotations"] ||= Rotations
+ args[0][:headers].tap do |config|
+ config["action_dispatch.secret_key_base"] = SessionSecret
+ config["action_dispatch.authenticated_encrypted_cookie_salt"] = SessionSalt
+ config["action_dispatch.use_authenticated_cookie_encryption"] = true
+
+ config["action_dispatch.key_generator"] ||= Generator
+ config["action_dispatch.cookies_rotations"] ||= Rotations
+ end
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 75feae6fe0..a824ee0c84 100644
--- a/actionpack/test/dispatch/system_testing/driver_test.rb
+++ b/actionpack/test/dispatch/system_testing/driver_test.rb
@@ -12,7 +12,8 @@ class DriverTest < ActiveSupport::TestCase
test "initializing the driver with a browser" do
driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :chrome, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" })
assert_equal :selenium, driver.instance_variable_get(:@name)
- assert_equal :chrome, driver.instance_variable_get(:@browser)
+ assert_equal :chrome, driver.instance_variable_get(:@browser).name
+ assert_nil driver.instance_variable_get(:@browser).options
assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size)
assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options)
end
@@ -20,7 +21,15 @@ class DriverTest < ActiveSupport::TestCase
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 :headless_chrome, driver.instance_variable_get(:@browser).name
+ 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 headless firefox" do
+ driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :headless_firefox, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" })
+ assert_equal :selenium, driver.instance_variable_get(:@name)
+ assert_equal :headless_firefox, driver.instance_variable_get(:@browser).name
assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size)
assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options)
end
diff --git a/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
index 2afda31cf5..264844fc7d 100644
--- a/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
+++ b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
@@ -35,6 +35,11 @@ class ScreenshotHelperTest < ActiveSupport::TestCase
end
end
+ test "defaults to simple output for the screenshot" do
+ new_test = DrivenBySeleniumWithChrome.new("x")
+ assert_equal "simple", new_test.send(:output_type)
+ end
+
test "display_image return artifact format when specify RAILS_SYSTEM_TESTING_SCREENSHOT environment" do
begin
original_output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"]
@@ -42,6 +47,8 @@ class ScreenshotHelperTest < ActiveSupport::TestCase
new_test = DrivenBySeleniumWithChrome.new("x")
+ assert_equal "artifact", new_test.send(:output_type)
+
Rails.stub :root, Pathname.getwd do
new_test.stub :passed?, false do
assert_match %r|url=artifact://.+?tmp/screenshots/failures_x\.png|, new_test.send(:display_image)
diff --git a/actionpack/test/dispatch/system_testing/server_test.rb b/actionpack/test/dispatch/system_testing/server_test.rb
index 1866225fc1..95e411faf4 100644
--- a/actionpack/test/dispatch/system_testing/server_test.rb
+++ b/actionpack/test/dispatch/system_testing/server_test.rb
@@ -6,10 +6,27 @@ require "action_dispatch/system_testing/server"
class ServerTest < ActiveSupport::TestCase
setup do
- ActionDispatch::SystemTesting::Server.new.run
+ @old_capybara_server = Capybara.server
end
test "port is always included" do
+ ActionDispatch::SystemTesting::Server.new.run
assert Capybara.always_include_port, "expected Capybara.always_include_port to be true"
end
+
+ test "server is changed from `default` to `puma`" do
+ Capybara.server = :default
+ ActionDispatch::SystemTesting::Server.new.run
+ refute_equal Capybara.server, Capybara.servers[:default]
+ end
+
+ test "server is not changed to `puma` when is different than default" do
+ Capybara.server = :webrick
+ ActionDispatch::SystemTesting::Server.new.run
+ assert_equal Capybara.server, Capybara.servers[:webrick]
+ end
+
+ teardown do
+ Capybara.server = @old_capybara_server
+ end
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 c6a6aef92b..b078a5abc5 100644
--- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb
+++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
@@ -28,6 +28,12 @@ class SetDriverToSeleniumHeadlessChromeTest < DrivenBySeleniumWithHeadlessChrome
end
end
+class SetDriverToSeleniumHeadlessFirefoxTest < DrivenBySeleniumWithHeadlessFirefox
+ test "uses selenium headless firefox" 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
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 4673d7cc11..5a584b12e5 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -18,7 +18,7 @@ module ActionDispatch
def test_filename_is_different_object
file_str = "foo"
uf = Http::UploadedFile.new(filename: file_str, tempfile: Object.new)
- assert_not_equal file_str.object_id , uf.original_filename.object_id
+ assert_not_equal file_str.object_id, uf.original_filename.object_id
end
def test_filename_should_be_in_utf_8
@@ -100,14 +100,14 @@ module ActionDispatch
def test_delegate_eof_to_tempfile
tf = Class.new { def eof?; true end; }
uf = Http::UploadedFile.new(tempfile: tf.new)
- assert uf.eof?
+ assert_predicate uf, :eof?
end
def test_respond_to?
tf = Class.new { def read; yield end }
uf = Http::UploadedFile.new(tempfile: tf.new)
- assert uf.respond_to?(:headers), "responds to headers"
- assert uf.respond_to?(:read), "responds to read"
+ assert_respond_to uf, :headers
+ assert_respond_to uf, :read
end
end
end
diff --git a/actionpack/test/journey/nodes/symbol_test.rb b/actionpack/test/journey/nodes/symbol_test.rb
index 1e687acef2..b0622ac71a 100644
--- a/actionpack/test/journey/nodes/symbol_test.rb
+++ b/actionpack/test/journey/nodes/symbol_test.rb
@@ -8,10 +8,10 @@ module ActionDispatch
class TestSymbol < ActiveSupport::TestCase
def test_default_regexp?
sym = Symbol.new "foo"
- assert sym.default_regexp?
+ assert_predicate sym, :default_regexp?
sym.regexp = nil
- assert_not sym.default_regexp?
+ assert_not_predicate sym, :default_regexp?
end
end
end
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
index 29cc74471d..183f421bcf 100644
--- a/actionpack/test/journey/router_test.rb
+++ b/actionpack/test/journey/router_test.rb
@@ -30,7 +30,7 @@ module ActionDispatch
def test_unicode
get "/ほげ", to: "foo#bar"
- #match the escaped version of /ほげ
+ # match the escaped version of /ほげ
env = rails_env "PATH_INFO" => "/%E3%81%BB%E3%81%92"
called = false
router.recognize(env) do |r, params|
diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb
index 81ce07526f..d5c81a8421 100644
--- a/actionpack/test/journey/routes_test.rb
+++ b/actionpack/test/journey/routes_test.rb
@@ -17,11 +17,11 @@ module ActionDispatch
def test_clear
mapper.get "/foo(/:id)", to: "foo#bar", as: "aaron"
- assert_not_predicate routes, :empty?
+ assert_not_empty routes
assert_equal 1, routes.length
routes.clear
- assert routes.empty?
+ assert_empty routes
assert_equal 0, routes.length
end
@@ -43,7 +43,7 @@ module ActionDispatch
mapper.get "/foo(/:id)", to: "foo#bar", as: "aaron"
assert_equal 1, @routes.anchored_routes.length
- assert_predicate @routes.custom_routes, :empty?
+ assert_empty @routes.custom_routes
mapper.get "/hello/:who", to: "foo#bar", as: "bar", who: /\d/
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index c700cb72ec..ea7f8b205e 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,45 +1,3 @@
-* Fix issues with `field_error_proc` wrapping `optgroup` and select divider `option`.
- Fixes #31088
- *Matthias Neumayr*
-
-* Remove deprecated Erubis ERB handler.
-
- *Rafael Mendonça França*
-
-* Remove default `alt` text generation.
-
- Fixes #30096
-
- *Cameron Cundiff*
-
-* Add `srcset` option to `image_tag` helper.
-
- *Roberto Miranda*
-
-* Fix issues with scopes and engine on `current_page?` method.
-
- Fixes #29401.
-
- *Nikita Savrov*
-
-* Generate field ids in `collection_check_boxes` and `collection_radio_buttons`.
-
- This makes sure that the labels are linked up with the fields.
-
- Fixes #29014.
-
- *Yuji Yaginuma*
-
-* Add `:json` type to `auto_discovery_link_tag` to support [JSON Feeds](https://jsonfeed.org/version/1)
-
- *Mike Gunderloy*
-
-* Update `distance_of_time_in_words` helper to display better error messages
- for bad input.
-
- *Jay Hayes*
-
-
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionview/CHANGELOG.md) for previous changes.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionview/CHANGELOG.md) for previous changes.
diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE
index ac810e86d0..1cb3add0fc 100644
--- a/actionview/MIT-LICENSE
+++ b/actionview/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2017 David Heinemeier Hansson
+Copyright (c) 2004-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/README.rdoc b/actionview/README.rdoc
index d5029599b7..03a0723564 100644
--- a/actionview/README.rdoc
+++ b/actionview/README.rdoc
@@ -11,7 +11,7 @@ The latest version of Action View can be installed with RubyGems:
$ gem install actionview
-Source code can be downloaded as part of the Rails project on GitHub
+Source code can be downloaded as part of the Rails project on GitHub:
* https://github.com/rails/rails/tree/master/actionview
@@ -29,7 +29,7 @@ API documentation is at
* http://api.rubyonrails.org
-Bug reports can be filed for the Ruby on Rails project here:
+Bug reports for the Ruby on Rails project can be filed here:
* https://github.com/rails/rails/issues
diff --git a/actionview/RUNNING_UJS_TESTS.rdoc b/actionview/RUNNING_UJS_TESTS.rdoc
index a575624a06..e30c2aee55 100644
--- a/actionview/RUNNING_UJS_TESTS.rdoc
+++ b/actionview/RUNNING_UJS_TESTS.rdoc
@@ -1,7 +1,8 @@
== Running UJS tests
-Ensure that you can build the project and run tests.
-Run rake ujs:server first, and then run the web tests by
-visiting http://localhost:4567 in your browser.
+Ensure that you can build the project by running:
+ rake ujs:server
-rake ujs:server
+Then run the web tests by visiting the following URL in your browser:
+
+ http://localhost:4567
diff --git a/actionview/RUNNING_UNIT_TESTS.rdoc b/actionview/RUNNING_UNIT_TESTS.rdoc
index e99d5ca1df..4442dbdb9e 100644
--- a/actionview/RUNNING_UNIT_TESTS.rdoc
+++ b/actionview/RUNNING_UNIT_TESTS.rdoc
@@ -2,13 +2,13 @@
The easiest way to run the unit tests is through Rake. The default task runs
the entire test suite for all classes. For more information, checkout the
-full array of rake tasks with "rake -T"
+full array of rake tasks with <tt>rake -T</tt>
Rake can be found at https://ruby.github.io/rake/.
== Running by hand
-To run a single test suite
+Run a single test suite:
rake test TEST=path/to/test.rb
@@ -18,10 +18,9 @@ which can be further narrowed down to one test:
== Dependency on Active Record and database setup
-Test cases in the test/activerecord/ directory depend on having
-activerecord and sqlite3 installed. If Active Record is not in
-actionview/../activerecord directory, or the sqlite3 rubygem is not installed,
-these tests are skipped.
-
+Test cases in the +test/activerecord/+ directory depend on having
+activerecord+ and +sqlite3+ installed. If Active Record is not in
+actionview/../activerecord+ directory, or the +sqlite3+ Ruby gem is not installed,
+ these tests are skipped.
Other tests are runnable from a fresh copy of actionview without any configuration.
diff --git a/actionview/app/assets/javascripts/MIT-LICENSE b/actionview/app/assets/javascripts/MIT-LICENSE
index befcbdc7b7..28e1b12496 100644
--- a/actionview/app/assets/javascripts/MIT-LICENSE
+++ b/actionview/app/assets/javascripts/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2007-2017 Rails Core team
+Copyright (c) 2007-2018 Rails Core team
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionview/app/assets/javascripts/README.md b/actionview/app/assets/javascripts/README.md
index 8198011b02..185dddc7e5 100644
--- a/actionview/app/assets/javascripts/README.md
+++ b/actionview/app/assets/javascripts/README.md
@@ -50,6 +50,6 @@ Run `bundle exec rake ujs:server` first, and then run the web tests by visiting
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"
+[data]: https://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"
[validator]: http://validator.w3.org/
[csrf]: http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html
diff --git a/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
index a2135c9851..a7eee52060 100644
--- a/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs/utils/event.coffee
@@ -11,8 +11,18 @@ if typeof CustomEvent isnt 'function'
evt = document.createEvent('CustomEvent')
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
evt
+
CustomEvent.prototype = window.Event.prototype
+ # Fix setting `defaultPrevented` when `preventDefault()` is called
+ # http://stackoverflow.com/questions/23349191/event-preventdefault-is-not-working-in-ie-11-for-custom-events
+ { preventDefault } = CustomEvent.prototype
+ CustomEvent.prototype.preventDefault = ->
+ result = preventDefault.call(this)
+ if @cancelable and not @defaultPrevented
+ Object.defineProperty(this, 'defaultPrevented', get: -> true)
+ result
+
# Triggers a custom event on an element and returns false if the event result is false
# obj::
# a native DOM element
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index 9533f8d56c..c1eeda75f5 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2017 David Heinemeier Hansson
+# Copyright (c) 2004-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index dfd62bdcfd..1cf0bd3016 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -89,7 +89,7 @@ module ActionView
end
def digest(finder, stack = [])
- Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
+ ActiveSupport::Digest.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
end
def dependency_digest(finder, stack)
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index ed92490be7..77ae444a58 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -7,8 +7,8 @@ module ActionView
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index e362f13798..76b1c3fb6e 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -2,6 +2,8 @@
require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/hash/keys"
+require "active_support/core_ext/object/inclusion"
+require "active_support/core_ext/object/try"
require "action_view/helpers/asset_url_helper"
require "action_view/helpers/tag_helper"
@@ -45,11 +47,11 @@ module ActionView
# When the last parameter is a hash you can add HTML attributes using that
# parameter. The following options are supported:
#
- # * <tt>:extname</tt> - Append an extension to the generated url unless the extension
- # already exists. This only applies for relative urls.
- # * <tt>:protocol</tt> - Sets the protocol of the generated url, this option only
- # applies when a relative url and +host+ options are provided.
- # * <tt>:host</tt> - When a relative url is provided the host is added to the
+ # * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
+ # already exists. This only applies for relative URLs.
+ # * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only
+ # applies when a relative URL and +host+ options are provided.
+ # * <tt>:host</tt> - When a relative URL is provided the host is added to the
# that path.
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
# when it is set to true.
@@ -91,7 +93,7 @@ module ActionView
content_tag("script".freeze, "", tag_options)
}.join("\n").html_safe
- request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request)
+ request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request
sources_tags
end
@@ -131,7 +133,7 @@ module ActionView
sources_tags = sources.uniq.map { |source|
href = path_to_stylesheet(source, path_options)
- early_hints_links << "<#{href}>; rel=preload; as=stylesheet"
+ early_hints_links << "<#{href}>; rel=preload; as=style"
tag_options = {
"rel" => "stylesheet",
"media" => "screen",
@@ -140,7 +142,7 @@ module ActionView
tag(:link, tag_options)
}.join("\n").html_safe
- request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request)
+ request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request
sources_tags
end
@@ -221,8 +223,69 @@ module ActionView
}.merge!(options.symbolize_keys))
end
+ # Returns a link tag that browsers can use to preload the +source+.
+ # The +source+ can be the path of a resource managed by asset pipeline,
+ # a full path, or an URI.
+ #
+ # ==== Options
+ #
+ # * <tt>:type</tt> - Override the auto-generated mime type, defaults to the mime type for +source+ extension.
+ # * <tt>:as</tt> - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type.
+ # * <tt>:crossorigin</tt> - Specify the crossorigin attribute, required to load cross-origin resources.
+ # * <tt>:nopush</tt> - Specify if the use of server push is not desired for the resource. Defaults to +false+.
+ #
+ # ==== Examples
+ #
+ # preload_link_tag("custom_theme.css")
+ # # => <link rel="preload" href="/assets/custom_theme.css" as="style" type="text/css" />
+ #
+ # preload_link_tag("/videos/video.webm")
+ # # => <link rel="preload" href="/videos/video.mp4" as="video" type="video/webm" />
+ #
+ # preload_link_tag(post_path(format: :json), as: "fetch")
+ # # => <link rel="preload" href="/posts.json" as="fetch" type="application/json" />
+ #
+ # preload_link_tag("worker.js", as: "worker")
+ # # => <link rel="preload" href="/assets/worker.js" as="worker" type="text/javascript" />
+ #
+ # preload_link_tag("//example.com/font.woff2")
+ # # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous"/>
+ #
+ # preload_link_tag("//example.com/font.woff2", crossorigin: "use-credentials")
+ # # => <link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="use-credentials" />
+ #
+ # preload_link_tag("/media/audio.ogg", nopush: true)
+ # # => <link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />
+ #
+ def preload_link_tag(source, options = {})
+ href = asset_path(source, skip_pipeline: options.delete(:skip_pipeline))
+ extname = File.extname(source).downcase.delete(".")
+ mime_type = options.delete(:type) || Template::Types[extname].try(:to_s)
+ as_type = options.delete(:as) || resolve_link_as(extname, mime_type)
+ crossorigin = options.delete(:crossorigin)
+ crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
+ nopush = options.delete(:nopush) || false
+
+ link_tag = tag.link({
+ rel: "preload",
+ href: href,
+ as: as_type,
+ type: mime_type,
+ crossorigin: crossorigin
+ }.merge!(options.symbolize_keys))
+
+ early_hints_link = "<#{href}>; rel=preload; as=#{as_type}"
+ early_hints_link += "; type=#{mime_type}" if mime_type
+ early_hints_link += "; crossorigin=#{crossorigin}" if crossorigin
+ early_hints_link += "; nopush" if nopush
+
+ request.send_early_hints("Link" => early_hints_link) if respond_to?(:request) && request
+
+ link_tag
+ end
+
# Returns an HTML image tag for the +source+. The +source+ can be a full
- # path, a file or an Active Storage attachment.
+ # path, a file, or an Active Storage attachment.
#
# ==== Options
#
@@ -310,12 +373,13 @@ module ActionView
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
# a single video tag will be returned. If +sources+ is an array, a video
# tag with nested source tags for each source will be returned. The
- # +sources+ can be full paths or files that exists in your public videos
+ # +sources+ can be full paths or files that exist in your public videos
# directory.
#
# ==== Options
- # You can add HTML attributes using the +options+. The +options+ supports
- # two additional keys for convenience and conformance:
+ #
+ # When the last parameter is a hash you can add HTML attributes using that
+ # parameter. The following options are supported:
#
# * <tt>:poster</tt> - Set an image (like a screenshot) to be shown
# before the video loads. The path is calculated like the +src+ of +image_tag+.
@@ -332,7 +396,7 @@ module ActionView
# video_tag("trailer.ogg")
# # => <video src="/videos/trailer.ogg"></video>
# video_tag("trailer.ogg", controls: true, preload: 'none')
- # # => <video preload="none" controls="controls" src="/videos/trailer.ogg" ></video>
+ # # => <video preload="none" controls="controls" src="/videos/trailer.ogg"></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png")
# # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png"></video>
# video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true)
@@ -359,9 +423,14 @@ module ActionView
end
end
- # Returns an HTML audio tag for the +source+.
- # The +source+ can be full path or file that exists in
- # your public audios directory.
+ # Returns an HTML audio tag for the +sources+. If +sources+ is a string,
+ # a single audio tag will be returned. If +sources+ is an array, an audio
+ # tag with nested source tags for each source will be returned. The
+ # +sources+ can be full paths or files that exist in your public audios
+ # directory.
+ #
+ # When the last parameter is a hash you can add HTML attributes using that
+ # parameter.
#
# audio_tag("sound")
# # => <audio src="/audios/sound"></audio>
@@ -417,6 +486,18 @@ module ActionView
raise ArgumentError, "Cannot pass a :size option with a :height or :width option"
end
end
+
+ def resolve_link_as(extname, mime_type)
+ if extname == "js"
+ "script"
+ elsif extname == "css"
+ "style"
+ elsif extname == "vtt"
+ "track"
+ elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font))
+ type
+ end
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index a4dcfc9a6c..8cbe107e41 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -6,7 +6,7 @@ module ActionView
# = Action View Asset URL Helpers
module Helpers #:nodoc:
# This module provides methods for generating asset paths and
- # urls.
+ # URLs.
#
# image_path("rails.png")
# # => "/assets/rails.png"
@@ -57,8 +57,8 @@ module ActionView
# You can read more about setting up your DNS CNAME records from your ISP.
#
# Note: This is purely a browser performance optimization and is not meant
- # for server load balancing. See http://www.die.net/musings/page_load_time/
- # for background and http://www.browserscope.org/?category=network for
+ # for server load balancing. See https://www.die.net/musings/page_load_time/
+ # for background and https://www.browserscope.org/?category=network for
# connection limit data.
#
# Alternatively, you can exert more control over the asset host by setting
@@ -97,7 +97,7 @@ module ActionView
# still sending assets for plain HTTP requests from asset hosts. If you don't
# have SSL certificates for each of the asset hosts this technique allows you
# to avoid warnings in the client about mixed media.
- # Note that the request parameter might not be supplied, e.g. when the assets
+ # Note that the +request+ parameter might not be supplied, e.g. when the assets
# are precompiled via a Rake task. Make sure to use a +Proc+ instead of a lambda,
# since a +Proc+ allows missing parameters and sets them to +nil+.
#
@@ -149,13 +149,13 @@ module ActionView
# Below lists scenarios that apply to +asset_path+ whether or not you're
# using the asset pipeline.
#
- # - All fully qualified urls are returned immediately. This bypasses the
+ # - All fully qualified URLs are returned immediately. This bypasses the
# asset pipeline and all other behavior described.
#
# asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js"
#
# - All assets that begin with a forward slash are assumed to be full
- # urls and will not be expanded. This will bypass the asset pipeline.
+ # URLs and will not be expanded. This will bypass the asset pipeline.
#
# asset_path("/foo.png") # => "/foo.png"
#
@@ -324,7 +324,7 @@ module ActionView
# Since +javascript_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/dir/xmlhr.js
+ # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/js/xmlhr.js
#
def javascript_url(source, options = {})
url_to_asset(source, { type: :javascript }.merge!(options))
@@ -351,7 +351,7 @@ module ActionView
# Since +stylesheet_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/css/style.css
+ # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/assets/css/style.css
#
def stylesheet_url(source, options = {})
url_to_asset(source, { type: :stylesheet }.merge!(options))
@@ -381,7 +381,7 @@ module ActionView
# Since +image_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/edit.png
+ # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/assets/edit.png
#
def image_url(source, options = {})
url_to_asset(source, { type: :image }.merge!(options))
@@ -407,7 +407,7 @@ module ActionView
# Since +video_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/hd.avi
+ # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/videos/hd.avi
#
def video_url(source, options = {})
url_to_asset(source, { type: :video }.merge!(options))
@@ -433,7 +433,7 @@ module ActionView
# Since +audio_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/horse.wav
+ # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/audios/horse.wav
#
def audio_url(source, options = {})
url_to_asset(source, { type: :audio }.merge!(options))
@@ -458,7 +458,7 @@ module ActionView
# Since +font_url+ is based on +asset_url+ method you can set :host options. If :host
# options is set, it overwrites global +config.action_controller.asset_host+ setting.
#
- # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/font.ttf
+ # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/fonts/font.ttf
#
def font_url(source, options = {})
url_to_asset(source, { type: :font }.merge!(options))
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 09040ccbc4..4c45f122fe 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -116,7 +116,7 @@ module ActionView
when 10..19 then locale.t :less_than_x_seconds, count: 20
when 20..39 then locale.t :half_a_minute
when 40..59 then locale.t :less_than_x_minutes, count: 1
- else locale.t :x_minutes, count: 1
+ else locale.t :x_minutes, count: 1
end
when 2...45 then locale.t :x_minutes, count: distance_in_minutes
@@ -131,7 +131,7 @@ module ActionView
when 43200...86400 then locale.t :about_x_months, count: (distance_in_minutes.to_f / 43200.0).round
# 60 days up to 365 days
when 86400...525600 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round
- else
+ else
from_year = from_time.year
from_year += 1 if from_time.month >= 3
to_year = to_time.year
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 6d2ace8cf8..15aa9ec2dd 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -19,7 +19,7 @@ module ActionView
# compared to using vanilla HTML.
#
# Typically, a form designed to create or update a resource reflects the
- # identity of the resource in several ways: (i) the url that the form is
+ # identity of the resource in several ways: (i) the URL that the form is
# sent to (the form element's +action+ attribute) should result in a request
# being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
# parameter in the case of an existing resource), (ii) input fields should
@@ -166,7 +166,7 @@ module ActionView
# So for example you may use a named route directly. When the model is
# represented by a string or symbol, as in the example above, if the
# <tt>:url</tt> option is not specified, by default the form will be
- # sent back to the current url (We will describe below an alternative
+ # sent back to the current URL (We will describe below an alternative
# resource-oriented usage of +form_for+ in which the URL does not need
# to be specified explicitly).
# * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
@@ -478,6 +478,8 @@ module ActionView
mattr_accessor :form_with_generates_remote_forms, default: true
+ mattr_accessor :form_with_generates_ids, default: false
+
# Creates a form tag based on mixing URLs, scopes, or models.
#
# # Using just a URL:
@@ -606,7 +608,7 @@ module ActionView
# This is helpful when fragment-caching the form. Remote forms
# get the authenticity token from the <tt>meta</tt> tag, so embedding is
# unnecessary unless you support browsers without JavaScript.
- # * <tt>:local</tt> - By default form submits are remote and unobstrusive XHRs.
+ # * <tt>:local</tt> - By default form submits are remote and unobtrusive XHRs.
# Disable remote submits with <tt>local: true</tt>.
# * <tt>:skip_enforcing_utf8</tt> - By default a hidden field named +utf8+
# is output to enforce UTF-8 submits. Set to true to skip the field.
@@ -640,16 +642,6 @@ module ActionView
#
# Where <tt>@document = Document.find(params[:id])</tt>.
#
- # When using labels +form_with+ requires setting the id on the field being
- # labelled:
- #
- # <%= form_with(model: @post) do |form| %>
- # <%= form.label :title %>
- # <%= form.text_field :title, id: :post_title %>
- # <% end %>
- #
- # See +label+ for more on how the +for+ attribute is derived.
- #
# === Mixing with other form helpers
#
# While +form_with+ uses a FormBuilder object it's possible to mix and
@@ -746,7 +738,7 @@ module ActionView
# end
def form_with(model: nil, scope: nil, url: nil, format: nil, **options)
options[:allow_method_names_outside_object] = true
- options[:skip_default_ids] = true
+ options[:skip_default_ids] = !form_with_generates_ids
if model
url ||= polymorphic_path(model, format: format)
@@ -1022,14 +1014,13 @@ module ActionView
# <%= fields :comment do |fields| %>
# <%= fields.text_field :body %>
# <% end %>
- # # => <input type="text" name="comment[body]>
+ # # => <input type="text" name="comment[body]">
#
# # Using a model infers the scope and assigns field values:
- # <%= fields model: Comment.new(body: "full bodied") do |fields| %<
+ # <%= fields model: Comment.new(body: "full bodied") do |fields| %>
# <%= fields.text_field :body %>
# <% end %>
- # # =>
- # <input type="text" name="comment[body] value="full bodied">
+ # # => <input type="text" name="comment[body]" value="full bodied">
#
# # Using +fields+ with +form_with+:
# <%= form_with model: @post do |form| %>
@@ -1044,16 +1035,6 @@ module ActionView
# or model is yielded, so any generated field names are prefixed with
# either the passed scope or the scope inferred from the <tt>:model</tt>.
#
- # When using labels +fields+ requires setting the id on the field being
- # labelled:
- #
- # <%= fields :comment do |fields| %>
- # <%= fields.label :body %>
- # <%= fields.text_field :body, id: :comment_body %>
- # <% end %>
- #
- # See +label+ for more on how the +for+ attribute is derived.
- #
# === Mixing with other form helpers
#
# While +form_with+ uses a FormBuilder object it's possible to mix and
@@ -1072,7 +1053,7 @@ module ActionView
# FormOptionsHelper#collection_select and DateHelper#datetime_select.
def fields(scope = nil, model: nil, **options, &block)
options[:allow_method_names_outside_object] = true
- options[:skip_default_ids] = true
+ options[:skip_default_ids] = !form_with_generates_ids
if model
scope ||= model_name_from_record_or_class(model).param_key
@@ -1985,7 +1966,7 @@ module ActionView
# See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method.
def fields(scope = nil, model: nil, **options, &block)
options[:allow_method_names_outside_object] = true
- options[:skip_default_ids] = true
+ options[:skip_default_ids] = !FormHelper.form_with_generates_ids
convert_to_legacy_options(options)
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 02a44477c1..fe5e0b693e 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -214,9 +214,13 @@ module ActionView
# * +method+ - The attribute of +object+ corresponding to the select tag
# * +collection+ - An array of objects representing the <tt><optgroup></tt> tags.
# * +group_method+ - The name of a method which, when called on a member of +collection+, returns an
- # array of child objects representing the <tt><option></tt> tags.
+ # array of child objects representing the <tt><option></tt> tags. It can also be any object that responds
+ # to +call+, such as a +proc+, that will be called for each member of the +collection+ to retrieve the
+ # value.
# * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a
- # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag.
+ # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag. It can also be any object
+ # that responds to +call+, such as a +proc+, that will be called for each member of the +collection+ to
+ # retrieve the label.
# * +option_key_method+ - The name of a method which, when called on a child object of a member of
# +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag.
# * +option_value_method+ - The name of a method which, when called on a child object of a member of
@@ -457,9 +461,9 @@ module ActionView
def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
collection.map do |group|
option_tags = options_from_collection_for_select(
- group.send(group_method), option_key_method, option_value_method, selected_key)
+ value_for_collection(group, group_method), option_key_method, option_value_method, selected_key)
- content_tag("optgroup".freeze, option_tags, label: group.send(group_label_method))
+ content_tag("optgroup".freeze, option_tags, label: value_for_collection(group, group_label_method))
end.join.html_safe
end
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index 8934a9894c..fed908fcdb 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -97,7 +97,7 @@ module ActionView
index = name_and_id_index(options)
options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
- unless skip_default_ids?
+ if generate_ids?
options["id"] = options.fetch("id") { tag_id(index) }
if namespace = options.delete("namespace")
options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
@@ -183,8 +183,8 @@ module ActionView
end
end
- def skip_default_ids?
- @skip_default_ids
+ def generate_ids?
+ !@skip_default_ids
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb
index 56b48bbd62..02bd099784 100644
--- a/actionview/lib/action_view/helpers/tags/label.rb
+++ b/actionview/lib/action_view/helpers/tags/label.rb
@@ -75,10 +75,6 @@ module ActionView
def render_component(builder)
builder.translation
end
-
- def skip_default_ids?
- false # The id is used as the `for` attribute.
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 84d38aa416..34138de00e 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -13,9 +13,9 @@ module ActionView
#
# ==== Sanitization
#
- # Most text helpers by default sanitize the given content, but do not escape it.
- # This means HTML tags will appear in the page but all malicious code will be removed.
- # Let's look at some examples using the +simple_format+ method:
+ # Most text helpers that generate HTML output sanitize the given input by default,
+ # but do not escape it. This means HTML tags will appear in the page but all malicious
+ # code will be removed. Let's look at some examples using the +simple_format+ method:
#
# simple_format('<a href="http://example.com/">Example</a>')
# # => "<p><a href=\"http://example.com/\">Example</a></p>"
@@ -128,7 +128,7 @@ module ActionView
# # => You searched for: <a href="search?q=rails">rails</a>
#
# highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
- # # => "<a>ruby</a> on <mark>rails</mark>"
+ # # => <a href="javascript:alert('no!')">ruby</a> on <mark>rails</mark>
def highlight(text, phrases, options = {})
text = sanitize(text) if options.fetch(:sanitize, true)
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 9900e0cd03..889562c478 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -139,6 +139,11 @@ module ActionView
# link_to "Profiles", controller: "profiles"
# # => <a href="/profiles">Profiles</a>
#
+ # When name is +nil+ the href is presented instead
+ #
+ # link_to nil, "http://example.com"
+ # # => <a href="http://www.example.com">http://www.example.com</a>
+ #
# You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
#
# <%= link_to(@profile) do %>
@@ -589,7 +594,7 @@ module ActionView
end
def add_method_to_attributes!(html_options, method)
- if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/
+ if method_not_get_method?(method) && html_options["rel"] !~ /nofollow/
if html_options["rel"].blank?
html_options["rel"] = "nofollow"
else
@@ -599,6 +604,19 @@ module ActionView
html_options["data-method"] = method
end
+ STRINGIFIED_COMMON_METHODS = {
+ get: "get",
+ delete: "delete",
+ patch: "patch",
+ post: "post",
+ put: "put",
+ }.freeze
+
+ def method_not_get_method?(method)
+ return false unless method
+ (STRINGIFIED_COMMON_METHODS[method] || method.to_s.downcase) != "get"
+ end
+
def token_tag(token = nil, form_options: {})
if token != false && protect_against_forgery?
token ||= form_authenticity_token(form_options: form_options)
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index b22347c55c..73dfb267bb 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -22,8 +22,15 @@ module ActionView
initializer "action_view.form_with_generates_remote_forms" do |app|
ActiveSupport.on_load(:action_view) do
form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms)
- unless form_with_generates_remote_forms.nil?
- ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms
+ ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms
+ end
+ end
+
+ initializer "action_view.form_with_generates_ids" do |app|
+ ActiveSupport.on_load(:action_view) do
+ form_with_generates_ids = app.config.action_view.delete(:form_with_generates_ids)
+ unless form_with_generates_ids.nil?
+ ActionView::Helpers::FormHelper.form_with_generates_ids = form_with_generates_ids
end
end
end
diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
index ca49eb1144..276a28ce07 100644
--- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb
+++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
@@ -65,7 +65,9 @@ module ActionView
yielder = lambda { |*name| view._layout_for(*name) }
instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
+ outer_config = I18n.config
fiber = Fiber.new do
+ I18n.config = outer_config
if layout
layout.render(view, locals, output, &yielder)
else
diff --git a/actionview/package.json b/actionview/package.json
index 4cbf0207e5..624eb5de93 100644
--- a/actionview/package.json
+++ b/actionview/package.json
@@ -1,6 +1,6 @@
{
"name": "rails-ujs",
- "version": "5.2.0-alpha",
+ "version": "6.0.0-alpha",
"description": "Ruby on Rails unobtrusive scripting adapter",
"main": "lib/assets/compiled/rails-ujs.js",
"files": [
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index 9df2a73448..8a9d7982d3 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -9,7 +9,7 @@ class ApplicationController < ActionController::Base
end
module Quiz
- #Models
+ # Models
Question = Struct.new(:name, :id) do
extend ActiveModel::Naming
include ActiveModel::Conversion
diff --git a/actionview/test/active_record_unit.rb b/actionview/test/active_record_unit.rb
index b39ecd8813..7f48b515a0 100644
--- a/actionview/test/active_record_unit.rb
+++ b/actionview/test/active_record_unit.rb
@@ -38,7 +38,7 @@ class ActiveRecordTestConnector
end
rescue Exception => e # errors from ActiveRecord setup
$stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
- #$stderr.puts " #{e.backtrace.join("\n ")}\n"
+ # $stderr.puts " #{e.backtrace.join("\n ")}\n"
self.able_to_connect = false
end
diff --git a/actionview/test/fixtures/layouts/streaming_with_locale.erb b/actionview/test/fixtures/layouts/streaming_with_locale.erb
new file mode 100644
index 0000000000..e1fdad2073
--- /dev/null
+++ b/actionview/test/fixtures/layouts/streaming_with_locale.erb
@@ -0,0 +1,2 @@
+layout.locale: <%= I18n.locale %>
+<%= yield %>
diff --git a/actionview/test/fixtures/test/streaming_with_locale.erb b/actionview/test/fixtures/test/streaming_with_locale.erb
new file mode 100644
index 0000000000..b0f2b2f7e9
--- /dev/null
+++ b/actionview/test/fixtures/test/streaming_with_locale.erb
@@ -0,0 +1 @@
+view.locale: <%= I18n.locale %>
diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb
index 7475f5cc3c..6d98eacfb8 100644
--- a/actionview/test/template/asset_tag_helper_test.rb
+++ b/actionview/test/template/asset_tag_helper_test.rb
@@ -214,6 +214,17 @@ class AssetTagHelperTest < ActionView::TestCase
%(favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png') => %(<link href="/images/mb-icon.png" rel="apple-touch-icon" type="image/png" />)
}
+ PreloadLinkToTag = {
+ %(preload_link_tag '/styles/custom_theme.css') => %(<link rel="preload" href="/styles/custom_theme.css" as="style" type="text/css" />),
+ %(preload_link_tag '/videos/video.webm') => %(<link rel="preload" href="/videos/video.webm" as="video" type="video/webm" />),
+ %(preload_link_tag '/posts.json', as: 'fetch') => %(<link rel="preload" href="/posts.json" as="fetch" type="application/json" />),
+ %(preload_link_tag '/users', as: 'fetch', type: 'application/json') => %(<link rel="preload" href="/users" as="fetch" type="application/json" />),
+ %(preload_link_tag '//example.com/map?callback=initMap', as: 'fetch', type: 'application/javascript') => %(<link rel="preload" href="//example.com/map?callback=initMap" as="fetch" type="application/javascript" />),
+ %(preload_link_tag '//example.com/font.woff2') => %(<link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="anonymous"/>),
+ %(preload_link_tag '//example.com/font.woff2', crossorigin: 'use-credentials') => %(<link rel="preload" href="//example.com/font.woff2" as="font" type="font/woff2" crossorigin="use-credentials" />),
+ %(preload_link_tag '/media/audio.ogg', nopush: true) => %(<link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />)
+ }
+
VideoPathToTag = {
%(video_path("xml")) => %(/videos/xml),
%(video_path("xml.ogg")) => %(/videos/xml.ogg),
@@ -396,7 +407,7 @@ class AssetTagHelperTest < ActionView::TestCase
end
def test_javascript_include_tag_is_html_safe
- assert javascript_include_tag("prototype").html_safe?
+ assert_predicate javascript_include_tag("prototype"), :html_safe?
end
def test_javascript_include_tag_relative_protocol
@@ -440,9 +451,17 @@ class AssetTagHelperTest < ActionView::TestCase
}
end
+ def test_stylesheet_link_tag_without_request
+ @request = nil
+ assert_dom_equal(
+ %(<link rel="stylesheet" media="screen" href="/stylesheets/foo.css" />),
+ stylesheet_link_tag("foo.css")
+ )
+ end
+
def test_stylesheet_link_tag_is_html_safe
- assert stylesheet_link_tag("dir/file").html_safe?
- assert stylesheet_link_tag("dir/other/file", "dir/file2").html_safe?
+ assert_predicate stylesheet_link_tag("dir/file"), :html_safe?
+ assert_predicate stylesheet_link_tag("dir/other/file", "dir/file2"), :html_safe?
end
def test_stylesheet_link_tag_escapes_options
@@ -464,6 +483,11 @@ class AssetTagHelperTest < ActionView::TestCase
assert_dom_equal %(<link href="//assets.example.com/stylesheets/wellington.css" media="screen" rel="stylesheet" />), stylesheet_link_tag("wellington")
end
+ def test_javascript_include_tag_without_request
+ @request = nil
+ assert_dom_equal %(<script src="/javascripts/foo.js"></script>), javascript_include_tag("foo.js")
+ end
+
def test_image_path
ImagePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
@@ -522,6 +546,10 @@ class AssetTagHelperTest < ActionView::TestCase
FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_preload_link_tag
+ PreloadLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_video_path
VideoPathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
@@ -778,6 +806,23 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
end
end
+class AssetTagHelperWithoutRequestTest < ActionView::TestCase
+ tests ActionView::Helpers::AssetTagHelper
+
+ undef :request
+
+ def test_stylesheet_link_tag_without_request
+ assert_dom_equal(
+ %(<link rel="stylesheet" media="screen" href="/stylesheets/foo.css" />),
+ stylesheet_link_tag("foo.css")
+ )
+ end
+
+ def test_javascript_include_tag_without_request
+ assert_dom_equal %(<script src="/javascripts/foo.js"></script>), javascript_include_tag("foo.js")
+ end
+end
+
class AssetUrlHelperControllerTest < ActionView::TestCase
tests ActionView::Helpers::AssetUrlHelper
diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb
index 1be20dcaae..8e683cb48a 100644
--- a/actionview/test/template/atom_feed_helper_test.rb
+++ b/actionview/test/template/atom_feed_helper_test.rb
@@ -257,7 +257,7 @@ class AtomFeedTest < ActionController::TestCase
get :index, params: { id: "provide_builder" }
# because we pass in the non-default builder, the content generated by the
# helper should go 'nowhere'. Leaving the response body blank.
- assert @response.body.blank?
+ assert_predicate @response.body, :blank?
end
end
diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index 8a1c00fd00..31c280a91c 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -155,12 +155,12 @@ class CaptureHelperTest < ActionView::TestCase
content_for :title do
content_tag(:p, "title")
end
- assert content_for(:title).html_safe?
+ assert_predicate content_for(:title), :html_safe?
content_for :title, "", flush: true
content_for(:title) do
content_tag(:p, "title")
end
- assert content_for(:title).html_safe?
+ assert_predicate content_for(:title), :html_safe?
end
def test_provide
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index 5a5550438b..79e52ccc47 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -687,7 +687,7 @@ class DateHelperTest < ActionView::TestCase
expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n)
expected << "</select>\n"
- assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), include_blank: true , minute_step: 15)
+ assert_dom_equal expected, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), include_blank: true, minute_step: 15)
end
def test_select_minute_nil_with_blank
@@ -703,7 +703,7 @@ class DateHelperTest < ActionView::TestCase
expected << %(<option value=""></option>\n<option value="00">00</option>\n<option value="15">15</option>\n<option value="30">30</option>\n<option value="45">45</option>\n)
expected << "</select>\n"
- assert_dom_equal expected, select_minute(nil, include_blank: true , minute_step: 15)
+ assert_dom_equal expected, select_minute(nil, include_blank: true, minute_step: 15)
end
def test_select_minute_with_hidden
@@ -3593,25 +3593,25 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_html_safety
- assert select_day(16).html_safe?
- assert select_month(8).html_safe?
- assert select_year(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe?
- assert select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe?
- assert select_second(Time.mktime(2003, 8, 16, 8, 4, 18)).html_safe?
+ assert_predicate select_day(16), :html_safe?
+ assert_predicate select_month(8), :html_safe?
+ assert_predicate select_year(Time.mktime(2003, 8, 16, 8, 4, 18)), :html_safe?
+ assert_predicate select_minute(Time.mktime(2003, 8, 16, 8, 4, 18)), :html_safe?
+ assert_predicate select_second(Time.mktime(2003, 8, 16, 8, 4, 18)), :html_safe?
- assert select_minute(8, use_hidden: true).html_safe?
- assert select_month(8, prompt: "Choose month").html_safe?
+ assert_predicate select_minute(8, use_hidden: true), :html_safe?
+ assert_predicate select_month(8, prompt: "Choose month"), :html_safe?
- assert select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, { class: "selector" }).html_safe?
- assert select_date(Time.mktime(2003, 8, 16), date_separator: " / ", start_year: 2003, end_year: 2005, prefix: "date[first]").html_safe?
+ assert_predicate select_time(Time.mktime(2003, 8, 16, 8, 4, 18), {}, { class: "selector" }), :html_safe?
+ assert_predicate select_date(Time.mktime(2003, 8, 16), date_separator: " / ", start_year: 2003, end_year: 2005, prefix: "date[first]"), :html_safe?
end
def test_object_select_html_safety
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
- assert date_select("post", "written_on", default: Time.local(2006, 9, 19, 15, 16, 35), include_blank: true).html_safe?
- assert time_select("post", "written_on", ignore_date: true).html_safe?
+ assert_predicate date_select("post", "written_on", default: Time.local(2006, 9, 19, 15, 16, 35), include_blank: true), :html_safe?
+ assert_predicate time_select("post", "written_on", ignore_date: true), :html_safe?
end
def test_time_tag_with_date
diff --git a/actionview/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb
index 8b804105f4..bd702dbe94 100644
--- a/actionview/test/template/erb_util_test.rb
+++ b/actionview/test/template/erb_util_test.rb
@@ -70,24 +70,24 @@ class ErbUtilTest < ActiveSupport::TestCase
def test_json_escape_returns_unsafe_strings_when_passed_unsafe_strings
value = json_escape("asdf")
- assert !value.html_safe?
+ assert_not_predicate value, :html_safe?
end
def test_json_escape_returns_safe_strings_when_passed_safe_strings
value = json_escape("asdf".html_safe)
- assert value.html_safe?
+ assert_predicate value, :html_safe?
end
def test_html_escape_is_html_safe
escaped = h("<p>")
assert_equal "&lt;p&gt;", escaped
- assert escaped.html_safe?
+ assert_predicate escaped, :html_safe?
end
def test_html_escape_passes_html_escape_unmodified
escaped = h("<p>".html_safe)
assert_equal "<p>", escaped
- assert escaped.html_safe?
+ assert_predicate escaped, :html_safe?
end
def test_rest_in_ascii
@@ -104,11 +104,11 @@ class ErbUtilTest < ActiveSupport::TestCase
def test_html_escape_once_returns_unsafe_strings_when_passed_unsafe_strings
value = html_escape_once("1 < 2 &amp; 3")
- assert !value.html_safe?
+ assert_not_predicate value, :html_safe?
end
def test_html_escape_once_returns_safe_strings_when_passed_safe_strings
value = html_escape_once("1 < 2 &amp; 3".html_safe)
- assert value.html_safe?
+ assert_predicate value, :html_safe?
end
end
diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb
index c7d49070ce..0d224d0c46 100644
--- a/actionview/test/template/form_helper/form_with_test.rb
+++ b/actionview/test/template/form_helper/form_with_test.rb
@@ -5,6 +5,15 @@ require "controller/fake_models"
class FormWithTest < ActionView::TestCase
include RenderERBUtils
+
+ setup do
+ @old_value = ActionView::Helpers::FormHelper.form_with_generates_ids
+ ActionView::Helpers::FormHelper.form_with_generates_ids = true
+ end
+
+ teardown do
+ ActionView::Helpers::FormHelper.form_with_generates_ids = @old_value
+ end
end
class FormWithActsLikeFormTagTest < FormWithTest
@@ -99,7 +108,7 @@ class FormWithActsLikeFormTagTest < FormWithTest
actual = form_with(skip_enforcing_utf8: true)
expected = whole_form("http://www.example.com", skip_enforcing_utf8: true)
assert_dom_equal expected, actual
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
end
def test_form_with_with_block_in_erb
@@ -218,7 +227,7 @@ class FormWithActsLikeFormForTest < FormWithTest
@post = Post.new
@comment = Comment.new
- def @post.errors()
+ def @post.errors
Class.new {
def [](field); field == "author_name" ? ["can't be empty"] : [] end
def empty?() false end
@@ -314,17 +323,45 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = whole_form("/posts/123", "create-post", method: "patch") do
"<label for='post_title'>The Title</label>" \
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[secret]' type='hidden' value='0' />" \
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' />" \
+ "<select name='post[category]' id='post_category'><option value='animal'>animal</option>\n<option value='economy'>economy</option>\n<option value='sports'>sports</option></select>" \
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" \
+ "<button name='button' type='submit'>Create post</button>" \
+ "<button name='button' type='submit'><span>Create post</span></button>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_with_not_outputting_ids
+ old_value = ActionView::Helpers::FormHelper.form_with_generates_ids
+ ActionView::Helpers::FormHelper.form_with_generates_ids = false
+
+ form_with(model: @post, id: "create-post") do |f|
+ concat f.label(:title) { "The Title" }
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ concat f.select(:category, %w( animal economy sports ))
+ concat f.submit("Create post")
+ end
+
+ expected = whole_form("/posts/123", "create-post", method: "patch") do
+ "<label>The Title</label>" \
"<input name='post[title]' type='text' value='Hello World' />" \
"<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[secret]' type='hidden' value='0' />" \
"<input name='post[secret]' checked='checked' type='checkbox' value='1' />" \
"<select name='post[category]'><option value='animal'>animal</option>\n<option value='economy'>economy</option>\n<option value='sports'>sports</option></select>" \
- "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" \
- "<button name='button' type='submit'>Create post</button>" \
- "<button name='button' type='submit'><span>Create post</span></button>"
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
assert_dom_equal expected, output_buffer
+ ensure
+ ActionView::Helpers::FormHelper.form_with_generates_ids = old_value
end
def test_form_with_only_url_on_create
@@ -335,7 +372,7 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = whole_form("/posts") do
'<label for="title">Label me</label>' \
- '<input type="text" name="title">'
+ '<input type="text" name="title" id="title">'
end
assert_dom_equal expected, output_buffer
@@ -349,7 +386,7 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = whole_form("/posts/123") do
'<label for="title">Label me</label>' \
- '<input type="text" name="title">'
+ '<input type="text" name="title" id="title">'
end
assert_dom_equal expected, output_buffer
@@ -361,7 +398,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123") do
- '<input type="text" name="no_model_to_back_this_badboy">'
+ '<input type="text" name="no_model_to_back_this_badboy" id="no_model_to_back_this_badboy" >'
end
assert_dom_equal expected, output_buffer
@@ -373,7 +410,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: :patch) do
- '<input type="text" name="post[this_dont_exist_on_post]">'
+ '<input type="text" name="post[this_dont_exist_on_post]" id="post_this_dont_exist_on_post" >'
end
assert_dom_equal expected, output_buffer
@@ -391,8 +428,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end.new
form_with(model: obj, scope: "other_name", url: "/", id: "edit-other-name") do |f|
- assert_dom_equal '<input type="hidden" name="other_name[private_property]">', f.hidden_field(:private_property)
- assert_dom_equal '<input type="hidden" name="other_name[protected_property]">', f.hidden_field(:protected_property)
+ assert_dom_equal '<input type="hidden" name="other_name[private_property]" id="other_name_private_property">', f.hidden_field(:private_property)
+ assert_dom_equal '<input type="hidden" name="other_name[protected_property]" id="other_name_protected_property">', f.hidden_field(:protected_property)
end
end
@@ -459,7 +496,7 @@ class FormWithActsLikeFormForTest < FormWithTest
"<label for='post_active_false'>" \
"<input checked='checked' name='post[active]' type='radio' value='false' id='post_active_false' />" \
"false</label>" \
- "<input name='post[id]' type='hidden' value='1' />"
+ "<input name='post[id]' type='hidden' value='1' id='post_id' />"
end
assert_dom_equal expected, output_buffer
@@ -557,7 +594,7 @@ class FormWithActsLikeFormForTest < FormWithTest
"<label for='post_tag_ids_3'>" \
"<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' id='post_tag_ids_3' />" \
"Tag 3</label>" \
- "<input name='post[id]' type='hidden' value='1' />"
+ "<input name='post[id]' type='hidden' value='1' id='post_id' />"
end
assert_dom_equal expected, output_buffer
@@ -587,7 +624,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", "create-post", method: "patch", multipart: true) do
- "<input name='post[file]' type='file' />"
+ "<input name='post[file]' type='file' id='post_file' />"
end
assert_dom_equal expected, output_buffer
@@ -601,7 +638,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch", multipart: true) do
- "<input name='post[comment][file]' type='file' />"
+ "<input name='post[comment][file]' type='file' id='post_comment_file'/>"
end
assert_dom_equal expected, output_buffer
@@ -640,7 +677,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/44", method: "patch") do
- "<input name='post[title]' type='text' value='And his name will be forty and four.' />" \
+ "<input name='post[title]' type='text' value='And his name will be forty and four.' id='post_title' />" \
"<input name='commit' data-disable-with='Edit post' type='submit' value='Edit post' />"
end
@@ -658,10 +695,10 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = whole_form("/posts/123", "create-post", method: "patch") do
"<label for='other_name_title' class='post_title'>Title</label>" \
- "<input name='other_name[title]' value='Hello World' type='text' />" \
- "<textarea name='other_name[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='other_name[title]' value='Hello World' type='text' id='other_name_title' />" \
+ "<textarea name='other_name[body]' id='other_name_body'>\nBack to the hill and over it again!</textarea>" \
"<input name='other_name[secret]' value='0' type='hidden' />" \
- "<input name='other_name[secret]' checked='checked' value='1' type='checkbox' />" \
+ "<input name='other_name[secret]' checked='checked' value='1' type='checkbox' id='other_name_secret' />" \
"<input name='commit' value='Create post' data-disable-with='Create post' type='submit' />"
end
@@ -676,10 +713,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/", "create-post", method: "delete") do
- "<input name='post[title]' type='text' value='Hello World' />" \
- "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[secret]' type='hidden' value='0' />" \
- "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret'/>"
end
assert_dom_equal expected, output_buffer
@@ -693,10 +730,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/", "create-post", method: "delete") do
- "<input name='post[title]' type='text' value='Hello World' />" \
- "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body' >\nBack to the hill and over it again!</textarea>" \
"<input name='post[secret]' type='hidden' value='0' />" \
- "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' />"
end
assert_dom_equal expected, output_buffer
@@ -710,7 +747,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/search", "search-post", method: "get") do
- "<input name='post[title]' type='search' />"
+ "<input name='post[title]' type='search' id='post_title' />"
end
assert_dom_equal expected, output_buffer
@@ -724,10 +761,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/", "create-post", method: "patch") do
- "<input name='post[title]' type='text' value='Hello World' />" \
- "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body' >\nBack to the hill and over it again!</textarea>" \
"<input name='post[secret]' type='hidden' value='0' />" \
- "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' />"
end
assert_dom_equal expected, output_buffer
@@ -744,10 +781,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/", "create-post", method: "patch", local: true) do
- "<input name='post[title]' type='text' value='Hello World' />" \
- "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[secret]' type='hidden' value='0' />" \
- "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' />"
end
assert_dom_equal expected, output_buffer
@@ -761,7 +798,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/", skip_enforcing_utf8: true) do
- "<input name='post[title]' type='text' value='Hello World' />"
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />"
end
assert_dom_equal expected, output_buffer
@@ -773,7 +810,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/", skip_enforcing_utf8: false) do
- "<input name='post[title]' type='text' value='Hello World' />"
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />"
end
assert_dom_equal expected, output_buffer
@@ -787,10 +824,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/", "create-post") do
- "<input name='post[title]' type='text' value='Hello World' />" \
- "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body' >\nBack to the hill and over it again!</textarea>" \
"<input name='post[secret]' type='hidden' value='0' />" \
- "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' />"
end
assert_dom_equal expected, output_buffer
@@ -806,10 +843,10 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = whole_form("/posts/123", method: "patch") do
"<label for='post_123_title'>Title</label>" \
- "<input name='post[123][title]' type='text' value='Hello World' />" \
- "<textarea name='post[123][body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[123][title]' type='text' value='Hello World' id='post_123_title' />" \
+ "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[123][secret]' type='hidden' value='0' />" \
- "<input name='post[123][secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[123][secret]' checked='checked' type='checkbox' value='1' id='post_123_secret' />"
end
assert_dom_equal expected, output_buffer
@@ -823,10 +860,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[][title]' type='text' value='Hello World' />" \
- "<textarea name='post[][body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[][title]' type='text' value='Hello World' id='post__title' />" \
+ "<textarea name='post[][body]' id='post__body' >\nBack to the hill and over it again!</textarea>" \
"<input name='post[][secret]' type='hidden' value='0' />" \
- "<input name='post[][secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[][secret]' checked='checked' type='checkbox' value='1' id='post__secret' />"
end
assert_dom_equal expected, output_buffer
@@ -841,7 +878,7 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = whole_form("/posts/123", method: "patch") do
"<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" \
- "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' /></div>" \
+ "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' id='post_author_name' /></div>" \
"<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
@@ -859,7 +896,7 @@ class FormWithActsLikeFormForTest < FormWithTest
expected = whole_form("/posts/123", method: "patch") do
"<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" \
- "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' /></div>" \
+ "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' id='post_author_name' /></div>" \
"<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
@@ -947,7 +984,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: :patch) do
- '<input type="text" name="post[comment][dont_exist_on_model]">'
+ '<input type="text" name="post[comment][dont_exist_on_model]" id="post_comment_dont_exist_on_model" >'
end
assert_dom_equal expected, output_buffer
@@ -967,7 +1004,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form do
- '<input name="posts[post][0][comment][1][dont_exist_on_model]" type="text">'
+ '<input name="posts[post][0][comment][1][dont_exist_on_model]" type="text" id="posts_post_0_comment_1_dont_exist_on_model" >'
end
assert_dom_equal expected, output_buffer
@@ -982,7 +1019,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[comment][body]' type='text' value='Hello World' />"
+ "<input name='post[comment][body]' type='text' value='Hello World' id='post_comment_body' />"
end
assert_dom_equal expected, output_buffer
@@ -1002,7 +1039,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form do
- "<input name='posts[post][0][comment][1][name]' type='text' value='comment #1' />"
+ "<input name='posts[post][0][comment][1][name]' type='text' value='comment #1' id='posts_post_0_comment_1_name' />"
end
assert_dom_equal expected, output_buffer
@@ -1017,8 +1054,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[123][title]' type='text' value='Hello World' />" \
- "<input name='post[123][comment][][name]' type='text' value='new comment' />"
+ "<input name='post[123][title]' type='text' value='Hello World' id='post_123_title' />" \
+ "<input name='post[123][comment][][name]' type='text' value='new comment' id='post_123_comment__name' />"
end
assert_dom_equal expected, output_buffer
@@ -1033,8 +1070,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[1][title]' type='text' value='Hello World' />" \
- "<input name='post[1][comment][1][name]' type='text' value='new comment' />"
+ "<input name='post[1][title]' type='text' value='Hello World' id='post_1_title' />" \
+ "<input name='post[1][comment][1][name]' type='text' value='new comment' id='post_1_comment_1_name' />"
end
assert_dom_equal expected, output_buffer
@@ -1048,7 +1085,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[1][comment][title]' type='text' value='Hello World' />"
+ "<input name='post[1][comment][title]' type='text' value='Hello World' id='post_1_comment_title' />"
end
assert_dom_equal expected, output_buffer
@@ -1062,7 +1099,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[1][comment][5][title]' type='text' value='Hello World' />"
+ "<input name='post[1][comment][5][title]' type='text' value='Hello World' id='post_1_comment_5_title' />"
end
assert_dom_equal expected, output_buffer
@@ -1076,7 +1113,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[123][comment][title]' type='text' value='Hello World' />"
+ "<input name='post[123][comment][title]' type='text' value='Hello World' id='post_123_comment_title' />"
end
assert_dom_equal expected, output_buffer
@@ -1090,7 +1127,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[comment][5][title]' type='radio' value='hello' />"
+ "<input name='post[comment][5][title]' type='radio' value='hello' id='post_comment_5_title_hello' />"
end
assert_dom_equal expected, output_buffer
@@ -1104,7 +1141,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[123][comment][123][title]' type='text' value='Hello World' />"
+ "<input name='post[123][comment][123][title]' type='text' value='Hello World' id='post_123_comment_123_title' />"
end
assert_dom_equal expected, output_buffer
@@ -1124,9 +1161,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[123][comment][5][title]' type='text' value='Hello World' />"
+ "<input name='post[123][comment][5][title]' type='text' value='Hello World' id='post_123_comment_5_title' />"
end + whole_form("/posts/123", method: "patch") do
- "<input name='post[1][comment][123][title]' type='text' value='Hello World' />"
+ "<input name='post[1][comment][123][title]' type='text' value='Hello World' id='post_1_comment_123_title' />"
end
assert_dom_equal expected, output_buffer
@@ -1143,8 +1180,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[author_attributes][name]" type="text" value="new author" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[author_attributes][name]" type="text" value="new author" id="post_author_attributes_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1170,9 +1207,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[author_attributes][name]" type="text" value="author #321" />' \
- '<input name="post[author_attributes][id]" type="hidden" value="321" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" id="post_author_attributes_name" />' \
+ '<input name="post[author_attributes][id]" type="hidden" value="321" id="post_author_attributes_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1189,9 +1226,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[author_attributes][name]" type="text" value="author #321" />' \
- '<input name="post[author_attributes][id]" type="hidden" value="321" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" id="post_author_attributes_name" />' \
+ '<input name="post[author_attributes][id]" type="hidden" value="321" id="post_author_attributes_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1208,8 +1245,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[author_attributes][name]" type="text" value="author #321" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" id="post_author_attributes_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1226,8 +1263,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[author_attributes][name]" type="text" value="author #321" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" id="post_author_attributes_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1244,9 +1281,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[author_attributes][name]" type="text" value="author #321" />' \
- '<input name="post[author_attributes][id]" type="hidden" value="321" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" id="post_author_attributes_name" />' \
+ '<input name="post[author_attributes][id]" type="hidden" value="321" id="post_author_attributes_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1264,9 +1301,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[author_attributes][id]" type="hidden" value="321" />' \
- '<input name="post[author_attributes][name]" type="text" value="author #321" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[author_attributes][id]" type="hidden" value="321" id="post_author_attributes_id" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" id="post_author_attributes_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1285,11 +1322,11 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
- '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \
- '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" id="post_comments_attributes_0_id" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" id="post_comments_attributes_1_name" />' \
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" id="post_comments_attributes_1_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1312,11 +1349,11 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[author_attributes][name]" type="text" value="author #321" />' \
- '<input name="post[author_attributes][id]" type="hidden" value="321" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" id="post_author_attributes_name" />' \
+ '<input name="post[author_attributes][id]" type="hidden" value="321" id="post_author_attributes_id" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" id="post_comments_attributes_1_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1339,10 +1376,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[author_attributes][name]" type="text" value="author #321" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" id="post_author_attributes_name" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" id="post_comments_attributes_1_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1365,11 +1402,11 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[author_attributes][name]" type="text" value="author #321" />' \
- '<input name="post[author_attributes][id]" type="hidden" value="321" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" id="post_author_attributes_name" />' \
+ '<input name="post[author_attributes][id]" type="hidden" value="321" id="post_author_attributes_id" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" id="post_comments_attributes_1_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1388,11 +1425,11 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
- '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \
- '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" id="post_comments_attributes_0_id" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" id="post_comments_attributes_1_name" />' \
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" id="post_comments_attributes_1_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1412,11 +1449,11 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
- '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" id="post_comments_attributes_0_id" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" id="post_comments_attributes_1_id" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" id="post_comments_attributes_1_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1435,9 +1472,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="new comment" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="new comment" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="new comment" id="post_comments_attributes_1_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1456,10 +1493,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \
- '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="321" id="post_comments_attributes_0_id"/>' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="new comment" id="post_comments_attributes_1_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1474,7 +1511,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />'
end
assert_dom_equal expected, output_buffer
@@ -1491,11 +1528,11 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
- '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \
- '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" id="post_comments_attributes_0_id" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" id="post_comments_attributes_1_name" />' \
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" id="post_comments_attributes_1_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1512,11 +1549,11 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
- '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \
- '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" id="post_comments_attributes_0_id" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" id="post_comments_attributes_1_name" />' \
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" id="post_comments_attributes_1_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1547,11 +1584,11 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
- '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \
- '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="1" id="post_comments_attributes_0_id" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" id="post_comments_attributes_1_name" />' \
+ '<input name="post[comments_attributes][1][id]" type="hidden" value="2" id="post_comments_attributes_1_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1570,10 +1607,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[title]" type="text" value="Hello World" />' \
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \
- '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' \
- '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />'
+ '<input name="post[title]" type="text" value="Hello World" id="post_title" />' \
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="321" id="post_comments_attributes_0_id" />' \
+ '<input name="post[comments_attributes][1][name]" type="text" value="new comment" id="post_comments_attributes_1_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1590,8 +1627,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \
- '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
+ '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" id="post_comments_attributes_abc_name" />' \
+ '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" id="post_comments_attributes_abc_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1607,8 +1644,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \
- '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
+ '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" id="post_comments_attributes_abc_name" />' \
+ '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" id="post_comments_attributes_abc_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1630,8 +1667,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \
- '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
+ '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" id="post_comments_attributes_abc_name" />' \
+ '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" id="post_comments_attributes_abc_id" />'
end
assert_dom_equal expected, output_buffer
@@ -1716,18 +1753,18 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \
- '<input name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' \
- '<input name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' \
- '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' \
- '<input name="post[tags_attributes][0][value]" type="text" value="tag #123" />' \
- '<input name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' \
- '<input name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' \
- '<input name="post[tags_attributes][0][id]" type="hidden" value="123" />' \
- '<input name="post[tags_attributes][1][value]" type="text" value="tag #456" />' \
- '<input name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' \
- '<input name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' \
- '<input name="post[tags_attributes][1][id]" type="hidden" value="456" />'
+ '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" id="post_comments_attributes_0_name" />' \
+ '<input name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" id="post_comments_attributes_0_relevances_attributes_0_value" />' \
+ '<input name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" id="post_comments_attributes_0_relevances_attributes_0_id"/>' \
+ '<input name="post[comments_attributes][0][id]" type="hidden" value="321" id="post_comments_attributes_0_id"/>' \
+ '<input name="post[tags_attributes][0][value]" type="text" value="tag #123" id="post_tags_attributes_0_value"/>' \
+ '<input name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" id="post_tags_attributes_0_relevances_attributes_0_value"/>' \
+ '<input name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" id="post_tags_attributes_0_relevances_attributes_0_id"/>' \
+ '<input name="post[tags_attributes][0][id]" type="hidden" value="123" id="post_tags_attributes_0_id"/>' \
+ '<input name="post[tags_attributes][1][value]" type="text" value="tag #456" id="post_tags_attributes_1_value"/>' \
+ '<input name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" id="post_tags_attributes_1_relevances_attributes_0_value"/>' \
+ '<input name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" id="post_tags_attributes_1_relevances_attributes_0_id"/>' \
+ '<input name="post[tags_attributes][1][id]" type="hidden" value="456" id="post_tags_attributes_1_id"/>'
end
assert_dom_equal expected, output_buffer
@@ -1743,7 +1780,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- '<input name="post[author_attributes][name]" type="text" value="hash backed author" />'
+ '<input name="post[author_attributes][name]" type="text" value="hash backed author" id="post_author_attributes_name" />'
end
assert_dom_equal expected, output_buffer
@@ -1757,10 +1794,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected =
- "<input name='post[title]' type='text' value='Hello World' />" \
- "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[secret]' type='hidden' value='0' />" \
- "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' />"
assert_dom_equal expected, output_buffer
end
@@ -1773,10 +1810,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected =
- "<input name='post[123][title]' type='text' value='Hello World' />" \
- "<textarea name='post[123][body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[123][title]' type='text' value='Hello World' id='post_123_title' />" \
+ "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[123][secret]' type='hidden' value='0' />" \
- "<input name='post[123][secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[123][secret]' checked='checked' type='checkbox' value='1' id='post_123_secret' />"
assert_dom_equal expected, output_buffer
end
@@ -1789,10 +1826,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected =
- "<input name='post[][title]' type='text' value='Hello World' />" \
- "<textarea name='post[][body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[][title]' type='text' value='Hello World' id='post__title' />" \
+ "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[][secret]' type='hidden' value='0' />" \
- "<input name='post[][secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[][secret]' checked='checked' type='checkbox' value='1' id='post__secret' />"
assert_dom_equal expected, output_buffer
end
@@ -1805,10 +1842,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected =
- "<input name='post[abc][title]' type='text' value='Hello World' />" \
- "<textarea name='post[abc][body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[abc][title]' type='text' value='Hello World' id='post_abc_title' />" \
+ "<textarea name='post[abc][body]' id='post_abc_body'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[abc][secret]' type='hidden' value='0' />" \
- "<input name='post[abc][secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[abc][secret]' checked='checked' type='checkbox' value='1' id='post_abc_secret' />"
assert_dom_equal expected, output_buffer
end
@@ -1821,10 +1858,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected =
- "<input name='post[title]' type='text' value='Hello World' />" \
- "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body' >\nBack to the hill and over it again!</textarea>" \
"<input name='post[secret]' type='hidden' value='0' />" \
- "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' />"
assert_dom_equal expected, output_buffer
end
@@ -1837,10 +1874,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected =
- "<input name='post[title]' type='text' value='Hello World' />" \
- "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body' >\nBack to the hill and over it again!</textarea>" \
"<input name='post[secret]' type='hidden' value='0' />" \
- "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' />"
assert_dom_equal expected, output_buffer
end
@@ -1852,7 +1889,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
assert_dom_equal "<label for=\"author_post_title\">Title</label>" \
- "<input name='author[post][title]' type='text' value='Hello World' />",
+ "<input name='author[post][title]' type='text' value='Hello World' id='author_post_title' id='author_post_1_title' />",
output_buffer
end
@@ -1863,7 +1900,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" \
- "<input name='author[post][1][title]' type='text' value='Hello World' />",
+ "<input name='author[post][1][title]' type='text' value='Hello World' id='author_post_1_title' />",
output_buffer
end
@@ -1882,10 +1919,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", "create-post", method: "patch") do
- "<input name='post[title]' type='text' value='Hello World' />" \
- "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body' >\nBack to the hill and over it again!</textarea>" \
"<input name='parent_post[secret]' type='hidden' value='0' />" \
- "<input name='parent_post[secret]' checked='checked' type='checkbox' value='1' />"
+ "<input name='parent_post[secret]' checked='checked' type='checkbox' value='1' id='parent_post_secret' />"
end
assert_dom_equal expected, output_buffer
@@ -1902,9 +1939,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", "create-post", method: "patch") do
- "<input name='post[title]' type='text' value='Hello World' />" \
- "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
- "<input name='post[comment][name]' type='text' value='new comment' />"
+ "<input name='post[title]' type='text' value='Hello World' id='post_title' />" \
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[comment][name]' type='text' value='new comment' id='post_comment_name' />"
end
assert_dom_equal expected, output_buffer
@@ -1918,7 +1955,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<input name='post[category][name]' type='text' />"
+ "<input name='post[category][name]' type='text' id='post_category_name' />"
end
assert_dom_equal expected, output_buffer
@@ -1942,9 +1979,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" \
- "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" \
- "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>"
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' id='post_title'/><br/>" \
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" \
+ "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' /><br/>"
end
assert_dom_equal expected, output_buffer
@@ -1961,9 +1998,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" \
- "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" \
- "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>"
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' id='post_title' /><br/>" \
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" \
+ "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' /><br/>"
end
assert_dom_equal expected, output_buffer
@@ -1980,7 +2017,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>"
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' id='post_title' /><br/>"
end
assert_dom_equal expected, output_buffer
@@ -1995,7 +2032,7 @@ class FormWithActsLikeFormForTest < FormWithTest
concat f.text_field(:title)
end
- expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>"
+ expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' id='post_title' /><br/>"
assert_dom_equal expected, output_buffer
end
@@ -2007,7 +2044,7 @@ class FormWithActsLikeFormForTest < FormWithTest
concat f.text_field(:title)
end
- expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>"
+ expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' id='post_title' /><br/>"
assert_dom_equal expected, output_buffer
end
@@ -2020,9 +2057,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected =
- "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" \
- "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" \
- "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>"
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' id='post_title'/><br/>" \
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" \
+ "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' id='post_secret' /><br/>"
assert_dom_equal expected, output_buffer
end
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index ac64096908..b8fad090c5 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -105,7 +105,7 @@ class FormHelperTest < ActionView::TestCase
@post = Post.new
@comment = Comment.new
- def @post.errors()
+ def @post.errors
Class.new {
def [](field); field == "author_name" ? ["can't be empty"] : [] end
def empty?() false end
@@ -612,7 +612,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_check_box_is_html_safe
- assert check_box("post", "secret").html_safe?
+ assert_predicate check_box("post", "secret"), :html_safe?
end
def test_check_box_checked_if_object_value_is_same_that_check_value
@@ -747,19 +747,19 @@ class FormHelperTest < ActionView::TestCase
end
def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_big_decimal
- @post.secret = BigDecimal.new(0)
+ @post.secret = BigDecimal(0)
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="1" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="0" />',
check_box("post", "secret", {}, 0, 1)
)
- @post.secret = BigDecimal.new(1)
+ @post.secret = BigDecimal(1)
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
check_box("post", "secret", {}, 0, 1)
)
- @post.secret = BigDecimal.new(2.2, 1)
+ @post.secret = BigDecimal(2.2, 1)
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
check_box("post", "secret", {}, 0, 1)
@@ -775,7 +775,7 @@ class FormHelperTest < ActionView::TestCase
end
def test_check_box_with_nil_unchecked_value_is_html_safe
- assert check_box("post", "secret", {}, "on", nil).html_safe?
+ assert_predicate check_box("post", "secret", {}, "on", nil), :html_safe?
end
def test_check_box_with_multiple_behavior
@@ -1560,6 +1560,38 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_is_not_affected_by_form_with_generates_ids
+ old_value = ActionView::Helpers::FormHelper.form_with_generates_ids
+ ActionView::Helpers::FormHelper.form_with_generates_ids = false
+
+ form_for(@post, html: { id: "create-post" }) do |f|
+ concat f.label(:title) { "The Title" }
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ concat f.submit("Create post")
+ concat f.button("Create post")
+ concat f.button {
+ concat content_tag(:span, "Create post")
+ }
+ end
+
+ expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do
+ "<label for='post_title'>The Title</label>" \
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \
+ "<input name='post[secret]' type='hidden' value='0' />" \
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" \
+ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" \
+ "<button name='button' type='submit'>Create post</button>" \
+ "<button name='button' type='submit'><span>Create post</span></button>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ ensure
+ ActionView::Helpers::FormHelper.form_with_generates_ids = old_value
+ end
+
def test_form_for_with_collection_radio_buttons
post = Post.new
def post.active; false; end
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index f0eed1e290..f82eada869 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -337,8 +337,24 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_option_groups_from_collection_for_select_with_callable_group_method
+ group_proc = Proc.new { |c| c.countries }
+ assert_dom_equal(
+ "<optgroup label=\"&lt;Africa&gt;\"><option value=\"&lt;sa&gt;\">&lt;South Africa&gt;</option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>",
+ option_groups_from_collection_for_select(dummy_continents, group_proc, "continent_name", "country_id", "country_name", "dk")
+ )
+ end
+
+ def test_option_groups_from_collection_for_select_with_callable_group_label_method
+ label_proc = Proc.new { |c| c.continent_name }
+ assert_dom_equal(
+ "<optgroup label=\"&lt;Africa&gt;\"><option value=\"&lt;sa&gt;\">&lt;South Africa&gt;</option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>",
+ option_groups_from_collection_for_select(dummy_continents, "countries", label_proc, "country_id", "country_name", "dk")
+ )
+ end
+
def test_option_groups_from_collection_for_select_returns_html_safe_string
- assert option_groups_from_collection_for_select(dummy_continents, "countries", "continent_name", "country_id", "country_name", "dk").html_safe?
+ assert_predicate option_groups_from_collection_for_select(dummy_continents, "countries", "continent_name", "country_id", "country_name", "dk"), :html_safe?
end
def test_grouped_options_for_select_with_array
@@ -367,7 +383,7 @@ class FormOptionsHelperTest < ActionView::TestCase
assert_dom_equal(
"<optgroup label=\"----------\"><option value=\"US\">US</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"----------\"><option value=\"GB\">GB</option>\n<option value=\"Germany\">Germany</option></optgroup>",
- grouped_options_for_select([["US", "Canada"] , ["GB", "Germany"]], nil, divider: "----------")
+ grouped_options_for_select([["US", "Canada"], ["GB", "Germany"]], nil, divider: "----------")
)
end
@@ -386,7 +402,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_grouped_options_for_select_returns_html_safe_string
- assert grouped_options_for_select([["Hats", ["Baseball Cap", "Cowboy Hat"]]]).html_safe?
+ assert_predicate grouped_options_for_select([["Hats", ["Baseball Cap", "Cowboy Hat"]]]), :html_safe?
end
def test_grouped_options_for_select_with_prompt_returns_html_escaped_string
@@ -476,7 +492,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_time_zone_options_returns_html_safe_string
- assert time_zone_options_for_select.html_safe?
+ assert_predicate time_zone_options_for_select, :html_safe?
end
def test_select
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index 5e328ebf53..0d9bf77f98 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -142,14 +142,14 @@ class FormTagHelperTest < ActionView::TestCase
actual = form_tag({}, { enforce_utf8: true })
expected = whole_form("http://www.example.com", enforce_utf8: true)
assert_dom_equal expected, actual
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
end
def test_form_tag_enforce_utf8_false
actual = form_tag({}, { enforce_utf8: false })
expected = whole_form("http://www.example.com", enforce_utf8: false)
assert_dom_equal expected, actual
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
end
def test_form_tag_with_block_in_erb
diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb
index 402ee9b6ae..beee76f711 100644
--- a/actionview/test/template/lookup_context_test.rb
+++ b/actionview/test/template/lookup_context_test.rb
@@ -35,7 +35,7 @@ class LookupContextTest < ActiveSupport::TestCase
test "allows me to freeze and retrieve frozen formats" do
@lookup_context.formats.freeze
- assert @lookup_context.formats.frozen?
+ assert_predicate @lookup_context.formats, :frozen?
end
test "provides getters and setters for variants" do
diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb
index 2b671a6685..357ae1326a 100644
--- a/actionview/test/template/number_helper_test.rb
+++ b/actionview/test/template/number_helper_test.rb
@@ -79,7 +79,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "1.23 &lt;b&gt;km3&lt;/b&gt;", number_to_human(1_234_567_000_000, units: volume)
assert_equal "1.23 &lt;b&gt;Pl&lt;/b&gt;", number_to_human(1_234_567_000_000_000, units: volume)
- #Including fractionals
+ # Including fractionals
distance = { mili: "<b>mm</b>", centi: "<b>cm</b>", deci: "<b>dm</b>", unit: "<b>m</b>",
ten: "<b>dam</b>", hundred: "<b>hm</b>", thousand: "<b>km</b>",
micro: "<b>um</b>", nano: "<b>nm</b>", pico: "<b>pm</b>", femto: "<b>fm</b>" }
@@ -126,43 +126,43 @@ class NumberHelperTest < ActionView::TestCase
end
def test_number_helpers_outputs_are_html_safe
- assert number_to_human(1).html_safe?
- assert !number_to_human("<script></script>").html_safe?
- assert number_to_human("asdf".html_safe).html_safe?
- assert number_to_human("1".html_safe).html_safe?
-
- assert number_to_human_size(1).html_safe?
- assert number_to_human_size(1000000).html_safe?
- assert !number_to_human_size("<script></script>").html_safe?
- assert number_to_human_size("asdf".html_safe).html_safe?
- assert number_to_human_size("1".html_safe).html_safe?
-
- assert number_with_precision(1, strip_insignificant_zeros: false).html_safe?
- assert number_with_precision(1, strip_insignificant_zeros: true).html_safe?
- assert !number_with_precision("<script></script>").html_safe?
- assert number_with_precision("asdf".html_safe).html_safe?
- assert number_with_precision("1".html_safe).html_safe?
-
- assert number_to_currency(1).html_safe?
- assert !number_to_currency("<script></script>").html_safe?
- assert number_to_currency("asdf".html_safe).html_safe?
- assert number_to_currency("1".html_safe).html_safe?
-
- assert number_to_percentage(1).html_safe?
- assert !number_to_percentage("<script></script>").html_safe?
- assert number_to_percentage("asdf".html_safe).html_safe?
- assert number_to_percentage("1".html_safe).html_safe?
-
- assert number_to_phone(1).html_safe?
+ assert_predicate number_to_human(1), :html_safe?
+ assert_not_predicate number_to_human("<script></script>"), :html_safe?
+ assert_predicate number_to_human("asdf".html_safe), :html_safe?
+ assert_predicate number_to_human("1".html_safe), :html_safe?
+
+ assert_predicate number_to_human_size(1), :html_safe?
+ assert_predicate number_to_human_size(1000000), :html_safe?
+ assert_not_predicate number_to_human_size("<script></script>"), :html_safe?
+ assert_predicate number_to_human_size("asdf".html_safe), :html_safe?
+ assert_predicate number_to_human_size("1".html_safe), :html_safe?
+
+ assert_predicate number_with_precision(1, strip_insignificant_zeros: false), :html_safe?
+ assert_predicate number_with_precision(1, strip_insignificant_zeros: true), :html_safe?
+ assert_not_predicate number_with_precision("<script></script>"), :html_safe?
+ assert_predicate number_with_precision("asdf".html_safe), :html_safe?
+ assert_predicate number_with_precision("1".html_safe), :html_safe?
+
+ assert_predicate number_to_currency(1), :html_safe?
+ assert_not_predicate number_to_currency("<script></script>"), :html_safe?
+ assert_predicate number_to_currency("asdf".html_safe), :html_safe?
+ assert_predicate number_to_currency("1".html_safe), :html_safe?
+
+ assert_predicate number_to_percentage(1), :html_safe?
+ assert_not_predicate number_to_percentage("<script></script>"), :html_safe?
+ assert_predicate number_to_percentage("asdf".html_safe), :html_safe?
+ assert_predicate number_to_percentage("1".html_safe), :html_safe?
+
+ assert_predicate number_to_phone(1), :html_safe?
assert_equal "&lt;script&gt;&lt;/script&gt;", number_to_phone("<script></script>")
- assert number_to_phone("<script></script>").html_safe?
- assert number_to_phone("asdf".html_safe).html_safe?
- assert number_to_phone("1".html_safe).html_safe?
-
- assert number_with_delimiter(1).html_safe?
- assert !number_with_delimiter("<script></script>").html_safe?
- assert number_with_delimiter("asdf".html_safe).html_safe?
- assert number_with_delimiter("1".html_safe).html_safe?
+ assert_predicate number_to_phone("<script></script>"), :html_safe?
+ assert_predicate number_to_phone("asdf".html_safe), :html_safe?
+ assert_predicate number_to_phone("1".html_safe), :html_safe?
+
+ assert_predicate number_with_delimiter(1), :html_safe?
+ assert_not_predicate number_with_delimiter("<script></script>"), :html_safe?
+ assert_predicate number_with_delimiter("asdf".html_safe), :html_safe?
+ assert_predicate number_with_delimiter("1".html_safe), :html_safe?
end
def test_number_helpers_should_raise_error_if_invalid_when_specified
diff --git a/actionview/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb
index b5e9a77105..faeeded1c8 100644
--- a/actionview/test/template/output_safety_helper_test.rb
+++ b/actionview/test/template/output_safety_helper_test.rb
@@ -12,7 +12,7 @@ class OutputSafetyHelperTest < ActionView::TestCase
test "raw returns the safe string" do
result = raw(@string)
assert_equal @string, result
- assert result.html_safe?
+ assert_predicate result, :html_safe?
end
test "raw handles nil values correctly" do
@@ -53,11 +53,11 @@ class OutputSafetyHelperTest < ActionView::TestCase
test "to_sentence should escape non-html_safe values" do
actual = to_sentence(%w(< > & ' "))
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
assert_equal("&lt;, &gt;, &amp;, &#39;, and &quot;", actual)
actual = to_sentence(%w(<script>))
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
assert_equal("&lt;script&gt;", actual)
end
@@ -80,19 +80,19 @@ class OutputSafetyHelperTest < ActionView::TestCase
url = "https://example.com"
expected = %(<a href="#{url}">#{url}</a> and <p>&lt;marquee&gt;shady stuff&lt;/marquee&gt;<br /></p>)
actual = to_sentence([link_to(url, url), ptag])
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
assert_equal(expected, actual)
end
test "to_sentence handles blank strings" do
actual = to_sentence(["", "two", "three"])
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
assert_equal ", two, and three", actual
end
test "to_sentence handles nil values" do
actual = to_sentence([nil, "two", "three"])
- assert actual.html_safe?
+ assert_predicate actual, :html_safe?
assert_equal ", two, and three", actual
end
diff --git a/actionview/test/template/sanitize_helper_test.rb b/actionview/test/template/sanitize_helper_test.rb
index 0e690c82cb..181f09ab65 100644
--- a/actionview/test/template/sanitize_helper_test.rb
+++ b/actionview/test/template/sanitize_helper_test.rb
@@ -38,6 +38,6 @@ class SanitizeHelperTest < ActionView::TestCase
end
def test_sanitize_is_marked_safe
- assert sanitize("<html><script></script></html>").html_safe?
+ assert_predicate sanitize("<html><script></script></html>"), :html_safe?
end
end
diff --git a/actionview/test/template/streaming_render_test.rb b/actionview/test/template/streaming_render_test.rb
index 23edf7b538..ef000300cc 100644
--- a/actionview/test/template/streaming_render_test.rb
+++ b/actionview/test/template/streaming_render_test.rb
@@ -5,7 +5,7 @@ require "abstract_unit"
class TestController < ActionController::Base
end
-class FiberedTest < ActiveSupport::TestCase
+class SetupFiberedBase < ActiveSupport::TestCase
def setup
view_paths = ActionController::Base.view_paths
@assigns = { secret: "in the sauce", name: nil }
@@ -25,7 +25,9 @@ class FiberedTest < ActiveSupport::TestCase
end
string
end
+end
+class FiberedTest < SetupFiberedBase
def test_streaming_works
content = []
body = render_body(template: "test/hello_world", layout: "layouts/yield")
@@ -111,3 +113,20 @@ class FiberedTest < ActiveSupport::TestCase
buffered_render(template: "test/streaming", layout: "layouts/streaming_with_capture")
end
end
+
+class FiberedWithLocaleTest < SetupFiberedBase
+ def setup
+ @old_locale = I18n.locale
+ I18n.locale = "da"
+ super
+ end
+
+ def teardown
+ I18n.locale = @old_locale
+ end
+
+ def test_render_with_streaming_and_locale
+ assert_equal "layout.locale: da\nview.locale: da\n\n",
+ buffered_render(template: "test/streaming_with_locale", layout: "layouts/streaming_with_locale")
+ end
+end
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index 8c57803796..9a6226fd04 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -81,7 +81,7 @@ class TagHelperTest < ActionView::TestCase
def test_content_tag
assert_equal "<a href=\"create\">Create</a>", content_tag("a", "Create", "href" => "create")
- assert content_tag("a", "Create", "href" => "create").html_safe?
+ assert_predicate content_tag("a", "Create", "href" => "create"), :html_safe?
assert_equal content_tag("a", "Create", "href" => "create"),
content_tag("a", "Create", href: "create")
assert_equal "<p>&lt;script&gt;evil_js&lt;/script&gt;</p>",
@@ -92,7 +92,7 @@ class TagHelperTest < ActionView::TestCase
def test_tag_builder_with_content
assert_equal "<div id=\"post_1\">Content</div>", tag.div("Content", id: "post_1")
- assert tag.div("Content", id: "post_1").html_safe?
+ assert_predicate tag.div("Content", id: "post_1"), :html_safe?
assert_equal tag.div("Content", id: "post_1"),
tag.div("Content", "id": "post_1")
assert_equal "<p>&lt;script&gt;evil_js&lt;/script&gt;</p>",
@@ -307,8 +307,8 @@ class TagHelperTest < ActionView::TestCase
def test_tag_builder_disable_escaping
assert_equal '<a href="&amp;"></a>', tag.a(href: "&amp;", escape_attributes: false)
- assert_equal '<a href="&amp;">cnt</a>', tag.a(href: "&amp;" , escape_attributes: false) { "cnt" }
- assert_equal '<br data-hidden="&amp;">', tag.br("data-hidden": "&amp;" , escape_attributes: false)
+ assert_equal '<a href="&amp;">cnt</a>', tag.a(href: "&amp;", escape_attributes: false) { "cnt" }
+ assert_equal '<br data-hidden="&amp;">', tag.br("data-hidden": "&amp;", escape_attributes: false)
assert_equal '<a href="&amp;">content</a>', tag.a("content", href: "&amp;", escape_attributes: false)
assert_equal '<a href="&amp;">content</a>', tag.a(href: "&amp;", escape_attributes: false) { "content" }
end
@@ -316,18 +316,18 @@ class TagHelperTest < ActionView::TestCase
def test_data_attributes
["data", :data].each { |data|
assert_dom_equal '<a data-a-float="3.14" data-a-big-decimal="-123.456" data-a-number="1" data-array="[1,2,3]" data-hash="{&quot;key&quot;:&quot;value&quot;}" data-string-with-quotes="double&quot;quote&quot;party&quot;" data-string="hello" data-symbol="foo" />',
- tag("a", data => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
+ tag("a", data => { a_float: 3.14, a_big_decimal: BigDecimal("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
assert_dom_equal '<a data-a-float="3.14" data-a-big-decimal="-123.456" data-a-number="1" data-array="[1,2,3]" data-hash="{&quot;key&quot;:&quot;value&quot;}" data-string-with-quotes="double&quot;quote&quot;party&quot;" data-string="hello" data-symbol="foo" />',
- tag.a(data: { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
+ tag.a(data: { a_float: 3.14, a_big_decimal: BigDecimal("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
}
end
def test_aria_attributes
["aria", :aria].each { |aria|
assert_dom_equal '<a aria-a-float="3.14" aria-a-big-decimal="-123.456" aria-a-number="1" aria-array="[1,2,3]" aria-hash="{&quot;key&quot;:&quot;value&quot;}" aria-string-with-quotes="double&quot;quote&quot;party&quot;" aria-string="hello" aria-symbol="foo" />',
- tag("a", aria => { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
+ tag("a", aria => { a_float: 3.14, a_big_decimal: BigDecimal("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
assert_dom_equal '<a aria-a-float="3.14" aria-a-big-decimal="-123.456" aria-a-number="1" aria-array="[1,2,3]" aria-hash="{&quot;key&quot;:&quot;value&quot;}" aria-string-with-quotes="double&quot;quote&quot;party&quot;" aria-string="hello" aria-symbol="foo" />',
- tag.a(aria: { a_float: 3.14, a_big_decimal: BigDecimal.new("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
+ tag.a(aria: { a_float: 3.14, a_big_decimal: BigDecimal("-123.456"), a_number: 1, string: "hello", symbol: :foo, array: [1, 2, 3], hash: { key: "value" }, string_with_quotes: 'double"quote"party"' })
}
end
diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb
index f247de066f..45edfe18be 100644
--- a/actionview/test/template/text_helper_test.rb
+++ b/actionview/test/template/text_helper_test.rb
@@ -19,12 +19,12 @@ class TextHelperTest < ActionView::TestCase
end
def test_simple_format_should_be_html_safe
- assert simple_format("<b> test with html tags </b>").html_safe?
+ assert_predicate simple_format("<b> test with html tags </b>"), :html_safe?
end
def test_simple_format_included_in_isolation
helper_klass = Class.new { include ActionView::Helpers::TextHelper }
- assert helper_klass.new.simple_format("<b> test with html tags </b>").html_safe?
+ assert_predicate helper_klass.new.simple_format("<b> test with html tags </b>"), :html_safe?
end
def test_simple_format
@@ -123,7 +123,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_truncate_should_be_html_safe
- assert truncate("Hello World!", length: 12).html_safe?
+ assert_predicate truncate("Hello World!", length: 12), :html_safe?
end
def test_truncate_should_escape_the_input
@@ -136,12 +136,12 @@ class TextHelperTest < ActionView::TestCase
def test_truncate_with_escape_false_should_be_html_safe
truncated = truncate("Hello <script>code!</script>World!!", length: 12, escape: false)
- assert truncated.html_safe?
+ assert_predicate truncated, :html_safe?
end
def test_truncate_with_block_should_be_html_safe
truncated = truncate("Here's a long test and I need a continue to read link", length: 27) { link_to "Continue", "#" }
- assert truncated.html_safe?
+ assert_predicate truncated, :html_safe?
end
def test_truncate_with_block_should_escape_the_input
@@ -156,7 +156,7 @@ class TextHelperTest < ActionView::TestCase
def test_truncate_with_block_with_escape_false_should_be_html_safe
truncated = truncate("<script>code!</script>Here's a long test and I need a continue to read link", length: 27, escape: false) { link_to "Continue", "#" }
- assert truncated.html_safe?
+ assert_predicate truncated, :html_safe?
end
def test_truncate_with_block_should_escape_the_block
@@ -165,7 +165,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_highlight_should_be_html_safe
- assert highlight("This is a beautiful morning", "beautiful").html_safe?
+ assert_predicate highlight("This is a beautiful morning", "beautiful"), :html_safe?
end
def test_highlight
@@ -297,7 +297,7 @@ class TextHelperTest < ActionView::TestCase
end
def test_excerpt_should_not_be_html_safe
- assert !excerpt("This is a beautiful! morning", "beautiful", radius: 5).html_safe?
+ assert_not_predicate excerpt("This is a beautiful! morning", "beautiful", radius: 5), :html_safe?
end
def test_excerpt_in_borderline_cases
diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb
index 8956a584ff..f40595bf4d 100644
--- a/actionview/test/template/translation_helper_test.rb
+++ b/actionview/test/template/translation_helper_test.rb
@@ -75,7 +75,7 @@ class TranslationHelperTest < ActiveSupport::TestCase
def test_returns_missing_translation_message_with_unescaped_interpolation
expected = '<span class="translation_missing" title="translation missing: en.translations.missing, name: Kir, year: 2015, vulnerable: &amp;quot; onclick=&amp;quot;alert()&amp;quot;">Missing</span>'
assert_equal expected, translate(:"translations.missing", name: "Kir", year: "2015", vulnerable: %{" onclick="alert()"})
- assert translate(:"translations.missing").html_safe?
+ assert_predicate translate(:"translations.missing"), :html_safe?
end
def test_returns_missing_translation_message_does_filters_out_i18n_options
@@ -145,11 +145,11 @@ class TranslationHelperTest < ActiveSupport::TestCase
end
def test_translate_marks_translations_named_html_as_safe_html
- assert translate(:'translations.html').html_safe?
+ assert_predicate translate(:'translations.html'), :html_safe?
end
def test_translate_marks_translations_with_a_html_suffix_as_safe_html
- assert translate(:'translations.hello_html').html_safe?
+ assert_predicate translate(:'translations.hello_html'), :html_safe?
end
def test_translate_escapes_interpolations_in_translations_with_a_html_suffix
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 0cd0386cac..8bccda481b 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -663,7 +663,7 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_mail_to_returns_html_safe_string
- assert mail_to("david@loudthinking.com").html_safe?
+ assert_predicate mail_to("david@loudthinking.com"), :html_safe?
end
def test_mail_to_with_block
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 0c69a5c663..ec99b5d975 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,5 +1,3 @@
-* 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.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activejob/CHANGELOG.md) for previous changes.
diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE
index daa726b9f0..274211f710 100644
--- a/activejob/MIT-LICENSE
+++ b/activejob/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2017 David Heinemeier Hansson
+Copyright (c) 2014-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activejob/README.md b/activejob/README.md
index 8a9a23929b..f1ebb76e08 100644
--- a/activejob/README.md
+++ b/activejob/README.md
@@ -100,7 +100,7 @@ The latest version of Active Job can be installed with RubyGems:
$ gem install activejob
```
-Source code can be downloaded as part of the Rails project on GitHub
+Source code can be downloaded as part of the Rails project on GitHub:
* https://github.com/rails/rails/tree/master/activejob
@@ -117,7 +117,7 @@ API documentation is at:
* http://api.rubyonrails.org
-Bug reports can be filed for the Ruby on Rails project here:
+Bug reports for the Ruby on Rails project can be filed here:
* https://github.com/rails/rails/issues
diff --git a/activejob/Rakefile b/activejob/Rakefile
index 6f13ef449d..77f3e7f8ff 100644
--- a/activejob/Rakefile
+++ b/activejob/Rakefile
@@ -2,7 +2,7 @@
require "rake/testtask"
-#TODO: add qu back to the list after it support Rails 5.1
+# TODO: add qu back to the list after it support Rails 5.1
ACTIVEJOB_ADAPTERS = %w(async inline delayed_job que queue_classic resque sidekiq sneakers sucker_punch backburner test)
ACTIVEJOB_ADAPTERS.delete("queue_classic") if defined?(JRUBY_VERSION)
diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb
index 1f5bd7b3e9..626abaa767 100644
--- a/activejob/lib/active_job.rb
+++ b/activejob/lib/active_job.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2014-2017 David Heinemeier Hansson
+# Copyright (c) 2014-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activejob/lib/active_job/core.rb b/activejob/lib/active_job/core.rb
index c4e12fc518..879746fc01 100644
--- a/activejob/lib/active_job/core.rb
+++ b/activejob/lib/active_job/core.rb
@@ -97,17 +97,23 @@ module ActiveJob
# ==== Examples
#
# class DeliverWebhookJob < ActiveJob::Base
+ # attr_writer :attempt_number
+ #
+ # def attempt_number
+ # @attempt_number ||= 0
+ # end
+ #
# def serialize
- # super.merge('attempt_number' => (@attempt_number || 0) + 1)
+ # super.merge('attempt_number' => attempt_number + 1)
# end
#
# def deserialize(job_data)
# super
- # @attempt_number = job_data['attempt_number']
+ # self.attempt_number = job_data['attempt_number']
# end
#
- # rescue_from(TimeoutError) do |exception|
- # raise exception if @attempt_number > 5
+ # rescue_from(Timeout::Error) do |exception|
+ # raise exception if attempt_number > 5
# retry_job(wait: 10)
# end
# end
diff --git a/activejob/lib/active_job/exceptions.rb b/activejob/lib/active_job/exceptions.rb
index 8b4a88ba6a..ae700848d0 100644
--- a/activejob/lib/active_job/exceptions.rb
+++ b/activejob/lib/active_job/exceptions.rb
@@ -61,18 +61,28 @@ module ActiveJob
# Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job,
# like an Active Record, is no longer available, and the job is thus no longer relevant.
#
+ # You can also pass a block that'll be invoked. This block is yielded with the job instance as the first and the error instance as the second parameter.
+ #
# ==== Example
#
# class SearchIndexingJob < ActiveJob::Base
# discard_on ActiveJob::DeserializationError
+ # discard_on(CustomAppException) do |job, exception|
+ # ExceptionNotifier.caught(exception)
+ # end
#
# def perform(record)
# # Will raise ActiveJob::DeserializationError if the record can't be deserialized
+ # # Might raise CustomAppException for something domain specific
# end
# end
def discard_on(exception)
rescue_from exception do |error|
- logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}."
+ if block_given?
+ yield self, exception
+ else
+ logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}."
+ end
end
end
end
diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb
index 7ee61780e1..770f70dc5e 100644
--- a/activejob/lib/active_job/gem_version.rb
+++ b/activejob/lib/active_job/gem_version.rb
@@ -7,8 +7,8 @@ module ActiveJob
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index dd05800baf..006a683b85 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -29,28 +29,22 @@ module ActiveJob
# Specify the backend queue provider. The default queue adapter
# is the +:async+ queue. See QueueAdapters for more
# information.
- def queue_adapter=(name_or_adapter_or_class)
- interpret_adapter(name_or_adapter_or_class)
- end
-
- private
-
- def interpret_adapter(name_or_adapter_or_class)
- case name_or_adapter_or_class
- when Symbol, String
- assign_adapter(name_or_adapter_or_class.to_s,
- ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new)
+ def queue_adapter=(name_or_adapter)
+ case name_or_adapter
+ when Symbol, String
+ queue_adapter = ActiveJob::QueueAdapters.lookup(name_or_adapter).new
+ assign_adapter(name_or_adapter.to_s, queue_adapter)
+ else
+ if queue_adapter?(name_or_adapter)
+ adapter_name = "#{name_or_adapter.class.name.demodulize.remove('Adapter').underscore}"
+ assign_adapter(adapter_name, name_or_adapter)
else
- if queue_adapter?(name_or_adapter_or_class)
- adapter_name = "#{name_or_adapter_or_class.class.name.demodulize.remove('Adapter').underscore}"
- assign_adapter(adapter_name,
- name_or_adapter_or_class)
- else
- raise ArgumentError
- end
+ raise ArgumentError
end
end
+ end
+ private
def assign_adapter(adapter_name, queue_adapter)
self._queue_adapter_name = adapter_name
self._queue_adapter = queue_adapter
diff --git a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
index 1978179948..8eeef32b99 100644
--- a/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/delayed_job_adapter.rb
@@ -34,6 +34,10 @@ module ActiveJob
@job_data = job_data
end
+ def display_name
+ "#{job_data['job_class']} [#{job_data['job_id']}] from DelayedJob(#{job_data['queue_name']}) with arguments: #{job_data['arguments']}"
+ end
+
def perform
Base.execute(job_data)
end
diff --git a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
index 5a1135854b..f726e6ad93 100644
--- a/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/sidekiq_adapter.rb
@@ -18,7 +18,7 @@ module ActiveJob
# Rails.application.config.active_job.queue_adapter = :sidekiq
class SidekiqAdapter
def enqueue(job) #:nodoc:
- #Sidekiq::Client does not support symbols as keys
+ # Sidekiq::Client does not support symbols as keys
job.provider_job_id = Sidekiq::Client.push \
"class" => JobWrapper,
"wrapped" => job.class.to_s,
diff --git a/activejob/lib/rails/generators/job/templates/application_job.rb b/activejob/lib/rails/generators/job/templates/application_job.rb.tt
index f93745a31a..f93745a31a 100644
--- a/activejob/lib/rails/generators/job/templates/application_job.rb
+++ b/activejob/lib/rails/generators/job/templates/application_job.rb.tt
diff --git a/activejob/lib/rails/generators/job/templates/job.rb b/activejob/lib/rails/generators/job/templates/job.rb.tt
index 4ad2914a45..4ad2914a45 100644
--- a/activejob/lib/rails/generators/job/templates/job.rb
+++ b/activejob/lib/rails/generators/job/templates/job.rb.tt
diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb
index 13e6fcb727..7e7f854da0 100644
--- a/activejob/test/cases/argument_serialization_test.rb
+++ b/activejob/test/cases/argument_serialization_test.rb
@@ -12,7 +12,7 @@ class ArgumentSerializationTest < ActiveSupport::TestCase
end
[ nil, 1, 1.0, 1_000_000_000_000_000_000_000,
- "a", true, false, BigDecimal.new(5),
+ "a", true, false, BigDecimal(5),
[ 1, "a" ],
{ "a" => 1 }
].each do |arg|
diff --git a/activejob/test/cases/exceptions_test.rb b/activejob/test/cases/exceptions_test.rb
index 22fed0a808..bc33d79f61 100644
--- a/activejob/test/cases/exceptions_test.rb
+++ b/activejob/test/cases/exceptions_test.rb
@@ -58,6 +58,13 @@ class ExceptionsTest < ActiveJob::TestCase
end
end
+ test "custom handling of discarded job" do
+ perform_enqueued_jobs do
+ RetryJob.perform_later "CustomDiscardableError", 2
+ assert_equal "Dealt with a job that was discarded in a custom way", JobBuffer.last_value
+ end
+ end
+
test "custom handling of job that exceeds retry attempts" do
perform_enqueued_jobs do
RetryJob.perform_later "CustomCatchError", 6
diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb
index 0d8aa336a6..32ef485c45 100644
--- a/activejob/test/integration/queuing_test.rb
+++ b/activejob/test/integration/queuing_test.rb
@@ -45,6 +45,13 @@ class QueuingTest < ActiveSupport::TestCase
end
end
+ test "should supply a wrapped class name to DelayedJob" do
+ skip unless adapter_is?(:delayed_job)
+ ::HelloJob.perform_later
+ job = Delayed::Job.first
+ assert_match(/HelloJob \[[0-9a-f-]+\] from DelayedJob\(default\) with arguments: \[\]/, job.name)
+ end
+
test "resque JobWrapper should have instance variable queue" do
skip unless adapter_is?(:resque)
job = ::HelloJob.set(wait: 5.seconds).perform_later
diff --git a/activejob/test/jobs/retry_job.rb b/activejob/test/jobs/retry_job.rb
index 9aa99d9a21..82b80fe12b 100644
--- a/activejob/test/jobs/retry_job.rb
+++ b/activejob/test/jobs/retry_job.rb
@@ -10,6 +10,7 @@ class ExponentialWaitTenAttemptsError < StandardError; end
class CustomWaitTenAttemptsError < StandardError; end
class CustomCatchError < StandardError; end
class DiscardableError < StandardError; end
+class CustomDiscardableError < StandardError; end
class RetryJob < ActiveJob::Base
retry_on DefaultsError
@@ -19,6 +20,7 @@ class RetryJob < ActiveJob::Base
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. Message: #{exception.message}") }
discard_on DiscardableError
+ discard_on(CustomDiscardableError) { |job, exception| JobBuffer.add("Dealt with a job that was discarded in a custom way") }
def perform(raising, attempts)
if executions < attempts
diff --git a/activejob/test/support/delayed_job/delayed/backend/test.rb b/activejob/test/support/delayed_job/delayed/backend/test.rb
index 9280a37a5c..1691896b7c 100644
--- a/activejob/test/support/delayed_job/delayed/backend/test.rb
+++ b/activejob/test/support/delayed_job/delayed/backend/test.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-#copied from https://github.com/collectiveidea/delayed_job/blob/master/spec/delayed/backend/test.rb
+# copied from https://github.com/collectiveidea/delayed_job/blob/master/spec/delayed/backend/test.rb
require "ostruct"
# An in-memory backend suitable only for testing. Tries to behave as if it were an ORM.
diff --git a/activejob/test/support/integration/adapters/resque.rb b/activejob/test/support/integration/adapters/resque.rb
index 7d5174b957..2ed8302277 100644
--- a/activejob/test/support/integration/adapters/resque.rb
+++ b/activejob/test/support/integration/adapters/resque.rb
@@ -3,11 +3,12 @@
module ResqueJobsManager
def setup
ActiveJob::Base.queue_adapter = :resque
- 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.redis = Redis::Namespace.new "active_jobs_int_test", redis: Redis.new(url: "redis://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"
- exit
+ status = ENV["CI"] ? false : true
+ exit status
end
end
diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb
index ceb7fb61f2..c79de12eaf 100644
--- a/activejob/test/support/integration/adapters/sidekiq.rb
+++ b/activejob/test/support/integration/adapters/sidekiq.rb
@@ -5,20 +5,13 @@ require "sidekiq/api"
require "sidekiq/testing"
Sidekiq::Testing.disable!
-Sidekiq.configure_server do |config|
- config.redis = { url: "redis://:password@127.0.0.1:6379/12" }
-end
-
-Sidekiq.configure_client do |config|
- config.redis = { url: "redis://:password@127.0.0.1:6379/12" }
-end
-
module SidekiqJobsManager
def setup
ActiveJob::Base.queue_adapter = :sidekiq
unless can_run?
puts "Cannot run integration tests for sidekiq. To be able to run integration tests for sidekiq you need to install and start redis.\n"
- exit
+ status = ENV["CI"] ? false : true
+ exit status
end
end
@@ -58,6 +51,7 @@ module SidekiqJobsManager
self_write.puts("TERM")
end
+ require "sidekiq/cli"
require "sidekiq/launcher"
sidekiq = Sidekiq::Launcher.new(queues: ["integration_tests"],
environment: "test",
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 794744c646..13ea7d0bf3 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,49 +1,3 @@
-* Execute `ConfirmationValidator` validation when `_confirmation`'s value is `false`.
- *bogdanvlviv*
-* Allow passing a Proc or Symbol to length validator options.
-
- *Matt Rohrer*
-
-* Add method `#merge!` for `ActiveModel::Errors`.
-
- *Jahfer Husain*
-
-* Fix regression in numericality validator when comparing Decimal and Float input
- values with more scale than the schema.
-
- *Bradley Priest*
-
-* Fix methods `#keys`, `#values` in `ActiveModel::Errors`.
-
- Change `#keys` to only return the keys that don't have empty messages.
-
- Change `#values` to only return the not empty values.
-
- Example:
-
- # Before
- person = Person.new
- person.errors.keys # => []
- person.errors.values # => []
- person.errors.messages # => {}
- person.errors[:name] # => []
- person.errors.messages # => {:name => []}
- person.errors.keys # => [:name]
- person.errors.values # => [[]]
-
- # After
- person = Person.new
- person.errors.keys # => []
- person.errors.values # => []
- person.errors.messages # => {}
- person.errors[:name] # => []
- person.errors.messages # => {:name => []}
- person.errors.keys # => []
- person.errors.values # => []
-
- *bogdanvlviv*
-
-
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activemodel/CHANGELOG.md) for previous changes.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activemodel/CHANGELOG.md) for previous changes.
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index ac810e86d0..1cb3add0fc 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2017 David Heinemeier Hansson
+Copyright (c) 2004-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 772df0f8f6..1aaf4813ea 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -251,11 +251,11 @@ Active Model is released under the MIT license:
== Support
-API documentation is at
+API documentation is at:
* http://api.rubyonrails.org
-Bug reports can be filed for the Ruby on Rails project here:
+Bug reports for the Ruby on Rails project can be filed here:
* https://github.com/rails/rails/issues
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index a3884206a6..bc10d6b4b9 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2017 David Heinemeier Hansson
+# Copyright (c) 2004-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activemodel/lib/active_model/attribute.rb b/activemodel/lib/active_model/attribute.rb
index b75ff80b31..27f3e38e31 100644
--- a/activemodel/lib/active_model/attribute.rb
+++ b/activemodel/lib/active_model/attribute.rb
@@ -233,6 +233,10 @@ module ActiveModel
false
end
+ def forgetting_assignment
+ dup
+ end
+
def with_type(type)
self.class.new(name, type)
end
diff --git a/activemodel/lib/active_model/attribute/user_provided_default.rb b/activemodel/lib/active_model/attribute/user_provided_default.rb
index f274b687d4..a5dc6188d2 100644
--- a/activemodel/lib/active_model/attribute/user_provided_default.rb
+++ b/activemodel/lib/active_model/attribute/user_provided_default.rb
@@ -22,6 +22,28 @@ module ActiveModel
self.class.new(name, user_provided_value, type, original_attribute)
end
+ def marshal_dump
+ result = [
+ name,
+ value_before_type_cast,
+ type,
+ original_attribute,
+ ]
+ result << value if defined?(@value)
+ result
+ end
+
+ def marshal_load(values)
+ name, user_provided_value, type, original_attribute, value = values
+ @name = name
+ @user_provided_value = user_provided_value
+ @type = type
+ @original_attribute = original_attribute
+ if values.length == 5
+ @value = value
+ end
+ end
+
protected
attr_reader :user_provided_value
diff --git a/activemodel/lib/active_model/attribute_mutation_tracker.rb b/activemodel/lib/active_model/attribute_mutation_tracker.rb
index c67e1b809a..f55613ecd5 100644
--- a/activemodel/lib/active_model/attribute_mutation_tracker.rb
+++ b/activemodel/lib/active_model/attribute_mutation_tracker.rb
@@ -35,6 +35,10 @@ module ActiveModel
end
end
+ def changed_attribute_names
+ attr_names.select { |attr| changed?(attr) }
+ end
+
def any_changes?
attr_names.any? { |attr| changed?(attr) }
end
@@ -109,8 +113,5 @@ module ActiveModel
def original_value(*)
end
-
- def force_change(*)
- end
end
end
diff --git a/activemodel/lib/active_model/attribute_set.rb b/activemodel/lib/active_model/attribute_set.rb
index a892accbc6..a890ee3932 100644
--- a/activemodel/lib/active_model/attribute_set.rb
+++ b/activemodel/lib/active_model/attribute_set.rb
@@ -1,11 +1,12 @@
# frozen_string_literal: true
+require "active_support/core_ext/object/deep_dup"
require "active_model/attribute_set/builder"
require "active_model/attribute_set/yaml_encoder"
module ActiveModel
class AttributeSet # :nodoc:
- delegate :each_value, :fetch, to: :attributes
+ delegate :each_value, :fetch, :except, to: :attributes
def initialize(attributes)
@attributes = attributes
diff --git a/activemodel/lib/active_model/attribute_set/builder.rb b/activemodel/lib/active_model/attribute_set/builder.rb
index f94f47370f..bf2d06b48a 100644
--- a/activemodel/lib/active_model/attribute_set/builder.rb
+++ b/activemodel/lib/active_model/attribute_set/builder.rb
@@ -5,35 +5,30 @@ require "active_model/attribute"
module ActiveModel
class AttributeSet # :nodoc:
class Builder # :nodoc:
- attr_reader :types, :always_initialized, :default
+ attr_reader :types, :default_attributes
- def initialize(types, always_initialized = nil, &default)
+ def initialize(types, default_attributes = {})
@types = types
- @always_initialized = always_initialized
- @default = default
+ @default_attributes = default_attributes
end
def build_from_database(values = {}, additional_types = {})
- if always_initialized && !values.key?(always_initialized)
- values[always_initialized] = nil
- end
-
- attributes = LazyAttributeHash.new(types, values, additional_types, &default)
+ attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes)
AttributeSet.new(attributes)
end
end
end
class LazyAttributeHash # :nodoc:
- delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize
+ delegate :transform_values, :each_key, :each_value, :fetch, :except, to: :materialize
- def initialize(types, values, additional_types, &default)
+ def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
@types = types
@values = values
@additional_types = additional_types
@materialized = false
- @delegate_hash = {}
- @default = default || proc {}
+ @delegate_hash = delegate_hash
+ @default_attributes = default_attributes
end
def key?(key)
@@ -81,20 +76,22 @@ module ActiveModel
end
def marshal_dump
- materialize
+ [@types, @values, @additional_types, @default_attributes, @delegate_hash]
end
- def marshal_load(delegate_hash)
- @delegate_hash = delegate_hash
- @types = {}
- @values = {}
- @additional_types = {}
- @materialized = true
+ def marshal_load(values)
+ if values.is_a?(Hash)
+ empty_hash = {}.freeze
+ initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
+ @materialized = true
+ else
+ initialize(*values)
+ end
end
protected
- attr_reader :types, :values, :additional_types, :delegate_hash, :default
+ attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
def materialize
unless @materialized
@@ -117,7 +114,12 @@ module ActiveModel
if value_present
delegate_hash[name] = Attribute.from_database(name, value, type)
elsif types.key?(name)
- delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type)
+ attr = default_attributes[name]
+ if attr
+ delegate_hash[name] = attr.dup
+ else
+ delegate_hash[name] = Attribute.uninitialized(name, type)
+ end
end
end
end
diff --git a/activemodel/lib/active_model/attributes.rb b/activemodel/lib/active_model/attributes.rb
index cac461b549..046ae67ad7 100644
--- a/activemodel/lib/active_model/attributes.rb
+++ b/activemodel/lib/active_model/attributes.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-require "active_support/core_ext/object/deep_dup"
require "active_model/attribute_set"
require "active_model/attribute/user_provided_default"
@@ -24,7 +23,7 @@ module ActiveModel
end
self.attribute_types = attribute_types.merge(name => type)
define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
- define_attribute_methods(name)
+ define_attribute_method(name)
end
private
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index d2ebd18107..0044fde6c5 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -3,6 +3,7 @@
require "active_support/hash_with_indifferent_access"
require "active_support/core_ext/object/duplicable"
require "active_model/attribute_mutation_tracker"
+require "active_model/attribute_set"
module ActiveModel
# == Active \Model \Dirty
@@ -142,9 +143,8 @@ module ActiveModel
end
def changes_applied # :nodoc:
- @previously_changed = changes
+ _prepare_changes
@mutations_before_last_save = mutations_from_database
- @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
@mutations_from_database = nil
end
@@ -155,7 +155,7 @@ module ActiveModel
# person.name = 'bob'
# person.changed? # => true
def changed?
- changed_attributes.present?
+ mutations_from_database.any_changes?
end
# Returns an array with the name of the attributes with unsaved changes.
@@ -164,24 +164,24 @@ module ActiveModel
# person.name = 'bob'
# person.changed # => ["name"]
def changed
- changed_attributes.keys
+ mutations_from_database.changed_attribute_names
end
# Handles <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc:
- !!changes_include?(attr) &&
+ !!mutations_from_database.changed?(attr) &&
(to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) &&
- (from == OPTION_NOT_GIVEN || from == changed_attributes[attr])
+ (from == OPTION_NOT_GIVEN || from == attribute_was(attr))
end
# Handles <tt>*_was</tt> for +method_missing+.
def attribute_was(attr) # :nodoc:
- attribute_changed?(attr) ? changed_attributes[attr] : _read_attribute(attr)
+ mutations_from_database.original_value(attr)
end
# Handles <tt>*_previously_changed?</tt> for +method_missing+.
def attribute_previously_changed?(attr) #:nodoc:
- previous_changes_include?(attr)
+ mutations_before_last_save.changed?(attr)
end
# Restore all previous data of the provided attributes.
@@ -191,15 +191,12 @@ module ActiveModel
# Clears all dirty data: current changes and previous changes.
def clear_changes_information
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@mutations_before_last_save = nil
- @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
forget_attribute_assignments
@mutations_from_database = nil
end
def clear_attribute_changes(attr_names)
- attributes_changed_by_setter.except!(*attr_names)
attr_names.each do |attr_name|
clear_attribute_change(attr_name)
end
@@ -212,13 +209,7 @@ module ActiveModel
# person.name = 'robert'
# person.changed_attributes # => {"name" => "bob"}
def changed_attributes
- # This should only be set by methods which will call changed_attributes
- # multiple times when it is known that the computed value cannot change.
- if defined?(@cached_changed_attributes)
- @cached_changed_attributes
- else
- attributes_changed_by_setter.reverse_merge(mutations_from_database.changed_values).freeze
- end
+ mutations_from_database.changed_values.freeze
end
# Returns a hash of changed attributes indicating their original
@@ -228,9 +219,8 @@ module ActiveModel
# person.name = 'bob'
# person.changes # => { "name" => ["bill", "bob"] }
def changes
- cache_changed_attributes do
- ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
- end
+ _prepare_changes
+ mutations_from_database.changes
end
# Returns a hash of attributes that were changed before the model was saved.
@@ -240,8 +230,7 @@ module ActiveModel
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
- @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
- @previously_changed.merge(mutations_before_last_save.changes)
+ mutations_before_last_save.changes
end
def attribute_changed_in_place?(attr_name) # :nodoc:
@@ -257,11 +246,17 @@ module ActiveModel
unless defined?(@mutations_from_database)
@mutations_from_database = nil
end
- @mutations_from_database ||= if defined?(@attributes)
- ActiveModel::AttributeMutationTracker.new(@attributes)
- else
- NullMutationTracker.instance
+
+ unless defined?(@attributes)
+ @_pseudo_attributes = true
+ @attributes = AttributeSet.new(
+ Hash.new { |h, attr|
+ h[attr] = Attribute.with_cast_value(attr, _clone_attribute(attr), Type.default_value)
+ }
+ )
end
+
+ @mutations_from_database ||= ActiveModel::AttributeMutationTracker.new(@attributes)
end
def forget_attribute_assignments
@@ -272,68 +267,45 @@ module ActiveModel
@mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
end
- def cache_changed_attributes
- @cached_changed_attributes = changed_attributes
- yield
- ensure
- clear_changed_attributes_cache
- end
-
- def clear_changed_attributes_cache
- remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
- end
-
- # Returns +true+ if attr_name is changed, +false+ otherwise.
- def changes_include?(attr_name)
- attributes_changed_by_setter.include?(attr_name) || mutations_from_database.changed?(attr_name)
- end
- alias attribute_changed_by_setter? changes_include?
-
- # Returns +true+ if attr_name were changed before the model was saved,
- # +false+ otherwise.
- def previous_changes_include?(attr_name)
- previous_changes.include?(attr_name)
- end
-
# Handles <tt>*_change</tt> for +method_missing+.
def attribute_change(attr)
- [changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr)
+ [attribute_was(attr), _read_attribute(attr)] if attribute_changed?(attr)
end
# Handles <tt>*_previous_change</tt> for +method_missing+.
def attribute_previous_change(attr)
- previous_changes[attr] if attribute_previously_changed?(attr)
+ mutations_before_last_save.change_to_attribute(attr)
end
# Handles <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
- unless attribute_changed?(attr)
- begin
- value = _read_attribute(attr)
- value = value.duplicable? ? value.clone : value
- rescue TypeError, NoMethodError
- end
-
- set_attribute_was(attr, value)
+ attr = attr.to_s
+ mutations_from_database.force_change(attr).tap do
+ @attributes[attr] if defined?(@_pseudo_attributes)
end
- mutations_from_database.force_change(attr)
end
# Handles <tt>restore_*!</tt> for +method_missing+.
def restore_attribute!(attr)
if attribute_changed?(attr)
- __send__("#{attr}=", changed_attributes[attr])
+ __send__("#{attr}=", attribute_was(attr))
clear_attribute_changes([attr])
end
end
- def attributes_changed_by_setter
- @attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new
+ def _prepare_changes
+ if defined?(@_pseudo_attributes)
+ changed.each do |attr|
+ @attributes.write_from_user(attr, _read_attribute(attr))
+ end
+ end
end
- # Force an attribute to have a particular "before" value
- def set_attribute_was(attr, old_value)
- attributes_changed_by_setter[attr] = old_value
+ def _clone_attribute(attr)
+ value = _read_attribute(attr)
+ value.duplicable? ? value.clone : value
+ rescue TypeError, NoMethodError
+ value
end
end
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 971bdd08b1..275e3f1313 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -322,9 +322,13 @@ module ActiveModel
# person.errors.added? :name, :too_long # => false
# person.errors.added? :name, "is too long" # => false
def added?(attribute, message = :invalid, options = {})
- message = message.call if message.respond_to?(:call)
- message = normalize_message(attribute, message, options)
- self[attribute].include? message
+ if message.is_a? Symbol
+ self.details[attribute].map { |e| e[:error] }.include? message
+ else
+ message = message.call if message.respond_to?(:call)
+ message = normalize_message(attribute, message, options)
+ self[attribute].include? message
+ end
end
# Returns all the full error messages in an array.
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
index 39269c159c..cef5441e4a 100644
--- a/activemodel/lib/active_model/gem_version.rb
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -7,8 +7,8 @@ module ActiveModel
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index 34d9ac6c96..b7ceabb59a 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -29,7 +29,7 @@ module ActiveModel
# <tt>to_key</tt> returns an Enumerable of all (primary) key attributes
# of the model, and is used to a generate unique DOM id for the object.
def test_to_key
- assert model.respond_to?(:to_key), "The model should respond to to_key"
+ assert_respond_to model, :to_key
def model.persisted?() false end
assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
end
@@ -44,7 +44,7 @@ module ActiveModel
# tests for this behavior in lint because it doesn't make sense to force
# any of the possible implementation strategies on the implementer.
def test_to_param
- assert model.respond_to?(:to_param), "The model should respond to to_param"
+ assert_respond_to model, :to_param
def model.to_key() [1] end
def model.persisted?() false end
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
@@ -56,7 +56,7 @@ module ActiveModel
# <tt>to_partial_path</tt> is used for looking up partials. For example,
# a BlogPost model might return "blog_posts/blog_post".
def test_to_partial_path
- assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
+ assert_respond_to model, :to_partial_path
assert_kind_of String, model.to_partial_path
end
@@ -68,7 +68,7 @@ module ActiveModel
# will route to the create action. If it is persisted, a form for the
# object will route to the update action.
def test_persisted?
- assert model.respond_to?(:persisted?), "The model should respond to persisted?"
+ assert_respond_to model, :persisted?
assert_boolean model.persisted?, "persisted?"
end
@@ -79,14 +79,14 @@ module ActiveModel
#
# Check ActiveModel::Naming for more information.
def test_model_naming
- assert model.class.respond_to?(:model_name), "The model class should respond to model_name"
+ assert_respond_to model.class, :model_name
model_name = model.class.model_name
- assert model_name.respond_to?(:to_str)
- assert model_name.human.respond_to?(:to_str)
- assert model_name.singular.respond_to?(:to_str)
- assert model_name.plural.respond_to?(:to_str)
+ assert_respond_to model_name, :to_str
+ assert_respond_to model_name.human, :to_str
+ assert_respond_to model_name.singular, :to_str
+ assert_respond_to model_name.plural, :to_str
- assert model.respond_to?(:model_name), "The model instance should respond to model_name"
+ assert_respond_to model, :model_name
assert_equal model.model_name, model.class.model_name
end
@@ -100,13 +100,13 @@ module ActiveModel
# If localization is used, the strings should be localized for the current
# locale. If no error is present, the method should return an empty array.
def test_errors_aref
- assert model.respond_to?(:errors), "The model should respond to errors"
+ assert_respond_to model, :errors
assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
end
private
def model
- assert @model.respond_to?(:to_model), "The object should respond to to_model"
+ assert_respond_to @model, :to_model
@model.to_model
end
diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb
index 39324999c9..1d7a26fff5 100644
--- a/activemodel/lib/active_model/type.rb
+++ b/activemodel/lib/active_model/type.rb
@@ -24,7 +24,7 @@ module ActiveModel
class << self
attr_accessor :registry # :nodoc:
- # Add a new type to the registry, allowing it to be get through ActiveModel::Type#lookup
+ # Add a new type to the registry, allowing it to be gotten through ActiveModel::Type#lookup
def register(type_name, klass = nil, **options, &block)
registry.register(type_name, klass, **options, &block)
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index cdf11d190f..7f14d102dd 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -164,14 +164,14 @@ module ActiveModel
if options.key?(:on)
options = options.dup
+ options[:on] = Array(options[:on])
options[:if] = Array(options[:if])
options[:if].unshift ->(o) {
- !(Array(options[:on]) & Array(o.validation_context)).empty?
+ !(options[:on] & Array(o.validation_context)).empty?
}
end
- args << options
- set_callback(:validate, *args, &block)
+ set_callback(:validate, *args, options, &block)
end
# List all validators that are being used to validate the model using
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index 4d0ab2a2fe..887d31ae2a 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -54,15 +54,18 @@ module ActiveModel
# person.valid? # => true
# person.name # => "bob"
def before_validation(*args, &block)
- options = args.last
- if options.is_a?(Hash) && options[:on]
- options[:if] = Array(options[:if])
+ options = args.extract_options!
+
+ if options.key?(:on)
+ options = options.dup
options[:on] = Array(options[:on])
+ options[:if] = Array(options[:if])
options[:if].unshift ->(o) {
- options[:on].include? o.validation_context
+ !(options[:on] & Array(o.validation_context)).empty?
}
end
- set_callback(:validation, :before, *args, &block)
+
+ set_callback(:validation, :before, *args, options, &block)
end
# Defines a callback that will get called right after validation.
@@ -93,15 +96,18 @@ module ActiveModel
# person.status # => true
def after_validation(*args, &block)
options = args.extract_options!
+ options = options.dup
options[:prepend] = true
- options[:if] = Array(options[:if])
- if options[:on]
+
+ if options.key?(:on)
options[:on] = Array(options[:on])
+ options[:if] = Array(options[:if])
options[:if].unshift ->(o) {
- options[:on].include? o.validation_context
+ !(options[:on] & Array(o.validation_context)).empty?
}
end
- set_callback(:validation, :after, *(args << options), &block)
+
+ set_callback(:validation, :after, *args, options, &block)
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 43d9f82d9f..e28e7e9219 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -154,7 +154,7 @@ module ActiveModel
# When creating custom validators, it might be useful to be able to specify
# additional default keys. This can be done by overwriting this method.
def _validates_default_keys
- [:if, :unless, :on, :allow_blank, :allow_nil , :strict]
+ [:if, :unless, :on, :allow_blank, :allow_nil, :strict]
end
def _parse_validates_options(options)
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index d2837ec894..0cfc6f4b6b 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -220,7 +220,7 @@ class AttributeMethodsTest < ActiveModel::TestCase
ModelWithAttributes.define_attribute_methods(:foo)
ModelWithAttributes.undefine_attribute_methods
- assert !ModelWithAttributes.new.respond_to?(:foo)
+ assert_not_respond_to ModelWithAttributes.new, :foo
assert_raises(NoMethodError) { ModelWithAttributes.new.foo }
end
@@ -255,7 +255,7 @@ class AttributeMethodsTest < ActiveModel::TestCase
m = ModelWithAttributes2.new
m.attributes = { "private_method" => "<3", "protected_method" => "O_o" }
- assert !m.respond_to?(:private_method)
+ assert_not_respond_to m, :private_method
assert m.respond_to?(:private_method, true)
c = ClassWithProtected.new
diff --git a/activemodel/test/cases/attribute_set_test.rb b/activemodel/test/cases/attribute_set_test.rb
index 02c44c5d45..b868dba743 100644
--- a/activemodel/test/cases/attribute_set_test.rb
+++ b/activemodel/test/cases/attribute_set_test.rb
@@ -74,8 +74,8 @@ module ActiveModel
clone.freeze
- assert clone.frozen?
- assert_not attributes.frozen?
+ assert_predicate clone, :frozen?
+ assert_not_predicate attributes, :frozen?
end
test "to_hash returns a hash of the type cast values" do
@@ -105,8 +105,8 @@ module ActiveModel
test "known columns are built with uninitialized attributes" do
attributes = attributes_with_uninitialized_key
- assert attributes[:foo].initialized?
- assert_not attributes[:bar].initialized?
+ assert_predicate attributes[:foo], :initialized?
+ assert_not_predicate attributes[:bar], :initialized?
end
test "uninitialized attributes are not included in the attributes hash" do
@@ -163,12 +163,13 @@ module ActiveModel
end
test "the primary_key is always initialized" do
- builder = AttributeSet::Builder.new({ foo: Type::Integer.new }, :foo)
+ defaults = { foo: Attribute.from_user(:foo, nil, nil) }
+ builder = AttributeSet::Builder.new({ foo: Type::Integer.new }, defaults)
attributes = builder.build_from_database
assert attributes.key?(:foo)
assert_equal [:foo], attributes.keys
- assert attributes[:foo].initialized?
+ assert_predicate attributes[:foo], :initialized?
end
class MyType
@@ -208,11 +209,6 @@ module ActiveModel
assert_equal "value from user", attributes.fetch_value(:foo)
end
- def attributes_with_uninitialized_key
- builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
- builder.build_from_database(foo: "1.1")
- end
-
test "freezing doesn't prevent the set from materializing" do
builder = AttributeSet::Builder.new(foo: Type::String.new)
attributes = builder.build_from_database(foo: "1")
@@ -221,6 +217,22 @@ module ActiveModel
assert_equal({ foo: "1" }, attributes.to_hash)
end
+ test "marshaling dump/load legacy materialized attribute hash" do
+ builder = AttributeSet::Builder.new(foo: Type::String.new)
+ attributes = builder.build_from_database(foo: "1")
+
+ attributes.instance_variable_get(:@attributes).instance_eval do
+ class << self
+ def marshal_dump
+ materialize
+ end
+ end
+ end
+
+ attributes = Marshal.load(Marshal.dump(attributes))
+ assert_equal({ foo: "1" }, attributes.to_hash)
+ end
+
test "#accessed_attributes returns only attributes which have been read" do
builder = AttributeSet::Builder.new(foo: Type::Value.new, bar: Type::Value.new)
attributes = builder.build_from_database(foo: "1", bar: "2")
@@ -252,5 +264,11 @@ module ActiveModel
assert_equal attributes, attributes2
assert_not_equal attributes2, attributes3
end
+
+ private
+ def attributes_with_uninitialized_key
+ builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
+ builder.build_from_database(foo: "1.1")
+ end
end
end
diff --git a/activemodel/test/cases/attribute_test.rb b/activemodel/test/cases/attribute_test.rb
index 14d86cef97..ea2b0efd11 100644
--- a/activemodel/test/cases/attribute_test.rb
+++ b/activemodel/test/cases/attribute_test.rb
@@ -175,33 +175,33 @@ module ActiveModel
test "an attribute has not been read by default" do
attribute = Attribute.from_database(:foo, 1, Type::Value.new)
- assert_not attribute.has_been_read?
+ assert_not_predicate attribute, :has_been_read?
end
test "an attribute has been read when its value is calculated" do
attribute = Attribute.from_database(:foo, 1, Type::Value.new)
attribute.value
- assert attribute.has_been_read?
+ assert_predicate attribute, :has_been_read?
end
test "an attribute is not changed if it hasn't been assigned or mutated" do
attribute = Attribute.from_database(:foo, 1, Type::Value.new)
- refute attribute.changed?
+ assert_not_predicate attribute, :changed?
end
test "an attribute is changed if it's been assigned a new value" do
attribute = Attribute.from_database(:foo, 1, Type::Value.new)
changed = attribute.with_value_from_user(2)
- assert changed.changed?
+ assert_predicate changed, :changed?
end
test "an attribute is not changed if it's assigned the same value" do
attribute = Attribute.from_database(:foo, 1, Type::Value.new)
unchanged = attribute.with_value_from_user(1)
- refute unchanged.changed?
+ assert_not_predicate unchanged, :changed?
end
test "an attribute can not be mutated if it has not been read,
@@ -209,15 +209,15 @@ module ActiveModel
type_which_raises_from_all_methods = Object.new
attribute = Attribute.from_database(:foo, "bar", type_which_raises_from_all_methods)
- assert_not attribute.changed_in_place?
+ assert_not_predicate attribute, :changed_in_place?
end
test "an attribute is changed if it has been mutated" do
attribute = Attribute.from_database(:foo, "bar", Type::String.new)
attribute.value << "!"
- assert attribute.changed_in_place?
- assert attribute.changed?
+ assert_predicate attribute, :changed_in_place?
+ assert_predicate attribute, :changed?
end
test "an attribute can forget its changes" do
@@ -226,7 +226,7 @@ module ActiveModel
forgotten = changed.forgetting_assignment
assert changed.changed? # sanity check
- refute forgotten.changed?
+ assert_not_predicate forgotten, :changed?
end
test "with_value_from_user validates the value" do
diff --git a/activemodel/test/cases/attributes_dirty_test.rb b/activemodel/test/cases/attributes_dirty_test.rb
index 83a86371e0..c991176389 100644
--- a/activemodel/test/cases/attributes_dirty_test.rb
+++ b/activemodel/test/cases/attributes_dirty_test.rb
@@ -25,11 +25,11 @@ class AttributesDirtyTest < ActiveModel::TestCase
end
test "setting attribute will result in change" do
- assert !@model.changed?
- assert !@model.name_changed?
+ assert_not_predicate @model, :changed?
+ assert_not_predicate @model, :name_changed?
@model.name = "Ringo"
- assert @model.changed?
- assert @model.name_changed?
+ assert_predicate @model, :changed?
+ assert_predicate @model, :name_changed?
end
test "list of changed attribute keys" do
@@ -71,35 +71,35 @@ class AttributesDirtyTest < ActiveModel::TestCase
test "attribute mutation" do
@model.name = "Yam"
@model.save
- assert !@model.name_changed?
+ assert_not_predicate @model, :name_changed?
@model.name.replace("Hadad")
- assert @model.name_changed?
+ assert_predicate @model, :name_changed?
end
test "resetting attribute" do
@model.name = "Bob"
@model.restore_name!
assert_nil @model.name
- assert !@model.name_changed?
+ assert_not_predicate @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?
+ assert_predicate @model, :color_changed?
@model.save
- assert !@model.color_changed?
- assert !@model.changed?
+ assert_not_predicate @model, :color_changed?
+ assert_not_predicate @model, :changed?
@model.color = "red"
- assert !@model.color_changed?
- assert !@model.changed?
+ assert_not_predicate @model, :color_changed?
+ assert_not_predicate @model, :changed?
end
test "saving should reset model's changed status" do
@model.name = "Alf"
- assert @model.changed?
+ assert_predicate @model, :changed?
@model.save
- assert !@model.changed?
- assert !@model.name_changed?
+ assert_not_predicate @model, :changed?
+ assert_not_predicate @model, :name_changed?
end
test "saving should preserve previous changes" do
@@ -118,7 +118,7 @@ class AttributesDirtyTest < ActiveModel::TestCase
test "saving should preserve model's previous changed status" do
@model.name = "Jericho Cane"
@model.save
- assert @model.name_previously_changed?
+ assert_predicate @model, :name_previously_changed?
end
test "previous value is preserved when changed after save" do
@@ -143,7 +143,7 @@ class AttributesDirtyTest < ActiveModel::TestCase
test "using attribute_will_change! with a symbol" do
@model.size = 1
- assert @model.size_changed?
+ assert_predicate @model, :size_changed?
end
test "reload should reset all changes" do
@@ -170,7 +170,7 @@ class AttributesDirtyTest < ActiveModel::TestCase
@model.restore_attributes
- assert_not @model.changed?
+ assert_not_predicate @model, :changed?
assert_equal "Dmitry", @model.name
assert_equal "Red", @model.color
end
@@ -184,7 +184,7 @@ class AttributesDirtyTest < ActiveModel::TestCase
@model.restore_attributes(["name"])
- assert @model.changed?
+ assert_predicate @model, :changed?
assert_equal "Dmitry", @model.name
assert_equal "White", @model.color
end
diff --git a/activemodel/test/cases/attributes_test.rb b/activemodel/test/cases/attributes_test.rb
index 914aee1ac0..7c1d813ce0 100644
--- a/activemodel/test/cases/attributes_test.rb
+++ b/activemodel/test/cases/attributes_test.rb
@@ -33,7 +33,7 @@ module ActiveModel
assert_equal 2, data.integer_field
assert_equal "Rails FTW", data.string_field
- assert_equal BigDecimal.new("12.3"), data.decimal_field
+ assert_equal BigDecimal("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
@@ -64,5 +64,14 @@ module ActiveModel
assert_equal "4.4", data.integer_field
end
+
+ test "attributes with proc defaults can be marshalled" do
+ data = ModelForAttributesTest.new
+ attributes = data.instance_variable_get(:@attributes)
+ round_tripped = Marshal.load(Marshal.dump(data))
+ new_attributes = round_tripped.instance_variable_get(:@attributes)
+
+ assert_equal attributes, new_attributes
+ end
end
end
diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb
index a5d29d0f22..1ec12d8222 100644
--- a/activemodel/test/cases/callbacks_test.rb
+++ b/activemodel/test/cases/callbacks_test.rb
@@ -85,21 +85,21 @@ class CallbacksTest < ActiveModel::TestCase
end
test "only selects which types of callbacks should be created" do
- assert !ModelCallbacks.respond_to?(:before_initialize)
- assert !ModelCallbacks.respond_to?(:around_initialize)
+ assert_not_respond_to ModelCallbacks, :before_initialize
+ assert_not_respond_to ModelCallbacks, :around_initialize
assert_respond_to ModelCallbacks, :after_initialize
end
test "only selects which types of callbacks should be created from an array list" do
assert_respond_to ModelCallbacks, :before_multiple
assert_respond_to ModelCallbacks, :around_multiple
- assert !ModelCallbacks.respond_to?(:after_multiple)
+ assert_not_respond_to ModelCallbacks, :after_multiple
end
test "no callbacks should be created" do
- assert !ModelCallbacks.respond_to?(:before_empty)
- assert !ModelCallbacks.respond_to?(:around_empty)
- assert !ModelCallbacks.respond_to?(:after_empty)
+ assert_not_respond_to ModelCallbacks, :before_empty
+ assert_not_respond_to ModelCallbacks, :around_empty
+ assert_not_respond_to ModelCallbacks, :after_empty
end
class Violin
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index dfe041ff50..f769eb0da1 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -5,12 +5,13 @@ require "cases/helper"
class DirtyTest < ActiveModel::TestCase
class DirtyModel
include ActiveModel::Dirty
- define_attribute_methods :name, :color, :size
+ define_attribute_methods :name, :color, :size, :status
def initialize
@name = nil
@color = nil
@size = nil
+ @status = "initialized"
end
def name
@@ -40,6 +41,15 @@ class DirtyTest < ActiveModel::TestCase
@size = val
end
+ def status
+ @status
+ end
+
+ def status=(val)
+ status_will_change! unless val == @status
+ @status = val
+ end
+
def save
changes_applied
end
@@ -54,11 +64,11 @@ class DirtyTest < ActiveModel::TestCase
end
test "setting attribute will result in change" do
- assert !@model.changed?
- assert !@model.name_changed?
+ assert_not_predicate @model, :changed?
+ assert_not_predicate @model, :name_changed?
@model.name = "Ringo"
- assert @model.changed?
- assert @model.name_changed?
+ assert_predicate @model, :changed?
+ assert_predicate @model, :name_changed?
end
test "list of changed attribute keys" do
@@ -99,82 +109,93 @@ class DirtyTest < ActiveModel::TestCase
test "attribute mutation" do
@model.instance_variable_set("@name", "Yam".dup)
- assert !@model.name_changed?
+ assert_not_predicate @model, :name_changed?
@model.name.replace("Hadad")
- assert !@model.name_changed?
+ assert_not_predicate @model, :name_changed?
@model.name_will_change!
@model.name.replace("Baal")
- assert @model.name_changed?
+ assert_predicate @model, :name_changed?
end
test "resetting attribute" do
@model.name = "Bob"
@model.restore_name!
assert_nil @model.name
- assert !@model.name_changed?
+ assert_not_predicate @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?
+ assert_predicate @model, :color_changed?
@model.save
- assert !@model.color_changed?
- assert !@model.changed?
+ assert_not_predicate @model, :color_changed?
+ assert_not_predicate @model, :changed?
@model.color = "red"
- assert !@model.color_changed?
- assert !@model.changed?
+ assert_not_predicate @model, :color_changed?
+ assert_not_predicate @model, :changed?
end
test "saving should reset model's changed status" do
@model.name = "Alf"
- assert @model.changed?
+ assert_predicate @model, :changed?
@model.save
- assert !@model.changed?
- assert !@model.name_changed?
+ assert_not_predicate @model, :changed?
+ assert_not_predicate @model, :name_changed?
end
test "saving should preserve previous changes" do
@model.name = "Jericho Cane"
+ @model.status = "waiting"
@model.save
assert_equal [nil, "Jericho Cane"], @model.previous_changes["name"]
+ assert_equal ["initialized", "waiting"], @model.previous_changes["status"]
end
test "setting new attributes should not affect previous changes" do
@model.name = "Jericho Cane"
+ @model.status = "waiting"
@model.save
@model.name = "DudeFella ManGuy"
+ @model.status = "finished"
assert_equal [nil, "Jericho Cane"], @model.name_previous_change
+ assert_equal ["initialized", "waiting"], @model.previous_changes["status"]
end
test "saving should preserve model's previous changed status" do
@model.name = "Jericho Cane"
@model.save
- assert @model.name_previously_changed?
+ assert_predicate @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.status = "waiting"
+ assert_equal({ "name" => nil, "status" => "initialized" }, @model.changed_attributes)
@model.save
@model.name = "John"
- assert_equal({ "name" => "Paul" }, @model.changed_attributes)
+ @model.status = "finished"
+ assert_equal({ "name" => "Paul", "status" => "waiting" }, @model.changed_attributes)
end
test "changing the same attribute multiple times retains the correct original value" do
@model.name = "Otto"
+ @model.status = "waiting"
@model.save
@model.name = "DudeFella ManGuy"
@model.name = "Mr. Manfredgensonton"
+ @model.status = "processing"
+ @model.status = "finished"
assert_equal ["Otto", "Mr. Manfredgensonton"], @model.name_change
+ assert_equal ["waiting", "finished"], @model.status_change
assert_equal @model.name_was, "Otto"
end
test "using attribute_will_change! with a symbol" do
@model.size = 1
- assert @model.size_changed?
+ assert_predicate @model, :size_changed?
end
test "reload should reset all changes" do
@@ -201,7 +222,7 @@ class DirtyTest < ActiveModel::TestCase
@model.restore_attributes
- assert_not @model.changed?
+ assert_not_predicate @model, :changed?
assert_equal "Dmitry", @model.name
assert_equal "Red", @model.color
end
@@ -215,7 +236,7 @@ class DirtyTest < ActiveModel::TestCase
@model.restore_attributes(["name"])
- assert @model.changed?
+ assert_predicate @model, :changed?
assert_equal "Dmitry", @model.name
assert_equal "White", @model.color
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index ab18af0de1..fe351f922d 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -83,7 +83,7 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal 1, person.errors.count
person.errors.clear
- assert person.errors.empty?
+ assert_empty person.errors
end
test "error access is indifferent" do
@@ -128,8 +128,8 @@ class ErrorsTest < ActiveModel::TestCase
test "detecting whether there are errors with empty?, blank?, include?" do
person = Person.new
person.errors[:foo]
- assert person.errors.empty?
- assert person.errors.blank?
+ assert_empty person.errors
+ assert_predicate person.errors, :blank?
assert_not_includes person.errors, :foo
end
@@ -223,6 +223,13 @@ class ErrorsTest < ActiveModel::TestCase
assert !person.errors.added?(:name)
end
+ test "added? returns false when checking for an error by symbol and a different error with same message is present" do
+ I18n.backend.store_translations("en", errors: { attributes: { name: { wrong: "is wrong", used: "is wrong" } } })
+ person = Person.new
+ person.errors.add(:name, :wrong)
+ assert !person.errors.added?(:name, :used)
+ end
+
test "size calculates the number of error messages" do
person = Person.new
person.errors.add(:name, "cannot be blank")
@@ -313,7 +320,7 @@ class ErrorsTest < ActiveModel::TestCase
test "generate_message works without i18n_scope" do
person = Person.new
- assert !Person.respond_to?(:i18n_scope)
+ assert_not_respond_to Person, :i18n_scope
assert_nothing_raised {
person.errors.generate_message(:name, :blank)
}
@@ -364,7 +371,7 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal 1, person.errors.details.count
person.errors.clear
- assert person.errors.details.empty?
+ assert_empty person.errors.details
end
test "copy errors" do
diff --git a/activemodel/test/cases/type/boolean_test.rb b/activemodel/test/cases/type/boolean_test.rb
index 2d33579595..2de0f53640 100644
--- a/activemodel/test/cases/type/boolean_test.rb
+++ b/activemodel/test/cases/type/boolean_test.rb
@@ -7,8 +7,8 @@ module ActiveModel
class BooleanTest < ActiveModel::TestCase
def test_type_cast_boolean
type = Type::Boolean.new
- assert type.cast("").nil?
- assert type.cast(nil).nil?
+ assert_predicate type.cast(""), :nil?
+ assert_predicate type.cast(nil), :nil?
assert type.cast(true)
assert type.cast(1)
diff --git a/activemodel/test/cases/type/decimal_test.rb b/activemodel/test/cases/type/decimal_test.rb
index b91d67f95f..c0cf6ce590 100644
--- a/activemodel/test/cases/type/decimal_test.rb
+++ b/activemodel/test/cases/type/decimal_test.rb
@@ -7,22 +7,22 @@ module ActiveModel
class DecimalTest < ActiveModel::TestCase
def test_type_cast_decimal
type = Decimal.new
- assert_equal BigDecimal.new("0"), type.cast(BigDecimal.new("0"))
- assert_equal BigDecimal.new("123"), type.cast(123.0)
- assert_equal BigDecimal.new("1"), type.cast(:"1")
+ assert_equal BigDecimal("0"), type.cast(BigDecimal("0"))
+ assert_equal BigDecimal("123"), type.cast(123.0)
+ assert_equal BigDecimal("1"), type.cast(:"1")
end
def test_type_cast_decimal_from_invalid_string
type = Decimal.new
assert_nil type.cast("")
- assert_equal BigDecimal.new("1"), type.cast("1ignore")
- assert_equal BigDecimal.new("0"), type.cast("bad1")
- assert_equal BigDecimal.new("0"), type.cast("bad")
+ assert_equal BigDecimal("1"), type.cast("1ignore")
+ assert_equal BigDecimal("0"), type.cast("bad1")
+ assert_equal BigDecimal("0"), type.cast("bad")
end
def test_type_cast_decimal_from_float_with_large_precision
type = Decimal.new(precision: ::Float::DIG + 2)
- assert_equal BigDecimal.new("123.0"), type.cast(123.0)
+ assert_equal BigDecimal("123.0"), type.cast(123.0)
end
def test_type_cast_from_float_with_unspecified_precision
@@ -48,7 +48,7 @@ module ActiveModel
def test_type_cast_decimal_from_object_responding_to_d
value = Object.new
def value.to_d
- BigDecimal.new("1")
+ BigDecimal("1")
end
type = Decimal.new
assert_equal BigDecimal("1"), type.cast(value)
diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb
index 801577474a..8bc4f4723a 100644
--- a/activemodel/test/cases/validations/absence_validation_test.rb
+++ b/activemodel/test/cases/validations/absence_validation_test.rb
@@ -17,16 +17,16 @@ class AbsenceValidationTest < ActiveModel::TestCase
t = Topic.new
t.title = "foo"
t.content = "bar"
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["must be blank"], t.errors[:title]
assert_equal ["must be blank"], t.errors[:content]
t.title = ""
t.content = "something"
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["must be blank"], t.errors[:content]
assert_equal [], t.errors[:title]
t.content = ""
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_absence_of_with_array_arguments
@@ -34,7 +34,7 @@ class AbsenceValidationTest < ActiveModel::TestCase
t = Topic.new
t.title = "foo"
t.content = "bar"
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["must be blank"], t.errors[:title]
assert_equal ["must be blank"], t.errors[:content]
end
@@ -43,7 +43,7 @@ class AbsenceValidationTest < ActiveModel::TestCase
Person.validates_absence_of :karma, message: "This string contains 'single' and \"double\" quotes"
p = Person.new
p.karma = "good"
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last
end
@@ -51,19 +51,19 @@ class AbsenceValidationTest < ActiveModel::TestCase
Person.validates_absence_of :karma
p = Person.new
p.karma = "good"
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["must be blank"], p.errors[:karma]
p.karma = nil
- assert p.valid?
+ assert_predicate p, :valid?
end
def test_validates_absence_of_for_ruby_class_with_custom_reader
CustomReader.validates_absence_of :karma
p = CustomReader.new
p[:karma] = "excellent"
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["must be blank"], p.errors[:karma]
p[:karma] = ""
- assert p.valid?
+ assert_predicate p, :valid?
end
end
diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb
index c5f54b1868..7662f996ae 100644
--- a/activemodel/test/cases/validations/acceptance_validation_test.rb
+++ b/activemodel/test/cases/validations/acceptance_validation_test.rb
@@ -15,54 +15,54 @@ class AcceptanceValidationTest < ActiveModel::TestCase
Topic.validates_acceptance_of(:terms_of_service)
t = Topic.new("title" => "We should not be confirmed")
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_terms_of_service_agreement
Topic.validates_acceptance_of(:terms_of_service)
t = Topic.new("title" => "We should be confirmed", "terms_of_service" => "")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["must be accepted"], t.errors[:terms_of_service]
t.terms_of_service = "1"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_eula
Topic.validates_acceptance_of(:eula, message: "must be abided")
t = Topic.new("title" => "We should be confirmed", "eula" => "")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["must be abided"], t.errors[:eula]
t.eula = "1"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_terms_of_service_agreement_with_accept_value
Topic.validates_acceptance_of(:terms_of_service, accept: "I agree.")
t = Topic.new("title" => "We should be confirmed", "terms_of_service" => "")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["must be accepted"], t.errors[:terms_of_service]
t.terms_of_service = "I agree."
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_terms_of_service_agreement_with_multiple_accept_values
Topic.validates_acceptance_of(:terms_of_service, accept: [1, "I concur."])
t = Topic.new("title" => "We should be confirmed", "terms_of_service" => "")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["must be accepted"], t.errors[:terms_of_service]
t.terms_of_service = 1
- assert t.valid?
+ assert_predicate t, :valid?
t.terms_of_service = "I concur."
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_acceptance_of_for_ruby_class
@@ -71,11 +71,11 @@ class AcceptanceValidationTest < ActiveModel::TestCase
p = Person.new
p.karma = ""
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["must be accepted"], p.errors[:karma]
p.karma = "1"
- assert p.valid?
+ assert_predicate p, :valid?
ensure
Person.clear_validators!
end
@@ -83,6 +83,6 @@ class AcceptanceValidationTest < ActiveModel::TestCase
def test_validates_acceptance_of_true
Topic.validates_acceptance_of(:terms_of_service)
- assert Topic.new(terms_of_service: true).valid?
+ assert_predicate Topic.new(terms_of_service: true), :valid?
end
end
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
index d3a9a17a05..ff3cf61746 100644
--- a/activemodel/test/cases/validations/callbacks_test.rb
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -59,6 +59,18 @@ class DogValidatorWithOnCondition < Dog
def set_after_validation_marker; history << "after_validation_marker" ; end
end
+class DogValidatorWithOnMultipleCondition < Dog
+ before_validation :set_before_validation_marker_on_context_a, on: :context_a
+ before_validation :set_before_validation_marker_on_context_b, on: :context_b
+ after_validation :set_after_validation_marker_on_context_a, on: :context_a
+ after_validation :set_after_validation_marker_on_context_b, on: :context_b
+
+ def set_before_validation_marker_on_context_a; history << "before_validation_marker on context_a"; end
+ def set_before_validation_marker_on_context_b; history << "before_validation_marker on context_b"; end
+ def set_after_validation_marker_on_context_a; history << "after_validation_marker on context_a" ; end
+ def set_after_validation_marker_on_context_b; history << "after_validation_marker on context_b" ; end
+end
+
class DogValidatorWithIfCondition < Dog
before_validation :set_before_validation_marker1, if: -> { true }
before_validation :set_before_validation_marker2, if: -> { false }
@@ -98,6 +110,37 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase
assert_equal [], d.history
end
+ def test_on_multiple_condition_is_respected_for_validation_with_matching_context
+ d = DogValidatorWithOnMultipleCondition.new
+ d.valid?(:context_a)
+ assert_equal ["before_validation_marker on context_a", "after_validation_marker on context_a"], d.history
+
+ d = DogValidatorWithOnMultipleCondition.new
+ d.valid?(:context_b)
+ assert_equal ["before_validation_marker on context_b", "after_validation_marker on context_b"], d.history
+
+ d = DogValidatorWithOnMultipleCondition.new
+ d.valid?([:context_a, :context_b])
+ assert_equal([
+ "before_validation_marker on context_a",
+ "before_validation_marker on context_b",
+ "after_validation_marker on context_a",
+ "after_validation_marker on context_b"
+ ], d.history)
+ end
+
+ def test_on_multiple_condition_is_respected_for_validation_without_matching_context
+ d = DogValidatorWithOnMultipleCondition.new
+ d.valid?(:save)
+ assert_equal [], d.history
+ end
+
+ def test_on_multiple_condition_is_respected_for_validation_without_context
+ d = DogValidatorWithOnMultipleCondition.new
+ d.valid?
+ assert_equal [], d.history
+ end
+
def test_before_validation_and_after_validation_callbacks_should_be_called
d = DogWithMethodCallbacks.new
d.valid?
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index caea8b65ef..1704db9a48 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -13,24 +13,24 @@ class ConditionalValidationTest < ActiveModel::TestCase
# When the method returns true
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_if_validation_using_array_of_true_methods
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: [:condition_is_true, :condition_is_true])
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_unless_validation_using_array_of_false_methods
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: [:condition_is_false, :condition_is_false])
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -38,21 +38,21 @@ class ConditionalValidationTest < ActiveModel::TestCase
# When the method returns true
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: :condition_is_true)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
assert_empty t.errors[:title]
end
def test_if_validation_using_array_of_true_and_false_methods
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: [:condition_is_true, :condition_is_false])
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
assert_empty t.errors[:title]
end
def test_unless_validation_using_array_of_true_and_felse_methods
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: [:condition_is_true, :condition_is_false])
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
assert_empty t.errors[:title]
end
@@ -60,7 +60,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
# When the method returns false
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_false)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
assert_empty t.errors[:title]
end
@@ -68,8 +68,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
# When the method returns false
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: :condition_is_false)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -78,8 +78,8 @@ class ConditionalValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
if: Proc.new { |r| r.content.size > 4 })
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -88,7 +88,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
unless: Proc.new { |r| r.content.size > 4 })
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
assert_empty t.errors[:title]
end
@@ -97,7 +97,7 @@ class ConditionalValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
if: Proc.new { |r| r.title != "uhohuhoh" })
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
assert_empty t.errors[:title]
end
@@ -106,23 +106,23 @@ class ConditionalValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}",
unless: Proc.new { |r| r.title != "uhohuhoh" })
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 5"], t.errors["title"]
end
def test_validation_using_conbining_if_true_and_unless_true_conditions
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true, unless: :condition_is_true)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
assert_empty t.errors[:title]
end
def test_validation_using_conbining_if_true_and_unless_false_conditions
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true, unless: :condition_is_false)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 5"], t.errors["title"]
end
end
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index 8b2c65289b..8603a8ac5c 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -14,40 +14,40 @@ class ConfirmationValidationTest < ActiveModel::TestCase
Topic.validates_confirmation_of(:title)
t = Topic.new(author_name: "Plutarch")
- assert t.valid?
+ assert_predicate t, :valid?
t.title_confirmation = "Parallel Lives"
- assert t.invalid?
+ assert_predicate t, :invalid?
t.title_confirmation = nil
t.title = "Parallel Lives"
- assert t.valid?
+ assert_predicate t, :valid?
t.title_confirmation = "Parallel Lives"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_title_confirmation
Topic.validates_confirmation_of(:title)
t = Topic.new("title" => "We should be confirmed", "title_confirmation" => "")
- assert t.invalid?
+ assert_predicate t, :invalid?
t.title_confirmation = "We should be confirmed"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_confirmation_of_with_boolean_attribute
Topic.validates_confirmation_of(:approved)
t = Topic.new(approved: true, approved_confirmation: nil)
- assert t.valid?
+ assert_predicate t, :valid?
t.approved_confirmation = false
- assert t.invalid?
+ assert_predicate t, :invalid?
t.approved_confirmation = true
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_confirmation_of_for_ruby_class
@@ -55,12 +55,12 @@ class ConfirmationValidationTest < ActiveModel::TestCase
p = Person.new
p.karma_confirmation = "None"
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["doesn't match Karma"], p.errors[:karma_confirmation]
p.karma = "None"
- assert p.valid?
+ assert_predicate p, :valid?
ensure
Person.clear_validators!
end
@@ -77,7 +77,7 @@ class ConfirmationValidationTest < ActiveModel::TestCase
Topic.validates_confirmation_of(:title)
t = Topic.new("title" => "We should be confirmed", "title_confirmation" => "")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation]
ensure
I18n.load_path.replace @old_load_path
@@ -122,13 +122,13 @@ class ConfirmationValidationTest < ActiveModel::TestCase
Topic.validates_confirmation_of(:title, case_sensitive: true)
t = Topic.new(title: "title", title_confirmation: "Title")
- assert t.invalid?
+ assert_predicate t, :invalid?
end
def test_title_confirmation_with_case_sensitive_option_false
Topic.validates_confirmation_of(:title, case_sensitive: false)
t = Topic.new(title: "title", title_confirmation: "Title")
- assert t.valid?
+ assert_predicate t, :valid?
end
end
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index 68d611e904..50bd47065c 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -14,8 +14,8 @@ class ExclusionValidationTest < ActiveModel::TestCase
def test_validates_exclusion_of
Topic.validates_exclusion_of(:title, in: %w( abe monkey ))
- assert Topic.new("title" => "something", "content" => "abc").valid?
- assert Topic.new("title" => "monkey", "content" => "abc").invalid?
+ assert_predicate Topic.new("title" => "something", "content" => "abc"), :valid?
+ assert_predicate Topic.new("title" => "monkey", "content" => "abc"), :invalid?
end
def test_validates_exclusion_of_with_formatted_message
@@ -24,8 +24,8 @@ class ExclusionValidationTest < ActiveModel::TestCase
assert Topic.new("title" => "something", "content" => "abc")
t = Topic.new("title" => "monkey")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["option monkey is restricted"], t.errors[:title]
end
@@ -35,8 +35,8 @@ class ExclusionValidationTest < ActiveModel::TestCase
assert Topic.new("title" => "something", "content" => "abc")
t = Topic.new("title" => "monkey")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
end
def test_validates_exclusion_of_for_ruby_class
@@ -44,12 +44,12 @@ class ExclusionValidationTest < ActiveModel::TestCase
p = Person.new
p.karma = "abe"
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["is reserved"], p.errors[:karma]
p.karma = "Lifo"
- assert p.valid?
+ assert_predicate p, :valid?
ensure
Person.clear_validators!
end
@@ -60,26 +60,26 @@ class ExclusionValidationTest < ActiveModel::TestCase
t = Topic.new
t.title = "elephant"
t.author_name = "sikachu"
- assert t.invalid?
+ assert_predicate t, :invalid?
t.title = "wasabi"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_exclusion_of_with_range
Topic.validates_exclusion_of :content, in: ("a".."g")
- assert Topic.new(content: "g").invalid?
- assert Topic.new(content: "h").valid?
+ assert_predicate Topic.new(content: "g"), :invalid?
+ assert_predicate Topic.new(content: "h"), :valid?
end
def test_validates_exclusion_of_with_time_range
Topic.validates_exclusion_of :created_at, in: 6.days.ago..2.days.ago
- assert Topic.new(created_at: 5.days.ago).invalid?
- assert Topic.new(created_at: 3.days.ago).invalid?
- assert Topic.new(created_at: 7.days.ago).valid?
- assert Topic.new(created_at: 1.day.ago).valid?
+ assert_predicate Topic.new(created_at: 5.days.ago), :invalid?
+ assert_predicate Topic.new(created_at: 3.days.ago), :invalid?
+ assert_predicate Topic.new(created_at: 7.days.ago), :valid?
+ assert_predicate Topic.new(created_at: 1.day.ago), :valid?
end
def test_validates_inclusion_of_with_symbol
@@ -92,7 +92,7 @@ class ExclusionValidationTest < ActiveModel::TestCase
%w(abe)
end
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["is reserved"], p.errors[:karma]
p = Person.new
@@ -102,7 +102,7 @@ class ExclusionValidationTest < ActiveModel::TestCase
%w()
end
- assert p.valid?
+ assert_predicate p, :valid?
ensure
Person.clear_validators!
end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 3ddda2154a..2a7088b3e8 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -5,7 +5,7 @@ require "cases/helper"
require "models/topic"
require "models/person"
-class PresenceValidationTest < ActiveModel::TestCase
+class FormatValidationTest < ActiveModel::TestCase
def teardown
Topic.clear_validators!
end
@@ -16,22 +16,22 @@ class PresenceValidationTest < ActiveModel::TestCase
t = Topic.new("title" => "i'm incorrect", "content" => "Validation macros rule!")
assert t.invalid?, "Shouldn't be valid"
assert_equal ["is bad data"], t.errors[:title]
- assert t.errors[:content].empty?
+ assert_empty t.errors[:content]
t.title = "Validation macros rule!"
- assert t.valid?
- assert t.errors[:title].empty?
+ assert_predicate t, :valid?
+ assert_empty t.errors[:title]
assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) }
end
def test_validate_format_with_allow_blank
Topic.validates_format_of(:title, with: /\AValidation\smacros \w+!\z/, allow_blank: true)
- assert Topic.new("title" => "Shouldn't be valid").invalid?
- assert Topic.new("title" => "").valid?
- assert Topic.new("title" => nil).valid?
- assert Topic.new("title" => "Validation macros rule!").valid?
+ assert_predicate Topic.new("title" => "Shouldn't be valid"), :invalid?
+ assert_predicate Topic.new("title" => ""), :valid?
+ assert_predicate Topic.new("title" => nil), :valid?
+ assert_predicate Topic.new("title" => "Validation macros rule!"), :valid?
end
# testing ticket #3142
@@ -42,7 +42,7 @@ class PresenceValidationTest < ActiveModel::TestCase
assert t.invalid?, "Shouldn't be valid"
assert_equal ["is bad data"], t.errors[:title]
- assert t.errors[:content].empty?
+ assert_empty t.errors[:content]
t.title = "-11"
assert t.invalid?, "Shouldn't be valid"
@@ -58,14 +58,14 @@ class PresenceValidationTest < ActiveModel::TestCase
t.title = "1"
- assert t.valid?
- assert t.errors[:title].empty?
+ assert_predicate t, :valid?
+ assert_empty t.errors[:title]
end
def test_validate_format_with_formatted_message
Topic.validates_format_of(:title, with: /\AValid Title\z/, message: "can't be %{value}")
t = Topic.new(title: "Invalid title")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["can't be Invalid title"], t.errors[:title]
end
@@ -114,10 +114,10 @@ class PresenceValidationTest < ActiveModel::TestCase
t = Topic.new
t.title = "digit"
t.content = "Pixies"
- assert t.invalid?
+ assert_predicate t, :invalid?
t.content = "1234"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_format_of_without_lambda
@@ -126,10 +126,10 @@ class PresenceValidationTest < ActiveModel::TestCase
t = Topic.new
t.title = "characters"
t.content = "1234"
- assert t.invalid?
+ assert_predicate t, :invalid?
t.content = "Pixies"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_format_of_for_ruby_class
@@ -137,12 +137,12 @@ class PresenceValidationTest < ActiveModel::TestCase
p = Person.new
p.karma = "Pixies"
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["is invalid"], p.errors[:karma]
p.karma = "1234"
- assert p.valid?
+ assert_predicate p, :valid?
ensure
Person.clear_validators!
end
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 94df0649a9..daad76759f 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -13,61 +13,61 @@ class InclusionValidationTest < ActiveModel::TestCase
def test_validates_inclusion_of_range
Topic.validates_inclusion_of(:title, in: "aaa".."bbb")
- assert Topic.new("title" => "bbc", "content" => "abc").invalid?
- assert Topic.new("title" => "aa", "content" => "abc").invalid?
- assert Topic.new("title" => "aaab", "content" => "abc").invalid?
- assert Topic.new("title" => "aaa", "content" => "abc").valid?
- assert Topic.new("title" => "abc", "content" => "abc").valid?
- assert Topic.new("title" => "bbb", "content" => "abc").valid?
+ assert_predicate Topic.new("title" => "bbc", "content" => "abc"), :invalid?
+ assert_predicate Topic.new("title" => "aa", "content" => "abc"), :invalid?
+ assert_predicate Topic.new("title" => "aaab", "content" => "abc"), :invalid?
+ assert_predicate Topic.new("title" => "aaa", "content" => "abc"), :valid?
+ assert_predicate Topic.new("title" => "abc", "content" => "abc"), :valid?
+ assert_predicate Topic.new("title" => "bbb", "content" => "abc"), :valid?
end
def test_validates_inclusion_of_time_range
range_begin = 1.year.ago
range_end = Time.now
Topic.validates_inclusion_of(:created_at, in: range_begin..range_end)
- assert Topic.new(title: "aaa", created_at: 2.years.ago).invalid?
- assert Topic.new(title: "aaa", created_at: 3.months.ago).valid?
- assert Topic.new(title: "aaa", created_at: 37.weeks.from_now).invalid?
- assert Topic.new(title: "aaa", created_at: range_begin).valid?
- assert Topic.new(title: "aaa", created_at: range_end).valid?
+ assert_predicate Topic.new(title: "aaa", created_at: 2.years.ago), :invalid?
+ assert_predicate Topic.new(title: "aaa", created_at: 3.months.ago), :valid?
+ assert_predicate Topic.new(title: "aaa", created_at: 37.weeks.from_now), :invalid?
+ assert_predicate Topic.new(title: "aaa", created_at: range_begin), :valid?
+ assert_predicate Topic.new(title: "aaa", created_at: range_end), :valid?
end
def test_validates_inclusion_of_date_range
range_begin = 1.year.until(Date.today)
range_end = Date.today
Topic.validates_inclusion_of(:created_at, in: range_begin..range_end)
- assert Topic.new(title: "aaa", created_at: 2.years.until(Date.today)).invalid?
- assert Topic.new(title: "aaa", created_at: 3.months.until(Date.today)).valid?
- assert Topic.new(title: "aaa", created_at: 37.weeks.since(Date.today)).invalid?
- assert Topic.new(title: "aaa", created_at: 1.year.until(Date.today)).valid?
- assert Topic.new(title: "aaa", created_at: Date.today).valid?
- assert Topic.new(title: "aaa", created_at: range_begin).valid?
- assert Topic.new(title: "aaa", created_at: range_end).valid?
+ assert_predicate Topic.new(title: "aaa", created_at: 2.years.until(Date.today)), :invalid?
+ assert_predicate Topic.new(title: "aaa", created_at: 3.months.until(Date.today)), :valid?
+ assert_predicate Topic.new(title: "aaa", created_at: 37.weeks.since(Date.today)), :invalid?
+ assert_predicate Topic.new(title: "aaa", created_at: 1.year.until(Date.today)), :valid?
+ assert_predicate Topic.new(title: "aaa", created_at: Date.today), :valid?
+ assert_predicate Topic.new(title: "aaa", created_at: range_begin), :valid?
+ assert_predicate Topic.new(title: "aaa", created_at: range_end), :valid?
end
def test_validates_inclusion_of_date_time_range
range_begin = 1.year.until(DateTime.current)
range_end = DateTime.current
Topic.validates_inclusion_of(:created_at, in: range_begin..range_end)
- assert Topic.new(title: "aaa", created_at: 2.years.until(DateTime.current)).invalid?
- assert Topic.new(title: "aaa", created_at: 3.months.until(DateTime.current)).valid?
- assert Topic.new(title: "aaa", created_at: 37.weeks.since(DateTime.current)).invalid?
- assert Topic.new(title: "aaa", created_at: range_begin).valid?
- assert Topic.new(title: "aaa", created_at: range_end).valid?
+ assert_predicate Topic.new(title: "aaa", created_at: 2.years.until(DateTime.current)), :invalid?
+ assert_predicate Topic.new(title: "aaa", created_at: 3.months.until(DateTime.current)), :valid?
+ assert_predicate Topic.new(title: "aaa", created_at: 37.weeks.since(DateTime.current)), :invalid?
+ assert_predicate Topic.new(title: "aaa", created_at: range_begin), :valid?
+ assert_predicate Topic.new(title: "aaa", created_at: range_end), :valid?
end
def test_validates_inclusion_of
Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ))
- assert Topic.new("title" => "a!", "content" => "abc").invalid?
- assert Topic.new("title" => "a b", "content" => "abc").invalid?
- assert Topic.new("title" => nil, "content" => "def").invalid?
+ assert_predicate Topic.new("title" => "a!", "content" => "abc"), :invalid?
+ assert_predicate Topic.new("title" => "a b", "content" => "abc"), :invalid?
+ assert_predicate Topic.new("title" => nil, "content" => "def"), :invalid?
t = Topic.new("title" => "a", "content" => "I know you are but what am I?")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = "uhoh"
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is not included in the list"], t.errors[:title]
assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: nil) }
@@ -81,30 +81,30 @@ class InclusionValidationTest < ActiveModel::TestCase
def test_validates_inclusion_of_with_allow_nil
Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ), allow_nil: true)
- assert Topic.new("title" => "a!", "content" => "abc").invalid?
- assert Topic.new("title" => "", "content" => "abc").invalid?
- assert Topic.new("title" => nil, "content" => "abc").valid?
+ assert_predicate Topic.new("title" => "a!", "content" => "abc"), :invalid?
+ assert_predicate Topic.new("title" => "", "content" => "abc"), :invalid?
+ assert_predicate Topic.new("title" => nil, "content" => "abc"), :valid?
end
def test_validates_inclusion_of_with_formatted_message
Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ), message: "option %{value} is not in the list")
- assert Topic.new("title" => "a", "content" => "abc").valid?
+ assert_predicate Topic.new("title" => "a", "content" => "abc"), :valid?
t = Topic.new("title" => "uhoh", "content" => "abc")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["option uhoh is not in the list"], t.errors[:title]
end
def test_validates_inclusion_of_with_within_option
Topic.validates_inclusion_of(:title, within: %w( a b c d e f g ))
- assert Topic.new("title" => "a", "content" => "abc").valid?
+ assert_predicate Topic.new("title" => "a", "content" => "abc"), :valid?
t = Topic.new("title" => "uhoh", "content" => "abc")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
end
def test_validates_inclusion_of_for_ruby_class
@@ -112,12 +112,12 @@ class InclusionValidationTest < ActiveModel::TestCase
p = Person.new
p.karma = "Lifo"
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["is not included in the list"], p.errors[:karma]
p.karma = "monkey"
- assert p.valid?
+ assert_predicate p, :valid?
ensure
Person.clear_validators!
end
@@ -128,10 +128,10 @@ class InclusionValidationTest < ActiveModel::TestCase
t = Topic.new
t.title = "wasabi"
t.author_name = "sikachu"
- assert t.invalid?
+ assert_predicate t, :invalid?
t.title = "elephant"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_inclusion_of_with_symbol
@@ -144,7 +144,7 @@ class InclusionValidationTest < ActiveModel::TestCase
%w()
end
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["is not included in the list"], p.errors[:karma]
p = Person.new
@@ -154,7 +154,7 @@ class InclusionValidationTest < ActiveModel::TestCase
%w(Lifo)
end
- assert p.valid?
+ assert_predicate p, :valid?
ensure
Person.clear_validators!
end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 42f76f3e3c..774a2cde74 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -13,153 +13,153 @@ class LengthValidationTest < ActiveModel::TestCase
def test_validates_length_of_with_allow_nil
Topic.validates_length_of(:title, is: 5, allow_nil: true)
- assert Topic.new("title" => "ab").invalid?
- assert Topic.new("title" => "").invalid?
- assert Topic.new("title" => nil).valid?
- assert Topic.new("title" => "abcde").valid?
+ assert_predicate Topic.new("title" => "ab"), :invalid?
+ assert_predicate Topic.new("title" => ""), :invalid?
+ assert_predicate Topic.new("title" => nil), :valid?
+ assert_predicate Topic.new("title" => "abcde"), :valid?
end
def test_validates_length_of_with_allow_blank
Topic.validates_length_of(:title, is: 5, allow_blank: true)
- assert Topic.new("title" => "ab").invalid?
- assert Topic.new("title" => "").valid?
- assert Topic.new("title" => nil).valid?
- assert Topic.new("title" => "abcde").valid?
+ assert_predicate Topic.new("title" => "ab"), :invalid?
+ assert_predicate Topic.new("title" => ""), :valid?
+ assert_predicate Topic.new("title" => nil), :valid?
+ assert_predicate Topic.new("title" => "abcde"), :valid?
end
def test_validates_length_of_using_minimum
Topic.validates_length_of :title, minimum: 5
t = Topic.new("title" => "valid", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = "not"
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is too short (minimum is 5 characters)"], t.errors[:title]
t.title = ""
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is too short (minimum is 5 characters)"], t.errors[:title]
t.title = nil
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
end
def test_validates_length_of_using_maximum_should_allow_nil
Topic.validates_length_of :title, maximum: 10
t = Topic.new
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_optionally_validates_length_of_using_minimum
Topic.validates_length_of :title, minimum: 5, allow_nil: true
t = Topic.new("title" => "valid", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = nil
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_length_of_using_maximum
Topic.validates_length_of :title, maximum: 5
t = Topic.new("title" => "valid", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = "notvalid"
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is too long (maximum is 5 characters)"], t.errors[:title]
t.title = ""
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_optionally_validates_length_of_using_maximum
Topic.validates_length_of :title, maximum: 5, allow_nil: true
t = Topic.new("title" => "valid", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = nil
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_length_of_using_within
Topic.validates_length_of(:title, :content, within: 3..5)
t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content]
t.title = nil
t.content = nil
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
assert_equal ["is too short (minimum is 3 characters)"], t.errors[:content]
t.title = "abe"
t.content = "mad"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_length_of_using_within_with_exclusive_range
Topic.validates_length_of(:title, within: 4...10)
t = Topic.new("title" => "9 chars!!")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = "Now I'm 10"
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["is too long (maximum is 9 characters)"], t.errors[:title]
t.title = "Four"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_optionally_validates_length_of_using_within
Topic.validates_length_of :title, :content, within: 3..5, allow_nil: true
t = Topic.new("title" => "abc", "content" => "abcd")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = nil
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_length_of_using_is
Topic.validates_length_of :title, is: 5
t = Topic.new("title" => "valid", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = "notvalid"
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is the wrong length (should be 5 characters)"], t.errors[:title]
t.title = ""
- assert t.invalid?
+ assert_predicate t, :invalid?
t.title = nil
- assert t.invalid?
+ assert_predicate t, :invalid?
end
def test_optionally_validates_length_of_using_is
Topic.validates_length_of :title, is: 5, allow_nil: true
t = Topic.new("title" => "valid", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = nil
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_length_of_using_bignum
@@ -187,45 +187,45 @@ class LengthValidationTest < ActiveModel::TestCase
def test_validates_length_of_custom_errors_for_minimum_with_message
Topic.validates_length_of(:title, minimum: 5, message: "boo %{count}")
t = Topic.new("title" => "uhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["boo 5"], t.errors[:title]
end
def test_validates_length_of_custom_errors_for_minimum_with_too_short
Topic.validates_length_of(:title, minimum: 5, too_short: "hoo %{count}")
t = Topic.new("title" => "uhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 5"], t.errors[:title]
end
def test_validates_length_of_custom_errors_for_maximum_with_message
Topic.validates_length_of(:title, maximum: 5, message: "boo %{count}")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["boo 5"], t.errors[:title]
end
def test_validates_length_of_custom_errors_for_in
Topic.validates_length_of(:title, in: 10..20, message: "hoo %{count}")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 10"], t.errors["title"]
t = Topic.new("title" => "uhohuhohuhohuhohuhohuhohuhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 20"], t.errors["title"]
end
def test_validates_length_of_custom_errors_for_maximum_with_too_long
Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -233,29 +233,29 @@ class LengthValidationTest < ActiveModel::TestCase
Topic.validates_length_of :title, minimum: 3, maximum: 5, too_short: "too short", too_long: "too long"
t = Topic.new(title: "a")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["too short"], t.errors["title"]
t = Topic.new(title: "aaaaaa")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["too long"], t.errors["title"]
end
def test_validates_length_of_custom_errors_for_is_with_message
Topic.validates_length_of(:title, is: 5, message: "boo %{count}")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["boo 5"], t.errors["title"]
end
def test_validates_length_of_custom_errors_for_is_with_wrong_length
Topic.validates_length_of(:title, is: 5, wrong_length: "hoo %{count}")
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["hoo 5"], t.errors["title"]
end
@@ -263,11 +263,11 @@ class LengthValidationTest < ActiveModel::TestCase
Topic.validates_length_of :title, minimum: 5
t = Topic.new("title" => "一二三四五", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = "一二三四"
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
end
@@ -275,11 +275,11 @@ class LengthValidationTest < ActiveModel::TestCase
Topic.validates_length_of :title, maximum: 5
t = Topic.new("title" => "一二三四五", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = "一二34五六"
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"]
end
@@ -287,12 +287,12 @@ class LengthValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, :content, within: 3..5)
t = Topic.new("title" => "一二", "content" => "12三四五六七")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content]
t.title = "一二三"
t.content = "12三"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_optionally_validates_length_of_using_within_utf8
@@ -312,11 +312,11 @@ class LengthValidationTest < ActiveModel::TestCase
Topic.validates_length_of :title, is: 5
t = Topic.new("title" => "一二345", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = "一二345六"
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"]
end
@@ -324,11 +324,11 @@ class LengthValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:approved, is: 4)
t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1)
- assert t.invalid?
- assert t.errors[:approved].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:approved], :any?
t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1234)
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_length_of_for_ruby_class
@@ -336,12 +336,12 @@ class LengthValidationTest < ActiveModel::TestCase
p = Person.new
p.karma = "Pix"
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["is too short (minimum is 5 characters)"], p.errors[:karma]
p.karma = "The Smiths"
- assert p.valid?
+ assert_predicate p, :valid?
ensure
Person.clear_validators!
end
@@ -350,80 +350,80 @@ class LengthValidationTest < ActiveModel::TestCase
Topic.validates_length_of(:title, within: 5..Float::INFINITY)
t = Topic.new("title" => "1234")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
t.title = "12345"
- assert t.valid?
+ assert_predicate t, :valid?
Topic.validates_length_of(:author_name, maximum: Float::INFINITY)
- assert t.valid?
+ assert_predicate t, :valid?
t.author_name = "A very long author name that should still be valid." * 100
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_length_of_using_maximum_should_not_allow_nil_when_nil_not_allowed
Topic.validates_length_of :title, maximum: 10, allow_nil: false
t = Topic.new
- assert t.invalid?
+ assert_predicate t, :invalid?
end
def test_validates_length_of_using_maximum_should_not_allow_nil_and_empty_string_when_blank_not_allowed
Topic.validates_length_of :title, maximum: 10, allow_blank: false
t = Topic.new
- assert t.invalid?
+ assert_predicate t, :invalid?
t.title = ""
- assert t.invalid?
+ assert_predicate t, :invalid?
end
def test_validates_length_of_using_both_minimum_and_maximum_should_not_allow_nil
Topic.validates_length_of :title, minimum: 5, maximum: 10
t = Topic.new
- assert t.invalid?
+ assert_predicate t, :invalid?
end
def test_validates_length_of_using_minimum_0_should_not_allow_nil
Topic.validates_length_of :title, minimum: 0
t = Topic.new
- assert t.invalid?
+ assert_predicate t, :invalid?
t.title = ""
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_length_of_using_is_0_should_not_allow_nil
Topic.validates_length_of :title, is: 0
t = Topic.new
- assert t.invalid?
+ assert_predicate t, :invalid?
t.title = ""
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_with_diff_in_option
Topic.validates_length_of(:title, is: 5)
Topic.validates_length_of(:title, is: 5, if: Proc.new { false })
- assert Topic.new("title" => "david").valid?
- assert Topic.new("title" => "david2").invalid?
+ assert_predicate Topic.new("title" => "david"), :valid?
+ assert_predicate Topic.new("title" => "david2"), :invalid?
end
def test_validates_length_of_using_proc_as_maximum
Topic.validates_length_of :title, maximum: ->(model) { 5 }
t = Topic.new("title" => "valid", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = "notvalid"
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is too long (maximum is 5 characters)"], t.errors[:title]
t.title = ""
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_length_of_using_proc_as_maximum_with_model_method
@@ -431,14 +431,14 @@ class LengthValidationTest < ActiveModel::TestCase
Topic.validates_length_of :title, maximum: Proc.new(&:max_title_length)
t = Topic.new("title" => "valid", "content" => "whatever")
- assert t.valid?
+ assert_predicate t, :valid?
t.title = "notvalid"
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["is too long (maximum is 5 characters)"], t.errors[:title]
t.title = ""
- assert t.valid?
+ assert_predicate t, :valid?
end
end
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index fbbaf8a30c..01b78ae72e 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -20,7 +20,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
INTEGERS = [0, 10, -10] + INTEGER_STRINGS
- BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
+ BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal(bd) }
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "0xinvalidhex", "0Xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
INFINITY = [1.0 / 0.0]
@@ -81,10 +81,10 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_with_greater_than_using_differing_numeric_types
- Topic.validates_numericality_of :approved, greater_than: BigDecimal.new("97.18")
+ Topic.validates_numericality_of :approved, greater_than: BigDecimal("97.18")
- invalid!([-97.18, BigDecimal.new("97.18"), BigDecimal("-97.18")], "must be greater than 97.18")
- valid!([97.19, 98, BigDecimal.new("98"), BigDecimal.new("97.19")])
+ invalid!([-97.18, BigDecimal("97.18"), BigDecimal("-97.18")], "must be greater than 97.18")
+ valid!([97.19, 98, BigDecimal("98"), BigDecimal("97.19")])
end
def test_validates_numericality_with_greater_than_using_string_value
@@ -102,10 +102,10 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_with_greater_than_or_equal_using_differing_numeric_types
- Topic.validates_numericality_of :approved, greater_than_or_equal_to: BigDecimal.new("97.18")
+ Topic.validates_numericality_of :approved, greater_than_or_equal_to: BigDecimal("97.18")
- invalid!([-97.18, 97.17, 97, BigDecimal.new("97.17"), BigDecimal.new("-97.18")], "must be greater than or equal to 97.18")
- valid!([97.18, 98, BigDecimal.new("97.19")])
+ invalid!([-97.18, 97.17, 97, BigDecimal("97.17"), BigDecimal("-97.18")], "must be greater than or equal to 97.18")
+ valid!([97.18, 98, BigDecimal("97.19")])
end
def test_validates_numericality_with_greater_than_or_equal_using_string_value
@@ -123,10 +123,10 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_with_equal_to_using_differing_numeric_types
- Topic.validates_numericality_of :approved, equal_to: BigDecimal.new("97.18")
+ Topic.validates_numericality_of :approved, equal_to: BigDecimal("97.18")
invalid!([-97.18], "must be equal to 97.18")
- valid!([BigDecimal.new("97.18")])
+ valid!([BigDecimal("97.18")])
end
def test_validates_numericality_with_equal_to_using_string_value
@@ -144,10 +144,10 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_with_less_than_using_differing_numeric_types
- Topic.validates_numericality_of :approved, less_than: BigDecimal.new("97.18")
+ Topic.validates_numericality_of :approved, less_than: BigDecimal("97.18")
- invalid!([97.18, BigDecimal.new("97.18")], "must be less than 97.18")
- valid!([-97.0, 97.0, -97, 97, BigDecimal.new("-97"), BigDecimal.new("97")])
+ invalid!([97.18, BigDecimal("97.18")], "must be less than 97.18")
+ valid!([-97.0, 97.0, -97, 97, BigDecimal("-97"), BigDecimal("97")])
end
def test_validates_numericality_with_less_than_using_string_value
@@ -165,10 +165,10 @@ class NumericalityValidationTest < ActiveModel::TestCase
end
def test_validates_numericality_with_less_than_or_equal_to_using_differing_numeric_types
- Topic.validates_numericality_of :approved, less_than_or_equal_to: BigDecimal.new("97.18")
+ Topic.validates_numericality_of :approved, less_than_or_equal_to: BigDecimal("97.18")
invalid!([97.19, 98], "must be less than or equal to 97.18")
- valid!([-97.18, BigDecimal.new("-97.18"), BigDecimal.new("97.18")])
+ valid!([-97.18, BigDecimal("-97.18"), BigDecimal("97.18")])
end
def test_validates_numericality_with_less_than_or_equal_using_string_value
@@ -237,13 +237,13 @@ class NumericalityValidationTest < ActiveModel::TestCase
Topic.validates_numericality_of :approved, less_than: 4, message: "smaller than %{count}"
topic = Topic.new("title" => "numeric test", "approved" => 10)
- assert !topic.valid?
+ assert_not_predicate topic, :valid?
assert_equal ["smaller than 4"], topic.errors[:approved]
Topic.validates_numericality_of :approved, greater_than: 4, message: "greater than %{count}"
topic = Topic.new("title" => "numeric test", "approved" => 1)
- assert !topic.valid?
+ assert_not_predicate topic, :valid?
assert_equal ["greater than 4"], topic.errors[:approved]
end
@@ -252,12 +252,12 @@ class NumericalityValidationTest < ActiveModel::TestCase
p = Person.new
p.karma = "Pix"
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["is not a number"], p.errors[:karma]
p.karma = "1234"
- assert p.valid?
+ assert_predicate p, :valid?
ensure
Person.clear_validators!
end
@@ -268,7 +268,7 @@ class NumericalityValidationTest < ActiveModel::TestCase
topic = Topic.new
topic.approved = (base + 1).to_s
- assert topic.invalid?
+ assert_predicate topic, :invalid?
end
def test_validates_numericality_with_invalid_args
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
index 22c2f0af87..c3eca41070 100644
--- a/activemodel/test/cases/validations/presence_validation_test.rb
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -17,25 +17,25 @@ class PresenceValidationTest < ActiveModel::TestCase
Topic.validates_presence_of(:title, :content)
t = Topic.new
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["can't be blank"], t.errors[:title]
assert_equal ["can't be blank"], t.errors[:content]
t.title = "something"
t.content = " "
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["can't be blank"], t.errors[:content]
t.content = "like stuff"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_accepts_array_arguments
Topic.validates_presence_of %w(title content)
t = Topic.new
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["can't be blank"], t.errors[:title]
assert_equal ["can't be blank"], t.errors[:content]
end
@@ -43,7 +43,7 @@ class PresenceValidationTest < ActiveModel::TestCase
def test_validates_acceptance_of_with_custom_error_using_quotes
Person.validates_presence_of :karma, message: "This string contains 'single' and \"double\" quotes"
p = Person.new
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last
end
@@ -51,24 +51,24 @@ class PresenceValidationTest < ActiveModel::TestCase
Person.validates_presence_of :karma
p = Person.new
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["can't be blank"], p.errors[:karma]
p.karma = "Cold"
- assert p.valid?
+ assert_predicate p, :valid?
end
def test_validates_presence_of_for_ruby_class_with_custom_reader
CustomReader.validates_presence_of :karma
p = CustomReader.new
- assert p.invalid?
+ assert_predicate p, :invalid?
assert_equal ["can't be blank"], p.errors[:karma]
p[:karma] = "Cold"
- assert p.valid?
+ assert_predicate p, :valid?
end
def test_validates_presence_of_with_allow_nil_option
@@ -78,7 +78,7 @@ class PresenceValidationTest < ActiveModel::TestCase
assert t.valid?, t.errors.full_messages
t.title = ""
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["can't be blank"], t.errors[:title]
t.title = " "
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
index 7f32f5dc74..80c347703a 100644
--- a/activemodel/test/cases/validations/validates_test.rb
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -37,7 +37,7 @@ class ValidatesTest < ActiveModel::TestCase
person = Person.new
person.title = 123
- assert person.valid?
+ assert_predicate person, :valid?
end
def test_validates_with_built_in_validation_and_options
@@ -71,7 +71,7 @@ class ValidatesTest < ActiveModel::TestCase
def test_validates_with_if_as_shared_conditions
Person.validates :karma, presence: true, email: true, if: :condition_is_false
person = Person.new
- assert person.valid?
+ assert_predicate person, :valid?
end
def test_validates_with_unless_as_local_conditions
@@ -84,40 +84,40 @@ class ValidatesTest < ActiveModel::TestCase
def test_validates_with_unless_shared_conditions
Person.validates :karma, presence: true, email: true, unless: :condition_is_true
person = Person.new
- assert person.valid?
+ assert_predicate person, :valid?
end
def test_validates_with_allow_nil_shared_conditions
Person.validates :karma, length: { minimum: 20 }, email: true, allow_nil: true
person = Person.new
- assert person.valid?
+ assert_predicate person, :valid?
end
def test_validates_with_regexp
Person.validates :karma, format: /positive|negative/
person = Person.new
- assert person.invalid?
+ assert_predicate person, :invalid?
assert_equal ["is invalid"], person.errors[:karma]
person.karma = "positive"
- assert person.valid?
+ assert_predicate person, :valid?
end
def test_validates_with_array
Person.validates :gender, inclusion: %w(m f)
person = Person.new
- assert person.invalid?
+ assert_predicate person, :invalid?
assert_equal ["is not included in the list"], person.errors[:gender]
person.gender = "m"
- assert person.valid?
+ assert_predicate person, :valid?
end
def test_validates_with_range
Person.validates :karma, length: 6..20
person = Person.new
- assert person.invalid?
+ assert_predicate person, :invalid?
assert_equal ["is too short (minimum is 6 characters)"], person.errors[:karma]
person.karma = "something"
- assert person.valid?
+ assert_predicate person, :valid?
end
def test_validates_with_validator_class_and_options
@@ -159,7 +159,7 @@ class ValidatesTest < ActiveModel::TestCase
topic = Topic.new
topic.title = "What's happening"
topic.title_confirmation = "Not this"
- assert !topic.valid?
+ assert_not_predicate topic, :valid?
assert_equal ["Y U NO CONFIRM"], topic.errors[:title_confirmation]
end
end
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 13ef5e6a31..8239792c79 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -65,7 +65,7 @@ class ValidatesWithTest < ActiveModel::TestCase
test "with multiple classes" do
Topic.validates_with(ValidatorThatAddsErrors, OtherValidatorThatAddsErrors)
topic = Topic.new
- assert topic.invalid?
+ assert_predicate topic, :invalid?
assert_includes topic.errors[:base], ERROR_MESSAGE
assert_includes topic.errors[:base], OTHER_ERROR_MESSAGE
end
@@ -79,21 +79,21 @@ class ValidatesWithTest < ActiveModel::TestCase
validator.expect(:is_a?, false, [String])
Topic.validates_with(validator, if: :condition_is_true, foo: :bar)
- assert topic.valid?
+ assert_predicate topic, :valid?
validator.verify
end
test "validates_with with options" do
Topic.validates_with(ValidatorThatValidatesOptions, field: :first_name)
topic = Topic.new
- assert topic.invalid?
+ assert_predicate topic, :invalid?
assert_includes topic.errors[:base], ERROR_MESSAGE
end
test "validates_with each validator" do
Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content])
topic = Topic.new title: "Title", content: "Content"
- assert topic.invalid?
+ assert_predicate topic, :invalid?
assert_equal ["Value is Title"], topic.errors[:title]
assert_equal ["Value is Content"], topic.errors[:content]
end
@@ -113,28 +113,28 @@ class ValidatesWithTest < ActiveModel::TestCase
test "each validator skip nil values if :allow_nil is set to true" do
Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content], allow_nil: true)
topic = Topic.new content: ""
- assert topic.invalid?
- assert topic.errors[:title].empty?
+ assert_predicate topic, :invalid?
+ assert_empty topic.errors[:title]
assert_equal ["Value is "], topic.errors[:content]
end
test "each validator skip blank values if :allow_blank is set to true" do
Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content], allow_blank: true)
topic = Topic.new content: ""
- assert topic.valid?
- assert topic.errors[:title].empty?
- assert topic.errors[:content].empty?
+ assert_predicate topic, :valid?
+ assert_empty topic.errors[:title]
+ assert_empty topic.errors[:content]
end
test "validates_with can validate with an instance method" do
Topic.validates :title, with: :my_validation
topic = Topic.new title: "foo"
- assert topic.valid?
- assert topic.errors[:title].empty?
+ assert_predicate topic, :valid?
+ assert_empty topic.errors[:title]
topic = Topic.new
- assert !topic.valid?
+ assert_not_predicate topic, :valid?
assert_equal ["is missing"], topic.errors[:title]
end
@@ -142,8 +142,8 @@ class ValidatesWithTest < ActiveModel::TestCase
Topic.validates :title, :content, with: :my_validation_with_arg
topic = Topic.new title: "foo"
- assert !topic.valid?
- assert topic.errors[:title].empty?
+ assert_not_predicate topic, :valid?
+ assert_empty topic.errors[:title]
assert_equal ["is missing"], topic.errors[:content]
end
end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index ab8c41bbd0..7776233db5 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -30,7 +30,7 @@ class ValidationsTest < ActiveModel::TestCase
def test_single_attr_validation_and_error_msg
r = Reply.new
r.title = "There's no content!"
- assert r.invalid?
+ assert_predicate r, :invalid?
assert r.errors[:content].any?, "A reply without content should mark that attribute as invalid"
assert_equal ["is Empty"], r.errors["content"], "A reply without content should contain an error"
assert_equal 1, r.errors.count
@@ -38,7 +38,7 @@ class ValidationsTest < ActiveModel::TestCase
def test_double_attr_validation_and_error_msg
r = Reply.new
- assert r.invalid?
+ assert_predicate r, :invalid?
assert r.errors[:title].any?, "A reply without title should mark that attribute as invalid"
assert_equal ["is Empty"], r.errors["title"], "A reply without title should contain an error"
@@ -111,8 +111,8 @@ class ValidationsTest < ActiveModel::TestCase
def test_errors_empty_after_errors_on_check
t = Topic.new
- assert t.errors[:id].empty?
- assert t.errors.empty?
+ assert_empty t.errors[:id]
+ assert_empty t.errors
end
def test_validates_each
@@ -122,7 +122,7 @@ class ValidationsTest < ActiveModel::TestCase
hits += 1
end
t = Topic.new("title" => "valid", "content" => "whatever")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal 4, hits
assert_equal %w(gotcha gotcha), t.errors[:title]
assert_equal %w(gotcha gotcha), t.errors[:content]
@@ -135,7 +135,7 @@ class ValidationsTest < ActiveModel::TestCase
hits += 1
end
t = CustomReader.new("title" => "valid", "content" => "whatever")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal 4, hits
assert_equal %w(gotcha gotcha), t.errors[:title]
assert_equal %w(gotcha gotcha), t.errors[:content]
@@ -146,16 +146,16 @@ class ValidationsTest < ActiveModel::TestCase
def test_validate_block
Topic.validate { errors.add("title", "will never be valid") }
t = Topic.new("title" => "Title", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["will never be valid"], t.errors["title"]
end
def test_validate_block_with_params
Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
t = Topic.new("title" => "Title", "content" => "whatever")
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
assert_equal ["will never be valid"], t.errors["title"]
end
@@ -214,7 +214,7 @@ class ValidationsTest < ActiveModel::TestCase
def test_errors_conversions
Topic.validates_presence_of %w(title content)
t = Topic.new
- assert t.invalid?
+ assert_predicate t, :invalid?
xml = t.errors.to_xml
assert_match %r{<errors>}, xml
@@ -232,14 +232,14 @@ class ValidationsTest < ActiveModel::TestCase
Topic.validates_length_of :title, minimum: 2
t = Topic.new("title" => "")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal "can't be blank", t.errors["title"].first
Topic.validates_presence_of :title, :author_name
Topic.validate { errors.add("author_email_address", "will never be valid") }
Topic.validates_length_of :title, :content, minimum: 2
t = Topic.new title: ""
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal :title, key = t.errors.keys[0]
assert_equal "can't be blank", t.errors[key][0]
@@ -258,8 +258,8 @@ class ValidationsTest < ActiveModel::TestCase
t = Topic.new(title: "")
# If block should not fire
- assert t.valid?
- assert t.author_name.nil?
+ assert_predicate t, :valid?
+ assert_predicate t.author_name, :nil?
# If block should fire
assert t.invalid?(:update)
@@ -270,18 +270,18 @@ class ValidationsTest < ActiveModel::TestCase
Topic.validates_presence_of :title
t = Topic.new
- assert t.invalid?
- assert t.errors[:title].any?
+ assert_predicate t, :invalid?
+ assert_predicate t.errors[:title], :any?
t.title = "Things are going to change"
- assert !t.invalid?
+ assert_not_predicate t, :invalid?
end
def test_validation_with_message_as_proc
Topic.validates_presence_of(:title, message: proc { "no blanks here".upcase })
t = Topic.new
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["NO BLANKS HERE"], t.errors[:title]
end
@@ -331,13 +331,13 @@ class ValidationsTest < ActiveModel::TestCase
Topic.validates :content, length: { minimum: 10 }
topic = Topic.new
- assert topic.invalid?
+ assert_predicate topic, :invalid?
assert_equal 3, topic.errors.size
topic.title = "Some Title"
topic.author_name = "Some Author"
topic.content = "Some Content Whose Length is more than 10."
- assert topic.valid?
+ assert_predicate topic, :valid?
end
def test_validate
@@ -381,7 +381,7 @@ class ValidationsTest < ActiveModel::TestCase
def test_strict_validation_not_fails
Topic.validates :title, strict: true, presence: true
- assert Topic.new(title: "hello").valid?
+ assert_predicate Topic.new(title: "hello"), :valid?
end
def test_strict_validation_particular_validator
@@ -414,7 +414,7 @@ class ValidationsTest < ActiveModel::TestCase
def test_validates_with_false_hash_value
Topic.validates :title, presence: false
- assert Topic.new.valid?
+ assert_predicate Topic.new, :valid?
end
def test_strict_validation_error_message
@@ -439,19 +439,19 @@ class ValidationsTest < ActiveModel::TestCase
duped = topic.dup
duped.title = nil
- assert duped.invalid?
+ assert_predicate duped, :invalid?
topic.title = nil
duped.title = "Mathematics"
- assert topic.invalid?
- assert duped.valid?
+ assert_predicate topic, :invalid?
+ assert_predicate duped, :valid?
end
def test_validation_with_message_as_proc_that_takes_a_record_as_a_parameter
Topic.validates_presence_of(:title, message: proc { |record| "You have failed me for the last time, #{record.author_name}." })
t = Topic.new(author_name: "Admiral")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["You have failed me for the last time, Admiral."], t.errors[:title]
end
@@ -459,7 +459,7 @@ class ValidationsTest < ActiveModel::TestCase
Topic.validates_presence_of(:title, message: proc { |record, data| "#{data[:attribute]} is missing. You have failed me for the last time, #{record.author_name}." })
t = Topic.new(author_name: "Admiral")
- assert t.invalid?
+ assert_predicate t, :invalid?
assert_equal ["Title is missing. You have failed me for the last time, Admiral."], t.errors[:title]
end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 34ef5c79e1..d73c0859ee 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,384 +1,3 @@
-* Fix `bin/rails db:migrate` with specified `VERSION`.
- `bin/rails db:migrate` with empty VERSION behaves as without `VERSION`.
- Check a format of `VERSION`: Allow a migration version number
- or name of a migration file. Raise error if format of `VERSION` is invalid.
- Raise error if target migration doesn't exist.
- *bogdanvlviv*
-* Fixed a bug where column orders for an index weren't written to
- db/schema.rb when using the sqlite adapter.
-
- Fixes #30902.
-
- *Paul Kuruvilla*
-
-* 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` which will be raised
- when lock wait timeout exceeded.
-
- *Gabriel Courtemanche*
-
-* Remove deprecated `#migration_keys`.
-
- *Ryuta Kamizono*
-
-* Automatically guess the inverse associations for STI.
-
- *Yuichiro Kaneko*
-
-* Ensure `sum` honors `distinct` on `has_many :through` associations
-
- Fixes #16791.
-
- *Aaron Wortham*
-
-* Add `binary` fixture helper method.
-
- *Atsushi Yoshida*
-
-* When using `Relation#or`, extract the common conditions and put them before the OR condition.
-
- *Maxime Handfield Lapointe*
-
-* `Relation#or` now accepts two relations who have different values for
- `references` only, as `references` can be implicitly called by `where`.
-
- Fixes #29411.
-
- *Sean Griffin*
-
-* `ApplicationRecord` is no longer generated when generating models. If you
- need to generate it, it can be created with `rails g application_record`.
-
- *Lisa Ugray*
-
-* Fix `COUNT(DISTINCT ...)` with `ORDER BY` and `LIMIT` to keep the existing select list.
-
- *Ryuta Kamizono*
-
-* When a `has_one` association is destroyed by `dependent: destroy`,
- `destroyed_by_association` will now be set to the reflection, matching the
- behaviour of `has_many` associations.
-
- *Lisa Ugray*
-
-* Fix `unscoped(where: [columns])` removing the wrong bind values
-
- When the `where` is called on a relation after a `or`, unscoping the column of that later `where` removed
- bind values used by the `or` instead. (possibly other cases too)
-
- ```
- Post.where(id: 1).or(Post.where(id: 2)).where(foo: 3).unscope(where: :foo).to_sql
- # Currently:
- # SELECT "posts".* FROM "posts" WHERE ("posts"."id" = 2 OR "posts"."id" = 3)
- # With fix:
- # SELECT "posts".* FROM "posts" WHERE ("posts"."id" = 1 OR "posts"."id" = 2)
- ```
-
- *Maxime Handfield Lapointe*
-
-* Values constructed using multi-parameter assignment will now use the
- post-type-cast value for rendering in single-field form inputs.
-
- *Sean Griffin*
-
-* `Relation#joins` is no longer affected by the target model's
- `current_scope`, with the exception of `unscoped`.
-
- Fixes #29338.
-
- *Sean Griffin*
-
-* Change sqlite3 boolean serialization to use 1 and 0
-
- SQLite natively recognizes 1 and 0 as true and false, but does not natively
- recognize 't' and 'f' as was previously serialized.
-
- This change in serialization requires a migration of stored boolean data
- for SQLite databases, so it's implemented behind a configuration flag
- whose default false value is deprecated.
-
- *Lisa Ugray*
-
-* Skip query caching when working with batches of records (`find_each`, `find_in_batches`,
- `in_batches`).
-
- Previously, records would be fetched in batches, but all records would be retained in memory
- until the end of the request or job.
-
- *Eugene Kenny*
-
-* Prevent errors raised by `sql.active_record` notification subscribers from being converted into
- `ActiveRecord::StatementInvalid` exceptions.
-
- *Dennis Taylor*
-
-* Fix eager loading/preloading association with scope including joins.
-
- Fixes #28324.
-
- *Ryuta Kamizono*
-
-* Fix transactions to apply state to child transactions
-
- Previously, if you had a nested transaction and the outer transaction was rolledback, the record from the
- inner transaction would still be marked as persisted.
-
- This change fixes that by applying the state of the parent transaction to the child transaction when the
- parent transaction is rolledback. This will correctly mark records from the inner transaction as not persisted.
-
- *Eileen M. Uchitelle*, *Aaron Patterson*
-
-* Deprecate `set_state` method in `TransactionState`
-
- Deprecated the `set_state` method in favor of setting the state via specific methods. If you need to mark the
- state of the transaction you can now use `rollback!`, `commit!` or `nullify!` instead of
- `set_state(:rolledback)`, `set_state(:committed)`, or `set_state(nil)`.
-
- *Eileen M. Uchitelle*, *Aaron Patterson*
-
-* Deprecate delegating to `arel` in `Relation`.
-
- *Ryuta Kamizono*
-
-* Fix eager loading to respect `store_full_sti_class` setting.
-
- *Ryuta Kamizono*
-
-* Query cache was unavailable when entering the `ActiveRecord::Base.cache` block
- without being connected.
-
- *Tsukasa Oishi*
-
-* Previously, when building records using a `has_many :through` association,
- if the child records were deleted before the parent was saved, they would
- still be persisted. Now, if child records are deleted before the parent is saved
- on a `has_many :through` association, the child records will not be persisted.
-
- *Tobias Kraze*
-
-* Merging two relations representing nested joins no longer transforms the joins of
- the merged relation into LEFT OUTER JOIN. Example to clarify:
-
- ```
- Author.joins(:posts).merge(Post.joins(:comments))
- # Before the change:
- #=> SELECT ... FROM authors INNER JOIN posts ON ... LEFT OUTER JOIN comments ON...
-
- # After the change:
- #=> SELECT ... FROM authors INNER JOIN posts ON ... INNER JOIN comments ON...
- ```
-
- TODO: Add to the Rails 5.2 upgrade guide
-
- *Maxime Handfield Lapointe*
-
-* `ActiveRecord::Persistence#touch` does not work well when optimistic locking enabled and
- `locking_column`, without default value, is null in the database.
-
- *bogdanvlviv*
-
-* Fix destroying existing object does not work well when optimistic locking enabled and
- `locking_column` is null in the database.
-
- *bogdanvlviv*
-
-* Use bulk INSERT to insert fixtures for better performance.
-
- *Kir Shatrov*
-
-* Prevent creation of bind param if casted value is nil.
-
- *Ryuta Kamizono*
-
-* Deprecate passing arguments and block at the same time to `count` and `sum` in `ActiveRecord::Calculations`.
-
- *Ryuta Kamizono*
-
-* Loading model schema from database is now thread-safe.
-
- Fixes #28589.
-
- *Vikrant Chaudhary*, *David Abdemoulaie*
-
-* Add `ActiveRecord::Base#cache_version` to support recyclable cache keys via the new versioned entries
- in `ActiveSupport::Cache`. This also means that `ActiveRecord::Base#cache_key` will now return a stable key
- that does not include a timestamp any more.
-
- NOTE: This feature is turned off by default, and `#cache_key` will still return cache keys with timestamps
- until you set `ActiveRecord::Base.cache_versioning = true`. That's the setting for all new apps on Rails 5.2+
-
- *DHH*
-
-* Respect `SchemaDumper.ignore_tables` in rake tasks for databases structure dump
-
- *Rusty Geldmacher*, *Guillermo Iguaran*
-
-* Add type caster to `RuntimeReflection#alias_name`
-
- Fixes #28959.
-
- *Jon Moss*
-
-* Deprecate `supports_statement_cache?`.
-
- *Ryuta Kamizono*
-
-* Raise error `UnknownMigrationVersionError` on the movement of migrations
- when the current migration does not exist.
-
- *bogdanvlviv*
-
-* Fix `bin/rails db:forward` first migration.
-
- *bogdanvlviv*
-
-* Support Descending Indexes for MySQL.
-
- MySQL 8.0.1 and higher supports descending indexes: `DESC` in an index definition is no longer ignored.
- See https://dev.mysql.com/doc/refman/8.0/en/descending-indexes.html.
-
- *Ryuta Kamizono*
-
-* Fix inconsistency with changed attributes when overriding AR attribute reader.
-
- *bogdanvlviv*
-
-* When calling the dynamic fixture accessor method with no arguments, it now returns all fixtures of this type.
- Previously this method always returned an empty array.
-
- *Kevin McPhillips*
-
-
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activerecord/CHANGELOG.md) for previous changes.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index f9e4444f07..cce00cbc3a 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2017 David Heinemeier Hansson
+Copyright (c) 2004-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index ba83a9adb2..19650b82ae 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -208,7 +208,7 @@ API documentation is at:
* http://api.rubyonrails.org
-Bug reports can be filed for the Ruby on Rails project here:
+Bug reports for the Ruby on Rails project can be filed here:
* https://github.com/rails/rails/issues
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 57c82bf469..591c451da5 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -68,7 +68,7 @@ end
(Dir["test/cases/**/*_test.rb"].reject {
|x| x.include?("/adapters/")
} + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file|
- sh(Gem.ruby, "-w" , "-Itest", file)
+ sh(Gem.ruby, "-w", "-Itest", file)
end || raise("Failures")
end
end
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 7ad06fe840..8e42a11df4 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -31,5 +31,5 @@ Gem::Specification.new do |s|
s.add_dependency "activesupport", version
s.add_dependency "activemodel", version
- s.add_dependency "arel", "9.0.0.alpha"
+ s.add_dependency "arel", ">= 9.0"
end
diff --git a/activerecord/bin/test b/activerecord/bin/test
index 83c192531e..9ecf27ce67 100755
--- a/activerecord/bin/test
+++ b/activerecord/bin/test
@@ -1,6 +1,12 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
+adapter_index = ARGV.index("--adapter") || ARGV.index("-a")
+if adapter_index
+ ARGV.delete_at(adapter_index)
+ ENV["ARCONN"] = ARGV.delete_at(adapter_index).strip
+end
+
COMPONENT_ROOT = File.expand_path("..", __dir__)
require_relative "../../tools/test"
@@ -17,4 +23,5 @@ module Minitest
end
end
+Minitest.load_plugins
Minitest.extensions.unshift "active_record"
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 5de6503144..b4377ad6be 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2004-2017 David Heinemeier Hansson
+# Copyright (c) 2004-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index e5e89734d2..27a641f05b 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -35,7 +35,7 @@ module ActiveRecord
# the database).
#
# class Customer < ActiveRecord::Base
- # composed_of :balance, class_name: "Money", mapping: %w(amount currency)
+ # composed_of :balance, class_name: "Money", mapping: %w(balance amount)
# composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
# end
#
@@ -177,9 +177,9 @@ module ActiveRecord
#
# Once a #composed_of relationship is specified for a model, records can be loaded from the database
# by specifying an instance of the value object in the conditions hash. The following example
- # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
+ # finds all customers with +address_street+ equal to "May Street" and +address_city+ equal to "Chicago":
#
- # Customer.where(balance: Money.new(20, "USD"))
+ # Customer.where(address: Address.new("May Street", "Chicago"))
#
module ClassMethods
# Adds reader and writer methods for manipulating a value object:
@@ -212,8 +212,7 @@ module ActiveRecord
#
# Option examples:
# composed_of :temperature, mapping: %w(reading celsius)
- # composed_of :balance, class_name: "Money", mapping: %w(balance amount),
- # converter: Proc.new { |balance| balance.to_money }
+ # composed_of :balance, class_name: "Money", mapping: %w(balance amount)
# composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ]
# composed_of :gps_location
# composed_of :gps_location, allow_nil: true
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 2b0b2864bc..403667fb70 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -2,8 +2,8 @@
module ActiveRecord
class AssociationRelation < Relation
- def initialize(klass, table, predicate_builder, association)
- super(klass, table, predicate_builder)
+ def initialize(klass, association)
+ super(klass)
@association = association
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 661605d3e5..b1cad0d0a4 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1061,12 +1061,6 @@ module ActiveRecord
# belongs_to :dungeon, inverse_of: :evil_wizard
# end
#
- # There are limitations to <tt>:inverse_of</tt> support:
- #
- # * does not work with <tt>:through</tt> associations.
- # * does not work with <tt>:polymorphic</tt> associations.
- # * inverse associations for #belongs_to associations #has_many are ignored.
- #
# For more information, see the documentation for the +:inverse_of+ option.
#
# == Deleting from associations
@@ -1279,6 +1273,9 @@ module ActiveRecord
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many
# association will use "person_id" as the default <tt>:foreign_key</tt>.
+ #
+ # If you are going to modify the association (rather than just read from it), then it is
+ # a good idea to set the <tt>:inverse_of</tt> option.
# [:foreign_type]
# Specify the column used to store the associated object's type, if this is a polymorphic
# association. By default this is guessed to be the name of the polymorphic association
@@ -1352,8 +1349,7 @@ module ActiveRecord
# <tt>:autosave</tt> to <tt>true</tt>.
# [:inverse_of]
# Specifies the name of the #belongs_to association on the associated object
- # that is the inverse of this #has_many association. Does not work in combination
- # with <tt>:through</tt> or <tt>:as</tt> options.
+ # that is the inverse of this #has_many association.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
# [:extend]
# Specifies a module or array of modules that will be extended into the association object returned.
@@ -1449,6 +1445,9 @@ module ActiveRecord
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association
# will use "person_id" as the default <tt>:foreign_key</tt>.
+ #
+ # If you are going to modify the association (rather than just read from it), then it is
+ # a good idea to set the <tt>:inverse_of</tt> option.
# [:foreign_type]
# Specify the column used to store the associated object's type, if this is a polymorphic
# association. By default this is guessed to be the name of the polymorphic association
@@ -1464,6 +1463,9 @@ module ActiveRecord
# <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
# source reflection. You can only use a <tt>:through</tt> query through a #has_one
# or #belongs_to association on the join model.
+ #
+ # If you are going to modify the association (rather than just read from it), then it is
+ # a good idea to set the <tt>:inverse_of</tt> option.
# [:source]
# Specifies the source association name used by #has_one <tt>:through</tt> queries.
# Only use it if the name cannot be inferred from the association.
@@ -1484,8 +1486,7 @@ module ActiveRecord
# <tt>:autosave</tt> to <tt>true</tt>.
# [:inverse_of]
# Specifies the name of the #belongs_to association on the associated object
- # that is the inverse of this #has_one association. Does not work in combination
- # with <tt>:through</tt> or <tt>:as</tt> options.
+ # that is the inverse of this #has_one association.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
# [:required]
# When set to +true+, the association will also have its presence validated.
@@ -1570,6 +1571,9 @@ module ActiveRecord
# association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
# <tt>belongs_to :favorite_person, class_name: "Person"</tt> will use a foreign key
# of "favorite_person_id".
+ #
+ # If you are going to modify the association (rather than just read from it), then it is
+ # a good idea to set the <tt>:inverse_of</tt> option.
# [:foreign_type]
# Specify the column used to store the associated object's type, if this is a polymorphic
# association. By default this is guessed to be the name of the association with a "_type"
@@ -1619,8 +1623,7 @@ module ActiveRecord
# +after_commit+ and +after_rollback+ callbacks are executed.
# [:inverse_of]
# Specifies the name of the #has_one or #has_many association on the associated
- # object that is the inverse of this #belongs_to association. Does not work in
- # combination with the <tt>:polymorphic</tt> options.
+ # object that is the inverse of this #belongs_to association.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
# [:optional]
# When set to +true+, the association will not have its presence validated.
@@ -1789,6 +1792,9 @@ module ActiveRecord
# of this class in lower-case and "_id" suffixed. So a Person class that makes
# a #has_and_belongs_to_many association to Project will use "person_id" as the
# default <tt>:foreign_key</tt>.
+ #
+ # If you are going to modify the association (rather than just read from it), then it is
+ # a good idea to set the <tt>:inverse_of</tt> option.
# [:association_foreign_key]
# Specify the foreign key used for the association on the receiving side of the association.
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 14881cfe17..4f3893588e 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -30,20 +30,12 @@ module ActiveRecord
join.left.scan(
/JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i
).size
- elsif join.respond_to? :left
+ elsif join.is_a?(Arel::Nodes::Join)
join.left.name == name ? 1 : 0
elsif join.is_a?(Hash)
join.fetch(name, 0)
else
- # this branch is reached by two tests:
- #
- # activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37
- # with :posts
- #
- # activerecord/test/cases/associations/eager_test.rb:1133
- # with :comments
- #
- 0
+ raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join"
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index ca1f9f1650..364f1fe74f 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -124,7 +124,7 @@ module ActiveRecord
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
# through association's scope)
def target_scope
- AssociationRelation.create(klass, klass.arel_table, klass.predicate_builder, self).merge!(klass.all)
+ AssociationRelation.create(klass, self).merge!(klass.all)
end
def extensions
@@ -156,9 +156,9 @@ module ActiveRecord
reset
end
- # We can't dump @reflection since it contains the scope proc
+ # We can't dump @reflection and @through_reflection since it contains the scope proc
def marshal_dump
- ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
+ ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
[@reflection.name, ivars]
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index ba54cd8f49..bd2012df84 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -12,17 +12,20 @@ module ActiveRecord
if record
raise_on_type_mismatch!(record)
update_counters_on_replace(record)
- replace_keys(record)
set_inverse_instance(record)
@updated = true
else
decrement_counters
- remove_keys
end
self.target = record
end
+ def target=(record)
+ replace_keys(record)
+ super
+ end
+
def default(&block)
writer(owner.instance_exec(&block)) if reader.nil?
end
@@ -49,9 +52,9 @@ module ActiveRecord
def update_counters(by)
if require_counter_update? && foreign_key_present?
if target && !stale_target?
- target.increment!(reflection.counter_cache_column, by)
+ target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
else
- klass.update_counters(target_id, reflection.counter_cache_column => by)
+ klass.update_counters(target_id, reflection.counter_cache_column => by, touch: reflection.options[:touch])
end
end
end
@@ -78,11 +81,8 @@ module ActiveRecord
end
def replace_keys(record)
- owner[reflection.foreign_key] = record._read_attribute(reflection.association_primary_key(record.class))
- end
-
- def remove_keys
- owner[reflection.foreign_key] = nil
+ owner[reflection.foreign_key] = record ?
+ record._read_attribute(reflection.association_primary_key(record.class)) : nil
end
def foreign_key_present?
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 4ce3474bd5..55d789c66a 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -13,12 +13,7 @@ module ActiveRecord
def replace_keys(record)
super
- owner[reflection.foreign_type] = record.class.base_class.name
- end
-
- def remove_keys
- super
- owner[reflection.foreign_type] = nil
+ owner[reflection.foreign_type] = record ? record.class.base_class.name : nil
end
def different_target?(record)
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index ca3032d967..7c69cd65ee 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -104,8 +104,8 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.define_readers(mixin, name)
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}(*args)
- association(:#{name}).reader(*args)
+ def #{name}
+ association(:#{name}).reader
end
CODE
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 9904ee4bed..c161454c1a 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -114,9 +114,13 @@ module ActiveRecord::Associations::Builder # :nodoc:
BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method)
}}
- model.after_save callback.(:saved_changes), if: :saved_changes?
- model.after_touch callback.(:changes_to_save)
- model.after_destroy callback.(:changes_to_save)
+ unless reflection.counter_cache_column
+ model.after_create callback.(:saved_changes), if: :saved_changes?
+ model.after_destroy callback.(:changes_to_save)
+ end
+
+ model.after_update callback.(:saved_changes), if: :saved_changes?
+ model.after_touch callback.(:changes_to_save)
end
def self.add_default_callbacks(model, reflection)
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index ed215fb22c..443ccaaa72 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -52,11 +52,11 @@ module ActiveRecord
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
- pk_type = reflection.association_primary_key_type
+ primary_key = reflection.association_primary_key
+ pk_type = klass.type_for_attribute(primary_key)
ids = Array(ids).reject(&:blank?)
ids.map! { |i| pk_type.cast(i) }
- primary_key = reflection.association_primary_key
records = klass.where(primary_key => ids).index_by do |r|
r.public_send(primary_key)
end.values_at(*ids).compact
@@ -79,7 +79,13 @@ module ActiveRecord
def find(*args)
if options[:inverse_of] && loaded?
args_flatten = args.flatten
- raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
+ model = scope.klass
+
+ if args_flatten.blank?
+ error_message = "Couldn't find #{model.name} without an ID"
+ raise RecordNotFound.new(error_message, model.name, model.primary_key, args)
+ end
+
result = find_by_scan(*args)
result_size = Array(result).size
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 8b4a48a38c..9a30198b95 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -32,7 +32,7 @@ module ActiveRecord
class CollectionProxy < Relation
def initialize(klass, association) #:nodoc:
@association = association
- super klass, klass.arel_table, klass.predicate_builder
+ super klass
extensions = association.extensions
extend(*extensions) if extensions.any?
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 07c7f28d2d..cf85a87fa7 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -63,7 +63,7 @@ module ActiveRecord
count = if reflection.has_cached_counter?
owner._read_attribute(reflection.counter_cache_column).to_i
else
- scope.count
+ scope.count(:all)
end
# If there's nothing in the database and @target has no new records
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index adbf52b87c..59929b8c4e 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -8,9 +8,7 @@ module ActiveRecord
def initialize(owner, reflection)
super
-
- @through_records = {}
- @through_association = nil
+ @through_records = {}
end
def concat(*records)
@@ -50,11 +48,6 @@ module ActiveRecord
end
private
-
- def through_association
- @through_association ||= owner.association(through_reflection.name)
- end
-
# The through record (built with build_record) is temporarily cached
# so that it may be reused if insert_record is subsequently called.
#
@@ -140,21 +133,15 @@ module ActiveRecord
scope = through_association.scope
scope.where! construct_join_attributes(*records)
+ scope = scope.where(through_scope_attributes)
case method
when :destroy
if scope.klass.primary_key
- count = scope.destroy_all.length
+ count = scope.destroy_all.count(&:destroyed?)
else
scope.each(&:_run_destroy_callbacks)
-
- arel = scope.arel
-
- stmt = Arel::DeleteManager.new
- stmt.from scope.klass.arel_table
- stmt.wheres = arel.constraints
-
- count = scope.klass.connection.delete(stmt, "SQL")
+ count = scope.delete_all
end
when :nullify
count = scope.update_all(source_reflection.foreign_key => nil)
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index 36746f9115..491282adf7 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -6,17 +6,16 @@ module ActiveRecord
class HasOneThroughAssociation < HasOneAssociation #:nodoc:
include ThroughAssociation
- def replace(record)
- create_through_record(record)
+ def replace(record, save = true)
+ create_through_record(record, save)
self.target = record
end
private
-
- def create_through_record(record)
+ def create_through_record(record, save)
ensure_not_nested
- through_proxy = owner.association(through_reflection.name)
+ through_proxy = through_association
through_record = through_proxy.load_target
if through_record && !record
@@ -30,7 +29,7 @@ module ActiveRecord
if through_record
through_record.update(attributes)
- elsif owner.new_record?
+ elsif owner.new_record? || !save
through_proxy.build(attributes)
else
through_proxy.create(attributes)
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index e1087be9b3..59320431ee 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -83,7 +83,7 @@ module ActiveRecord
# { author: :avatar }
# [ :books, { author: :avatar } ]
def preload(records, associations, preload_scope = nil)
- records = records.compact
+ records = Array.wrap(records).compact
if records.empty?
[]
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 735da152b7..ec6f0df484 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -82,11 +82,11 @@ module ActiveRecord
end
def association_key_type
- @klass.type_for_attribute(association_key_name.to_s).type
+ @klass.type_for_attribute(association_key_name).type
end
def owner_key_type
- @model.type_for_attribute(owner_key_name.to_s).type
+ @model.type_for_attribute(owner_key_name).type
end
def load_records(&block)
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index bce2a95ce1..5afb0bc068 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -4,9 +4,24 @@ module ActiveRecord
module Associations
# = Active Record Through Association
module ThroughAssociation #:nodoc:
- delegate :source_reflection, :through_reflection, to: :reflection
+ delegate :source_reflection, to: :reflection
private
+ def through_reflection
+ @through_reflection ||= begin
+ refl = reflection.through_reflection
+
+ while refl.through_reflection?
+ refl = refl.through_reflection
+ end
+
+ refl
+ end
+ end
+
+ def through_association
+ @through_association ||= owner.association(through_reflection.name)
+ end
# We merge in these scopes for two reasons:
#
@@ -38,24 +53,22 @@ module ActiveRecord
def construct_join_attributes(*records)
ensure_mutable
- if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
+ association_primary_key = source_reflection.association_primary_key(reflection.klass)
+
+ if association_primary_key == reflection.klass.primary_key && !options[:source_type]
join_attributes = { source_reflection.name => records }
else
join_attributes = {
- source_reflection.foreign_key =>
- records.map { |record|
- record.send(source_reflection.association_primary_key(reflection.klass))
- }
+ source_reflection.foreign_key => records.map(&association_primary_key.to_sym)
}
end
if options[:source_type]
- join_attributes[source_reflection.foreign_type] =
- records.map { |record| record.class.base_class.name }
+ join_attributes[source_reflection.foreign_type] = [ options[:source_type] ]
end
if records.count == 1
- Hash[join_attributes.map { |k, v| [k, v.first] }]
+ join_attributes.transform_values!(&:first)
else
join_attributes
end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 23d2aef214..c77790dc9a 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -167,6 +167,46 @@ module ActiveRecord
end
end
+ # Regexp whitelist. Matches the following:
+ # "#{table_name}.#{column_name}"
+ # "#{column_name}"
+ COLUMN_NAME_WHITELIST = /\A(?:\w+\.)?\w+\z/i
+
+ # Regexp whitelist. Matches the following:
+ # "#{table_name}.#{column_name}"
+ # "#{table_name}.#{column_name} #{direction}"
+ # "#{column_name}"
+ # "#{column_name} #{direction}"
+ COLUMN_NAME_ORDER_WHITELIST = /\A(?:\w+\.)?\w+(?:\s+asc|\s+desc)?\z/i
+
+ def enforce_raw_sql_whitelist(args, whitelist: COLUMN_NAME_WHITELIST) # :nodoc:
+ unexpected = args.reject do |arg|
+ arg.kind_of?(Arel::Node) ||
+ arg.is_a?(Arel::Nodes::SqlLiteral) ||
+ arg.is_a?(Arel::Attributes::Attribute) ||
+ arg.to_s.split(/\s*,\s*/).all? { |part| whitelist.match?(part) }
+ end
+
+ return if unexpected.none?
+
+ if allow_unsafe_raw_sql == :deprecated
+ ActiveSupport::Deprecation.warn(
+ "Dangerous query method (method whose arguments are used as raw " \
+ "SQL) called with non-attribute argument(s): " \
+ "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
+ "arguments will be disallowed in Rails 6.0. This method should " \
+ "not be called with user-provided values, such as request " \
+ "parameters or model attributes. Known-safe values can be passed " \
+ "by wrapping them in Arel.sql()."
+ )
+ else
+ raise(ActiveRecord::UnknownAttributeReference,
+ "Query method called with non-attribute argument(s): " +
+ unexpected.map(&:inspect).join(", ")
+ )
+ end
+ end
+
# Returns true if the given attribute exists, otherwise false.
#
# class Person < ActiveRecord::Base
@@ -416,7 +456,7 @@ module ActiveRecord
arel_table = self.class.arel_table
attribute_names.each do |name|
- attrs[arel_table[name]] = typecasted_attribute_value(name)
+ attrs[arel_table[name]] = _read_attribute(name)
end
attrs
end
@@ -443,9 +483,5 @@ module ActiveRecord
def pk_attribute?(name)
name == self.class.primary_key
end
-
- def typecasted_attribute_value(name)
- _read_attribute(name)
- end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 3de6fe566d..df4c79b0f6 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -32,9 +32,7 @@ module ActiveRecord
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
@mutations_before_last_save = nil
- @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
@mutations_from_database = nil
end
end
@@ -114,12 +112,12 @@ module ActiveRecord
# Alias for +changed+
def changed_attribute_names_to_save
- changes_to_save.keys
+ mutations_from_database.changed_attribute_names
end
# Alias for +changed_attributes+
def attributes_in_database
- changes_to_save.transform_values(&:first)
+ mutations_from_database.changed_values
end
private
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 0b7c9398a8..35150889d9 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -57,7 +57,7 @@ module ActiveRecord
# store_listing = StoreListing.new(price_in_cents: '10.1')
#
# # before
- # store_listing.price_in_cents # => BigDecimal.new(10.1)
+ # store_listing.price_in_cents # => BigDecimal(10.1)
#
# class StoreListing < ActiveRecord::Base
# attribute :price_in_cents, :integer
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 6974cf74f6..a1250c3835 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -436,6 +436,9 @@ module ActiveRecord
if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key)
unless reflection.through_reflection
record[reflection.foreign_key] = key
+ if inverse_reflection = reflection.inverse_of
+ record.association(inverse_reflection.name).loaded!
+ end
end
saved = record.save(validate: !autosave)
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 9ab2780760..9dbdf845bd 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -232,7 +232,7 @@ module ActiveRecord
#
# For example:
#
- # class Topic
+ # class Topic < ActiveRecord::Base
# has_many :children
#
# after_save :log_children
@@ -257,7 +257,7 @@ module ActiveRecord
#
# For example:
#
- # class Topic
+ # class Topic < ActiveRecord::Base
# has_many :children
#
# after_commit :log_children
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index 88b398ad45..dfba78614e 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -3,11 +3,11 @@
module ActiveRecord
module CollectionCacheKey
def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
- query_signature = Digest::MD5.hexdigest(collection.to_sql)
+ query_signature = ActiveSupport::Digest.hexdigest(collection.to_sql)
key = "#{collection.model_name.cache_key}/query-#{query_signature}"
- if collection.loaded?
- size = collection.size
+ if collection.loaded? || collection.distinct_value
+ size = collection.records.size
if size > 0
timestamp = collection.max_by(&timestamp_column)._read_attribute(timestamp_column)
end
@@ -15,13 +15,12 @@ module ActiveRecord
if collection.eager_loading?
collection = collection.send(:apply_join_dependency)
end
- column_type = type_for_attribute(timestamp_column.to_s)
+ column_type = type_for_attribute(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?
- query = collection.spawn
- query.select_values = [column]
+ query = collection.select(column)
subquery_alias = "subquery_for_cache_key"
subquery_column = "#{subquery_alias}.#{timestamp_column}"
subquery = query.arel.as(subquery_alias)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 0759f4d2b3..c730584902 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -63,15 +63,13 @@ module ActiveRecord
# There are several connection-pooling-related options that you can add to
# your database connection configuration:
#
- # * +pool+: number indicating size of connection pool (default 5)
- # * +checkout_timeout+: number of seconds to block and wait for a connection
- # before giving up and raising a timeout error (default 5 seconds).
- # * +reaping_frequency+: frequency in seconds to periodically run the
- # Reaper, which attempts to find and recover connections from dead
- # threads, which can occur if a programmer forgets to close a
- # connection at the end of a thread or a thread dies unexpectedly.
- # Regardless of this setting, the Reaper will be invoked before every
- # blocking wait. (Default +nil+, which means don't schedule the Reaper).
+ # * +pool+: maximum number of connections the pool may manage (default 5).
+ # * +idle_timeout+: number of seconds that a connection will be kept
+ # unused in the pool before it is automatically disconnected (default
+ # 300 seconds). Set this to zero to keep connections forever.
+ # * +checkout_timeout+: number of seconds to wait for a connection to
+ # become available before giving up and raising a timeout error (default
+ # 5 seconds).
#
#--
# Synchronization policy:
@@ -82,11 +80,8 @@ module ActiveRecord
# * private methods that require being called in a +synchronize+ blocks
# are now explicitly documented
class ConnectionPool
- # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
- # with which it shares a Monitor. But could be a generic Queue.
- #
- # The Queue in stdlib's 'thread' could replace this class except
- # stdlib's doesn't support waiting with a timeout.
+ # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
+ # with which it shares a Monitor.
class Queue
def initialize(lock = Monitor.new)
@lock = lock
@@ -175,7 +170,7 @@ module ActiveRecord
# Removes and returns the head of the queue if possible, or +nil+.
def remove
- @queue.shift
+ @queue.pop
end
# Remove and return the head the queue if the number of
@@ -283,12 +278,12 @@ module ActiveRecord
end
end
- # Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
- # A reaper instantiated with a +nil+ frequency will never reap the
- # connection pool.
+ # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
+ # +pool+. A reaper instantiated with a zero frequency will never reap
+ # the connection pool.
#
- # Configure the frequency by setting "reaping_frequency" in your
- # database yaml file.
+ # Configure the frequency by setting +reaping_frequency+ in your database
+ # yaml file (default 60 seconds).
class Reaper
attr_reader :pool, :frequency
@@ -298,11 +293,12 @@ module ActiveRecord
end
def run
- return unless frequency
+ return unless frequency && frequency > 0
Thread.new(frequency, pool) { |t, p|
loop do
sleep t
p.reap
+ p.flush
end
}
end
@@ -326,6 +322,10 @@ module ActiveRecord
@spec = spec
@checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
+ if @idle_timeout = spec.config.fetch(:idle_timeout, 300)
+ @idle_timeout = @idle_timeout.to_f
+ @idle_timeout = nil if @idle_timeout <= 0
+ end
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
@@ -356,7 +356,10 @@ module ActiveRecord
@lock_thread = false
- @reaper = Reaper.new(self, spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f)
+ # +reaping_frequency+ is configurable mostly for historical reasons, but it could
+ # also be useful if someone wants a very low +idle_timeout+.
+ reaping_frequency = spec.config.fetch(:reaping_frequency, 60)
+ @reaper = Reaper.new(self, reaping_frequency && reaping_frequency.to_f)
@reaper.run
end
@@ -450,6 +453,21 @@ module ActiveRecord
disconnect(false)
end
+ # Discards all connections in the pool (even if they're currently
+ # leased!), along with the pool itself. Any further interaction with the
+ # pool (except #spec and #schema_cache) is undefined.
+ #
+ # See AbstractAdapter#discard!
+ def discard! # :nodoc:
+ synchronize do
+ return if @connections.nil? # already discarded
+ @connections.each do |conn|
+ conn.discard!
+ end
+ @connections = @available = @thread_cached_conns = nil
+ end
+ end
+
# Clears the cache which maps classes and re-connects connections that
# require reloading.
#
@@ -575,6 +593,35 @@ module ActiveRecord
end
end
+ # Disconnect all connections that have been idle for at least
+ # +minimum_idle+ seconds. Connections currently checked out, or that were
+ # checked in less than +minimum_idle+ seconds ago, are unaffected.
+ def flush(minimum_idle = @idle_timeout)
+ return if minimum_idle.nil?
+
+ idle_connections = synchronize do
+ @connections.select do |conn|
+ !conn.in_use? && conn.seconds_idle >= minimum_idle
+ end.each do |conn|
+ conn.lease
+
+ @available.delete conn
+ @connections.delete conn
+ end
+ end
+
+ idle_connections.each do |conn|
+ conn.disconnect!
+ end
+ end
+
+ # Disconnect all currently idle connections. Connections currently checked
+ # out are unaffected.
+ def flush!
+ reap
+ flush(-1)
+ end
+
def num_waiting_in_queue # :nodoc:
@available.num_waiting
end
@@ -866,11 +913,31 @@ module ActiveRecord
# about the model. The model needs to pass a specification name to the handler,
# in order to look up the correct connection pool.
class ConnectionHandler
+ def self.unowned_pool_finalizer(pid_map) # :nodoc:
+ lambda do |_|
+ discard_unowned_pools(pid_map)
+ end
+ end
+
+ def self.discard_unowned_pools(pid_map) # :nodoc:
+ pid_map.each do |pid, pools|
+ pools.values.compact.each(&:discard!) unless pid == Process.pid
+ end
+ end
+
def initialize
# These caches are keyed by spec.name (ConnectionSpecification#name).
@owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h, k|
+ # Discard the parent's connection pools immediately; we have no need
+ # of them
+ ConnectionHandler.discard_unowned_pools(h)
+
h[k] = Concurrent::Map.new(initial_capacity: 2)
end
+
+ # Backup finalizer: if the forked child never needed a pool, the above
+ # early discard has not occurred
+ ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
end
def connection_pool_list
@@ -924,6 +991,13 @@ module ActiveRecord
connection_pool_list.each(&:disconnect!)
end
+ # Disconnects all currently idle connections.
+ #
+ # See ConnectionPool#flush! for details.
+ def flush_idle_connections!
+ connection_pool_list.each(&:flush!)
+ end
+
# Locate the connection of the nearest super class. This can be an
# active or defined connection: if it is the latter, it will be
# opened and set as the active connection for the class it was defined
@@ -931,9 +1005,7 @@ module ActiveRecord
def retrieve_connection(spec_name) #:nodoc:
pool = retrieve_connection_pool(spec_name)
raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool
- conn = pool.connection
- raise ConnectionNotEstablished, "No connection for '#{spec_name}' in connection pool" unless conn
- conn
+ pool.connection
end
# Returns true if a connection that's accessible to this class has
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 36048bee03..08f3e15a4b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -356,35 +356,33 @@ module ActiveRecord
# Inserts a set of fixtures into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
def insert_fixtures(fixtures, table_name)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `insert_fixtures` is deprecated and will be removed in the next version of Rails.
+ Consider using `insert_fixtures_set` for performance improvement.
+ MSG
return if fixtures.empty?
- columns = schema_cache.columns_hash(table_name)
+ execute(build_fixture_sql(fixtures, table_name), "Fixtures Insert")
+ end
- values = fixtures.map do |fixture|
- fixture = fixture.stringify_keys
+ def insert_fixtures_set(fixture_set, tables_to_delete = [])
+ fixture_inserts = fixture_set.map do |table_name, fixtures|
+ next if fixtures.empty?
- unknown_columns = fixture.keys - columns.keys
- if unknown_columns.any?
- raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.)
- end
+ build_fixture_sql(fixtures, table_name)
+ end.compact
- columns.map do |name, column|
- if fixture.key?(name)
- type = lookup_cast_type_from_column(column)
- bind = Relation::QueryAttribute.new(name, fixture[name], type)
- with_yaml_fallback(bind.value_for_database)
- else
- Arel.sql("DEFAULT")
+ table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}".dup }
+ total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts))
+
+ disable_referential_integrity do
+ transaction(requires_new: true) do
+ total_sql.each do |sql|
+ execute sql, "Fixtures Load"
+ yield if block_given?
end
end
end
-
- table = Arel::Table.new(table_name)
- manager = Arel::InsertManager.new
- manager.into(table)
- columns.each_key { |column| manager.columns << table[column] }
- manager.values = manager.create_values_list(values)
- execute manager.to_sql, "Fixtures Insert"
end
def empty_insert_statement_value
@@ -416,6 +414,44 @@ module ActiveRecord
alias join_to_delete join_to_update
private
+ def default_insert_value(column)
+ Arel.sql("DEFAULT")
+ end
+
+ def build_fixture_sql(fixtures, table_name)
+ columns = schema_cache.columns_hash(table_name)
+
+ values = fixtures.map do |fixture|
+ fixture = fixture.stringify_keys
+
+ unknown_columns = fixture.keys - columns.keys
+ if unknown_columns.any?
+ raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.)
+ end
+
+ columns.map do |name, column|
+ if fixture.key?(name)
+ type = lookup_cast_type_from_column(column)
+ bind = Relation::QueryAttribute.new(name, fixture[name], type)
+ with_yaml_fallback(bind.value_for_database)
+ else
+ default_insert_value(column)
+ end
+ end
+ end
+
+ table = Arel::Table.new(table_name)
+ manager = Arel::InsertManager.new
+ manager.into(table)
+ columns.each_key { |column| manager.columns << table[column] }
+ manager.values = manager.create_values_list(values)
+
+ manager.to_sql
+ end
+
+ def combine_multi_statements(total_sql)
+ total_sql.join(";\n")
+ end
# Returns a subquery for the given key using the join information.
def subquery_for(key, select)
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 bd05fb8f6e..4a191d337c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -95,6 +95,7 @@ module ActiveRecord
if options_sql = options[:options]
create_sql << " #{options_sql}"
end
+ create_sql
end
def column_options(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 be2f625d74..0594b4b485 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -6,7 +6,7 @@ module ActiveRecord
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
class IndexDefinition # :nodoc:
- attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment
+ attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :comment
def initialize(
table, name,
@@ -14,6 +14,7 @@ module ActiveRecord
columns = [],
lengths: {},
orders: {},
+ opclasses: {},
where: nil,
type: nil,
using: nil,
@@ -23,13 +24,23 @@ module ActiveRecord
@name = name
@unique = unique
@columns = columns
- @lengths = lengths
- @orders = orders
+ @lengths = concise_options(lengths)
+ @orders = concise_options(orders)
+ @opclasses = concise_options(opclasses)
@where = where
@type = type
@using = using
@comment = comment
end
+
+ private
+ def concise_options(options)
+ if columns.size == options.size && options.values.uniq.size == 1
+ options.values.first
+ else
+ options
+ end
+ end
end
# Abstract representation of a column definition. Instances of this type
@@ -85,6 +96,11 @@ module ActiveRecord
options[:primary_key] != default_primary_key
end
+ def validate?
+ options.fetch(:validate, true)
+ end
+ alias validated? validate?
+
def defined_for?(to_table_ord = nil, to_table: nil, **options)
if to_table_ord
self.to_table == to_table_ord.to_s
@@ -204,6 +220,7 @@ module ActiveRecord
:decimal,
:float,
:integer,
+ :json,
:string,
:text,
:time,
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 9b7345f7c3..db033db913 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -600,7 +600,7 @@ module ActiveRecord
# to provide these in a migration's +change+ method so it can be reverted.
# In that case, +type+ and +options+ will be used by #add_column.
def remove_column(table_name, column_name, type = nil, options = {})
- execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}"
end
# Changes the column's definition according to the new options.
@@ -715,7 +715,7 @@ module ActiveRecord
#
# CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
#
- # Note: MySQL doesn't yet support index order (it accepts the syntax but ignores it).
+ # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it).
#
# ====== Creating a partial index
#
@@ -738,6 +738,28 @@ module ActiveRecord
#
# Note: only supported by PostgreSQL and MySQL
#
+ # ====== Creating an index with a specific operator class
+ #
+ # add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
+ #
+ # generates:
+ #
+ # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
+ #
+ # add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
+ #
+ # generates:
+ #
+ # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
+ #
+ # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
+ #
+ # generates:
+ #
+ # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
+ #
+ # Note: only supported by PostgreSQL
+ #
# ====== Creating an index with a specific type
#
# add_index(:developers, :name, type: :fulltext)
@@ -942,6 +964,8 @@ module ActiveRecord
# Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
# [<tt>:on_update</tt>]
# Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
+ # [<tt>:validate</tt>]
+ # (Postgres only) Specify whether or not the constraint should be validated. Defaults to +true+.
def add_foreign_key(from_table, to_table, options = {})
return unless supports_foreign_keys?
@@ -1025,8 +1049,8 @@ module ActiveRecord
sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i)
- versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file|
- ActiveRecord::Migrator.parse_migration_filename(file).first.to_i
+ versions = migration_context.migration_files.map do |file|
+ migration_context.parse_migration_filename(file).first.to_i
end
unless migrated.include?(version)
@@ -1120,7 +1144,7 @@ module ActiveRecord
def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
column_names = index_column_names(column_name)
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass)
index_type = options[:type].to_s if options.key?(:type)
index_type ||= options[:unique] ? "UNIQUE" : ""
@@ -1173,20 +1197,22 @@ module ActiveRecord
end
def add_index_sort_order(quoted_columns, **options)
- if order = options[:order]
- case order
- when Hash
- order = order.symbolize_keys
- quoted_columns.each { |name, column| column << " #{order[name].upcase}" if order[name].present? }
- when String
- quoted_columns.each { |name, column| column << " #{order.upcase}" if order.present? }
- end
+ orders = options_for_index_columns(options[:order])
+ quoted_columns.each do |name, column|
+ column << " #{orders[name].upcase}" if orders[name].present?
end
+ end
- quoted_columns
+ def options_for_index_columns(options)
+ if options.is_a?(Hash)
+ options.symbolize_keys
+ else
+ Hash.new { |hash, column| hash[column] = options }
+ end
end
- # Overridden by the MySQL adapter for supporting index lengths
+ # Overridden by the MySQL adapter for supporting index lengths and by
+ # the PostgreSQL adapter for supporting operator classes.
def add_options_for_index_columns(quoted_columns, **options)
if supports_index_sort_order?
quoted_columns = add_index_sort_order(quoted_columns, options)
@@ -1340,6 +1366,20 @@ module ActiveRecord
options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
end
+ def add_column_for_alter(table_name, column_name, type, options = {})
+ td = create_table_definition(table_name)
+ cd = td.new_column_definition(column_name, type, options)
+ schema_creation.accept(AddColumnDefinition.new(cd))
+ end
+
+ def remove_column_for_alter(table_name, column_name, type = nil, options = {})
+ "DROP COLUMN #{quote_column_name(column_name)}"
+ end
+
+ def remove_columns_for_alter(table_name, *column_names)
+ column_names.map { |column_name| remove_column_for_alter(table_name, column_name) }
+ end
+
def insert_versions_sql(versions)
sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 345983a655..559f068c39 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -5,6 +5,7 @@ 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 "active_support/concurrency/load_interlock_aware_monitor"
require "arel/collectors/bind"
require "arel/collectors/composite"
require "arel/collectors/sql_string"
@@ -104,10 +105,11 @@ module ActiveRecord
@logger = logger
@config = config
@pool = nil
+ @idle_since = Concurrent.monotonic_time
@schema_cache = SchemaCache.new self
@quoted_column_names, @quoted_table_names = {}, {}
@visitor = arel_visitor
- @lock = Monitor.new
+ @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
@@ -117,6 +119,14 @@ module ActiveRecord
end
end
+ def migrations_paths # :nodoc:
+ @config[:migrations_paths] || Migrator.migrations_paths
+ end
+
+ def migration_context # :nodoc:
+ MigrationContext.new(migrations_paths)
+ end
+
class Version
include Comparable
@@ -163,6 +173,7 @@ module ActiveRecord
"Current thread: #{Thread.current}."
end
+ @idle_since = Concurrent.monotonic_time
@owner = nil
else
raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
@@ -182,6 +193,12 @@ module ActiveRecord
end
end
+ # Seconds since this connection was returned to the pool
+ def seconds_idle # :nodoc:
+ return 0 if in_use?
+ Concurrent.monotonic_time - @idle_since
+ end
+
def unprepared_statement
old_prepared_statements, @prepared_statements = @prepared_statements, false
yield
@@ -263,6 +280,11 @@ module ActiveRecord
false
end
+ # Does this adapter support creating invalid constraints?
+ def supports_validate_constraints?
+ false
+ end
+
# Does this adapter support creating foreign key constraints
# in the same statement as creating the table?
def supports_foreign_keys_in_create?
@@ -304,6 +326,11 @@ module ActiveRecord
false
end
+ # Does this adapter support foreign/external tables?
+ def supports_foreign_tables?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -366,6 +393,19 @@ module ActiveRecord
reset_transaction
end
+ # Immediately forget this connection ever existed. Unlike disconnect!,
+ # this will not communicate with the server.
+ #
+ # After calling this method, the behavior of all other methods becomes
+ # undefined. This is called internally just before a forked process gets
+ # rid of a connection that belonged to its parent.
+ def discard!
+ # This should be overridden by concrete adapters.
+ #
+ # Prevent @connection's finalizer from touching the socket, or
+ # otherwise communicating with its server, when it is collected.
+ end
+
# Reset the state of this connection, directing the DBMS to clear
# transactions and other connection-related server-side state. Usually a
# database-dependent operation.
@@ -510,12 +550,7 @@ module ActiveRecord
end
def extract_limit(sql_type)
- case sql_type
- when /^bigint/i
- 8
- when /\((.*)\)/
- $1.to_i
- end
+ $1.to_i if sql_type =~ /\((.*)\)/
end
def translate_exception_class(e, sql)
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 bfec6fb784..1fb0bd8d56 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -44,7 +44,7 @@ module ActiveRecord
json: { name: "json" },
}
- class StatementPool < ConnectionAdapters::StatementPool
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
private def dealloc(stmt)
stmt[:stmt].close
end
@@ -117,11 +117,11 @@ module ActiveRecord
end
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
- query_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1
+ query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
end
def release_advisory_lock(lock_name) # :nodoc:
- query_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1
+ query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
end
def native_database_types
@@ -249,7 +249,7 @@ module ActiveRecord
# create_database 'matt_development', charset: :big5
def create_database(name, options = {})
if options[:collation]
- execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
else
execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
end
@@ -292,14 +292,10 @@ module ActiveRecord
SQL
end
- def create_table(table_name, **options) #:nodoc:
- super(table_name, options: "ENGINE=InnoDB", **options)
- end
-
def bulk_change_table(table_name, operations) #:nodoc:
sqls = operations.flat_map do |command, args|
table, arguments = args.shift, args
- method = :"#{command}_sql"
+ method = :"#{command}_for_alter"
if respond_to?(method, true)
send(method, table, *arguments)
@@ -372,11 +368,11 @@ module ActiveRecord
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}")
end
def rename_column(table_name, column_name, new_column_name) #:nodoc:
- execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}")
rename_column_indexes(table_name, column_name, new_column_name)
end
@@ -403,12 +399,13 @@ module ActiveRecord
fk.constraint_name AS 'name',
rc.update_rule AS 'on_update',
rc.delete_rule AS 'on_delete'
- FROM information_schema.key_column_usage fk
- JOIN information_schema.referential_constraints rc
+ FROM information_schema.referential_constraints rc
+ JOIN information_schema.key_column_usage fk
USING (constraint_schema, constraint_name)
WHERE fk.referenced_column_name IS NOT NULL
AND fk.table_schema = #{scope[:schema]}
AND fk.table_name = #{scope[:name]}
+ AND rc.constraint_schema = #{scope[:schema]}
AND rc.table_name = #{scope[:name]}
SQL
@@ -529,23 +526,49 @@ module ActiveRecord
index.using == :btree || super
end
- def insert_fixtures(*)
- without_sql_mode("NO_AUTO_VALUE_ON_ZERO") { super }
+ def insert_fixtures_set(fixture_set, tables_to_delete = [])
+ with_multi_statements do
+ super { discard_remaining_results }
+ end
end
private
+ def combine_multi_statements(total_sql)
+ total_sql.each_with_object([]) do |sql, total_sql_chunks|
+ previous_packet = total_sql_chunks.last
+ sql << ";\n"
+ if max_allowed_packet_reached?(sql, previous_packet) || total_sql_chunks.empty?
+ total_sql_chunks << sql
+ else
+ previous_packet << sql
+ end
+ end
+ end
- def without_sql_mode(mode)
- result = execute("SELECT @@SESSION.sql_mode")
- current_mode = result.first[0]
- return yield unless current_mode.include?(mode)
+ def max_allowed_packet_reached?(current_packet, previous_packet)
+ if current_packet.bytesize > max_allowed_packet
+ raise ActiveRecordError, "Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable."
+ elsif previous_packet.nil?
+ false
+ else
+ (current_packet.bytesize + previous_packet.bytesize) > max_allowed_packet
+ end
+ end
+
+ def max_allowed_packet
+ bytes_margin = 2
+ @max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin)
+ end
+
+ def with_multi_statements
+ previous_flags = @config[:flags]
+ @config[:flags] = Mysql2::Client::MULTI_STATEMENTS
+ reconnect!
- sql_mode = "REPLACE(@@sql_mode, '#{mode}', '')"
- execute("SET @@SESSION.sql_mode = #{sql_mode}")
yield
ensure
- sql_mode = "CONCAT(@@sql_mode, ',#{mode}')"
- execute("SET @@SESSION.sql_mode = #{sql_mode}")
+ @config[:flags] = previous_flags
+ reconnect!
end
def initialize_type_map(m = type_map)
@@ -605,25 +628,6 @@ module ActiveRecord
end
end
- def add_index_length(quoted_columns, **options)
- if length = options[:length]
- case length
- when Hash
- length = length.symbolize_keys
- quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? }
- when Integer
- quoted_columns.each { |name, column| column << "(#{length})" }
- end
- end
-
- quoted_columns
- end
-
- def add_options_for_index_columns(quoted_columns, **options)
- quoted_columns = add_index_length(quoted_columns, options)
- super
- end
-
# See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
ER_DUP_ENTRY = 1062
ER_NOT_NULL_VIOLATION = 1048
@@ -635,6 +639,8 @@ module ActiveRecord
ER_CANNOT_ADD_FOREIGN = 1215
ER_CANNOT_CREATE_TABLE = 1005
ER_LOCK_WAIT_TIMEOUT = 1205
+ ER_QUERY_INTERRUPTED = 1317
+ ER_QUERY_TIMEOUT = 3024
def translate_exception(exception, message)
case error_number(exception)
@@ -659,19 +665,17 @@ module ActiveRecord
when ER_LOCK_DEADLOCK
Deadlocked.new(message)
when ER_LOCK_WAIT_TIMEOUT
- TransactionTimeout.new(message)
+ LockWaitTimeout.new(message)
+ when ER_QUERY_TIMEOUT
+ StatementTimeout.new(message)
+ when ER_QUERY_INTERRUPTED
+ QueryCanceled.new(message)
else
super
end
end
- def add_column_sql(table_name, column_name, type, options = {})
- td = create_table_definition(table_name)
- cd = td.new_column_definition(column_name, type, options)
- schema_creation.accept(AddColumnDefinition.new(cd))
- end
-
- def change_column_sql(table_name, column_name, type, options = {})
+ def change_column_for_alter(table_name, column_name, type, options = {})
column = column_for(table_name, column_name)
type ||= column.sql_type
@@ -692,7 +696,7 @@ module ActiveRecord
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
end
- def rename_column_sql(table_name, column_name, new_column_name)
+ def rename_column_for_alter(table_name, column_name, new_column_name)
column = column_for(table_name, column_name)
options = {
default: column.default,
@@ -706,31 +710,23 @@ module ActiveRecord
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
end
- def remove_column_sql(table_name, column_name, type = nil, options = {})
- "DROP #{quote_column_name(column_name)}"
- end
-
- def remove_columns_sql(table_name, *column_names)
- column_names.map { |column_name| remove_column_sql(table_name, column_name) }
- end
-
- def add_index_sql(table_name, column_name, options = {})
+ def add_index_for_alter(table_name, column_name, options = {})
index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
index_algorithm[0, 0] = ", " if index_algorithm.present?
"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
end
- def remove_index_sql(table_name, options = {})
+ def remove_index_for_alter(table_name, options = {})
index_name = index_name_for_remove(table_name, options)
"DROP INDEX #{quote_column_name(index_name)}"
end
- def add_timestamps_sql(table_name, options = {})
- [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
+ def add_timestamps_for_alter(table_name, options = {})
+ [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
end
- def remove_timestamps_sql(table_name, options = {})
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
+ def remove_timestamps_for_alter(table_name, options = {})
+ [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
end
# MySQL is too stupid to create a temporary table for use subquery, so we have
@@ -743,7 +739,8 @@ module ActiveRecord
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
subselect.distinct unless select.limit || select.offset || select.orders.any?
- Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key.name))
+ key_name = quote_column_name(key.name)
+ Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
end
def supports_rename_index?
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
index a058a72872..458c9bfd70 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -11,7 +11,7 @@ module ActiveRecord
else
super
end
- @connection.next_result while @connection.more_results?
+ discard_remaining_results
result
end
@@ -50,11 +50,18 @@ module ActiveRecord
alias :exec_update :exec_delete
private
+ def default_insert_value(column)
+ Arel.sql("DEFAULT") unless column.auto_increment?
+ end
def last_inserted_id(result)
@connection.last_id
end
+ def discard_remaining_results
+ @connection.abandon_results!
+ end
+
def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
# made since we established the 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 da25e4863c..2ed4ad16ae 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -32,10 +32,6 @@ module ActiveRecord
args.each { |name| column(name, :longtext, options) }
end
- def json(*args, **options)
- args.each { |name| column(name, :json, options) }
- end
-
def unsigned_integer(*args, **options)
args.each { |name| column(name, :unsigned_integer, options) }
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 a15c7d1787..ce50590651 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -22,23 +22,26 @@ module ActiveRecord
index_using = mysql_index_type
end
- indexes << IndexDefinition.new(
+ indexes << [
row[:Table],
row[:Key_name],
row[:Non_unique].to_i == 0,
+ [],
+ lengths: {},
+ orders: {},
type: index_type,
using: index_using,
comment: row[:Index_comment].presence
- )
+ ]
end
- indexes.last.columns << row[:Column_name]
- indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
- indexes.last.orders.merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
+ indexes.last[-2] << row[:Column_name]
+ indexes.last[-1][:lengths].merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part]
+ indexes.last[-1][:orders].merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
end
end
- indexes
+ indexes.map { |index| IndexDefinition.new(*index) }
end
def remove_column(table_name, column_name, type = nil, options = {})
@@ -103,6 +106,18 @@ module ActiveRecord
super unless specifier == "RESTRICT"
end
+ def add_index_length(quoted_columns, **options)
+ lengths = options_for_index_columns(options[:length])
+ quoted_columns.each do |name, column|
+ column << "(#{lengths[name]})" if lengths[name].present?
+ end
+ end
+
+ def add_options_for_index_columns(quoted_columns, **options)
+ quoted_columns = add_index_length(quoted_columns, options)
+ super
+ end
+
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 8de582fee1..bfdc7995f0 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -3,9 +3,8 @@
require "active_record/connection_adapters/abstract_mysql_adapter"
require "active_record/connection_adapters/mysql/database_statements"
-gem "mysql2", ">= 0.3.18", "< 0.5"
+gem "mysql2", "~> 0.4.4"
require "mysql2"
-raise "mysql2 0.4.3 is not supported. Please upgrade to 0.4.4+" if Mysql2::VERSION == "0.4.3"
module ActiveRecord
module ConnectionHandling # :nodoc:
@@ -105,6 +104,11 @@ module ActiveRecord
@connection.close
end
+ def discard! # :nodoc:
+ @connection.automatic_close = false
+ @connection = nil
+ end
+
private
def connect
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb
index 879dba7afd..e7d33855c4 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/decimal.rb
@@ -6,7 +6,7 @@ module ActiveRecord
module OID # :nodoc:
class Decimal < Type::Decimal # :nodoc:
def infinity(options = {})
- BigDecimal.new("Infinity") * (options[:negative] ? -1 : 1)
+ BigDecimal("Infinity") * (options[:negative] ? -1 : 1)
end
end
end
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 a89aa5ea09..6edb7cfd3c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -60,7 +60,7 @@ module ActiveRecord
end
def type_cast_single_for_database(value)
- infinity?(value) ? "" : @subtype.serialize(value)
+ infinity?(value) ? value : @subtype.serialize(value)
end
def extract_bounds(value)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 9fdeab06c1..e75202b0be 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -138,7 +138,7 @@ module ActiveRecord
end
def encode_range(range)
- "[#{type_cast(range.first)},#{type_cast(range.last)}#{range.exclude_end? ? ')' : ']'}"
+ "[#{type_cast_range_value(range.first)},#{type_cast_range_value(range.last)}#{range.exclude_end? ? ')' : ']'}"
end
def determine_encoding_of_strings_in_array(value)
@@ -154,6 +154,14 @@ module ActiveRecord
else _type_cast(values)
end
end
+
+ def type_cast_range_value(value)
+ infinity?(value) ? "" : type_cast(value)
+ end
+
+ def infinity?(value)
+ value.respond_to?(:infinite?) && value.infinite?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
index 59f661da25..8e381a92cf 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
@@ -5,6 +5,18 @@ module ActiveRecord
module PostgreSQL
class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
private
+ def visit_AlterTable(o)
+ super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
+ end
+
+ def visit_AddForeignKey(o)
+ super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
+ end
+
+ def visit_ValidateConstraint(name)
+ "VALIDATE CONSTRAINT #{quote_column_name(name)}"
+ end
+
def add_column_options!(sql, options)
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
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 75622eb304..6047217fcd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -95,10 +95,6 @@ module ActiveRecord
args.each { |name| column(name, :int8range, options) }
end
- def json(*args, **options)
- args.each { |name| column(name, :json, options) }
- end
-
def jsonb(*args, **options)
args.each { |name| column(name, :jsonb, options) }
end
@@ -192,6 +188,19 @@ module ActiveRecord
class Table < ActiveRecord::ConnectionAdapters::Table
include ColumnMethods
end
+
+ class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
+ attr_reader :constraint_validations
+
+ def initialize(td)
+ super
+ @constraint_validations = []
+ end
+
+ def validate_constraint(name)
+ @constraint_validations << name
+ end
+ end
end
end
end
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 846e721983..7999434ad8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -38,7 +38,7 @@ module ActiveRecord
" TABLESPACE = \"#{value}\""
when :connection_limit
" CONNECTION LIMIT = #{value}"
- else
+ else
""
end
end
@@ -87,10 +87,7 @@ module ActiveRecord
result = query(<<-SQL, "SCHEMA")
SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
- pg_catalog.obj_description(i.oid, 'pg_class') AS comment,
- (SELECT COUNT(*) FROM pg_opclass o
- JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c
- ON o.oid = c.oid WHERE o.opcdefault = 'f')
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -109,11 +106,13 @@ module ActiveRecord
inddef = row[3]
oid = row[4]
comment = row[5]
- opclass = row[6]
using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten
- if indkey.include?(0) || opclass > 0
+ orders = {}
+ opclasses = {}
+
+ if indkey.include?(0)
columns = expressions
else
columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact
@@ -123,10 +122,16 @@ module ActiveRecord
AND a.attnum IN (#{indkey.join(",")})
SQL
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
- orders = Hash[
- expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] }
- ]
+ # add info on sort order (only desc order is explicitly specified, asc is the default)
+ # and non-default opclasses
+ expressions.scan(/(?<column>\w+)\s?(?<opclass>\w+_ops)?\s?(?<desc>DESC)?\s?(?<nulls>NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls|
+ opclasses[column] = opclass.to_sym if opclass
+ if nulls
+ orders[column] = [desc, nulls].compact.join(" ")
+ else
+ orders[column] = :desc if desc
+ end
+ end
end
IndexDefinition.new(
@@ -135,6 +140,7 @@ module ActiveRecord
unique,
columns,
orders: orders,
+ opclasses: opclasses,
where: where,
using: using.to_sym,
comment: comment.presence
@@ -362,6 +368,31 @@ module ActiveRecord
SQL
end
+ def bulk_change_table(table_name, operations)
+ sql_fragments = []
+ non_combinable_operations = []
+
+ operations.each do |command, args|
+ table, arguments = args.shift, args
+ method = :"#{command}_for_alter"
+
+ if respond_to?(method, true)
+ sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) }
+ sql_fragments << sqls
+ non_combinable_operations.concat(procs)
+ else
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
+ non_combinable_operations.each(&:call)
+ sql_fragments = []
+ non_combinable_operations = []
+ send(command, table, *arguments)
+ end
+ end
+
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty?
+ non_combinable_operations.each(&:call)
+ end
+
# Renames a table.
# Also renames a table's primary key sequence if the sequence name exists and
# matches the Active Record default.
@@ -392,50 +423,23 @@ module ActiveRecord
def change_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
- quoted_table_name = quote_table_name(table_name)
- quoted_column_name = quote_column_name(column_name)
- sql_type = type_to_sql(type, options)
- sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
- if options[:collation]
- sql << " COLLATE \"#{options[:collation]}\""
- end
- if options[:using]
- sql << " USING #{options[:using]}"
- elsif options[:cast_as]
- cast_as_type = type_to_sql(options[:cast_as], options)
- sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
- end
- execute sql
-
- change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
- change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
+ sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) }
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}"
+ procs.each(&:call)
end
# Changes the default value of a table column.
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
- clear_cache!
- column = column_for(table_name, column_name)
- return unless column
-
- default = extract_new_default_value(default_or_changes)
- alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s"
- if default.nil?
- # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
- # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
- execute alter_column_query % "DROP DEFAULT"
- else
- execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
- end
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}"
end
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
clear_cache!
unless null || default.nil?
column = column_for(table_name, column_name)
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
+ execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column
end
- execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}"
end
# Adds comment for given table column or drops it if +comment+ is a +nil+
@@ -458,8 +462,8 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
- execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do
+ index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do
execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
end
end
@@ -499,7 +503,7 @@ module ActiveRecord
def foreign_keys(table_name)
scope = quoted_scope(table_name)
fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
- SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid
FROM pg_constraint c
JOIN pg_class t1 ON c.conrelid = t1.oid
JOIN pg_class t2 ON c.confrelid = t2.oid
@@ -521,11 +525,20 @@ module ActiveRecord
options[:on_delete] = extract_foreign_key_action(row["on_delete"])
options[:on_update] = extract_foreign_key_action(row["on_update"])
+ options[:validate] = row["valid"]
ForeignKeyDefinition.new(table_name, row["to_table"], options)
end
end
+ def foreign_tables
+ query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA")
+ end
+
+ def foreign_table_exists?(table_name)
+ query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present?
+ end
+
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
sql = \
@@ -581,6 +594,43 @@ module ActiveRecord
PostgreSQL::SchemaDumper.create(self, options)
end
+ # Validates the given constraint.
+ #
+ # Validates the constraint named +constraint_name+ on +accounts+.
+ #
+ # validate_constraint :accounts, :constraint_name
+ def validate_constraint(table_name, constraint_name)
+ return unless supports_validate_constraints?
+
+ at = create_alter_table table_name
+ at.validate_constraint constraint_name
+
+ execute schema_creation.accept(at)
+ end
+
+ # Validates the given foreign key.
+ #
+ # Validates the foreign key on +accounts.branch_id+.
+ #
+ # validate_foreign_key :accounts, :branches
+ #
+ # Validates the foreign key on +accounts.owner_id+.
+ #
+ # validate_foreign_key :accounts, column: :owner_id
+ #
+ # Validates the foreign key named +special_fk_name+ on the +accounts+ table.
+ #
+ # validate_foreign_key :accounts, name: :special_fk_name
+ #
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
+ def validate_foreign_key(from_table, options_or_to_table = {})
+ return unless supports_validate_constraints?
+
+ fk_name_to_validate = foreign_key_for!(from_table, options_or_to_table).name
+
+ validate_constraint from_table, fk_name_to_validate
+ end
+
private
def schema_creation
PostgreSQL::SchemaCreation.new(self)
@@ -590,6 +640,10 @@ module ActiveRecord
PostgreSQL::TableDefinition.new(*args)
end
+ def create_alter_table(name)
+ PostgreSQL::AlterTable.new create_table_definition(name)
+ end
+
def new_column_from_field(table_name, field)
column_name, type, default, notnull, oid, fmod, collation, comment = field
type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
@@ -629,9 +683,75 @@ module ActiveRecord
end
end
+ def change_column_sql(table_name, column_name, type, options = {})
+ quoted_column_name = quote_column_name(column_name)
+ sql_type = type_to_sql(type, options)
+ sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
+ if options[:using]
+ sql << " USING #{options[:using]}"
+ elsif options[:cast_as]
+ cast_as_type = type_to_sql(options[:cast_as], options)
+ sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
+ end
+
+ sql
+ end
+
+ def change_column_for_alter(table_name, column_name, type, options = {})
+ sqls = [change_column_sql(table_name, column_name, type, options)]
+ sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default)
+ sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+ sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment)
+ sqls
+ end
+
+
+ # Changes the default value of a table column.
+ def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc:
+ column = column_for(table_name, column_name)
+ return unless column
+
+ default = extract_new_default_value(default_or_changes)
+ alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s"
+ if default.nil?
+ # <tt>DEFAULT NULL</tt> results in the same behavior as <tt>DROP DEFAULT</tt>. However, PostgreSQL will
+ # cast the default to the columns type, which leaves us with a default like "default NULL::character varying".
+ alter_column_query % "DROP DEFAULT"
+ else
+ alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}"
+ end
+ end
+
+ def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc:
+ "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL"
+ end
+
+ def add_timestamps_for_alter(table_name, options = {})
+ [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)]
+ end
+
+ def remove_timestamps_for_alter(table_name, options = {})
+ [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
+ end
+
+ def add_index_opclass(quoted_columns, **options)
+ opclasses = options_for_index_columns(options[:opclass])
+ quoted_columns.each do |name, column|
+ column << " #{opclasses[name]}" if opclasses[name].present?
+ end
+ end
+
+ def add_options_for_index_columns(quoted_columns, **options)
+ quoted_columns = add_index_opclass(quoted_columns, options)
+ super
+ end
+
def data_source_sql(name = nil, type: nil)
scope = quoted_scope(name, type: type)
- scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view
+ scope[:type] ||= "'r','v','m','f'" # (r)elation/table, (v)iew, (m)aterialized view, (f)oreign table
sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup
sql << " WHERE n.nspname = #{scope[:schema]}"
@@ -648,6 +768,8 @@ module ActiveRecord
"'r'"
when "VIEW"
"'v','m'"
+ when "FOREIGN TABLE"
+ "'f'"
end
scope = {}
scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 46863c41ab..dc6287e32c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility
-gem "pg", "~> 0.18"
+gem "pg", ">= 0.18", "< 2.0"
require "pg"
require "active_record/connection_adapters/abstract_adapter"
@@ -122,6 +122,10 @@ module ActiveRecord
include PostgreSQL::SchemaStatements
include PostgreSQL::DatabaseStatements
+ def supports_bulk_alter?
+ true
+ end
+
def supports_index_sort_order?
true
end
@@ -142,6 +146,10 @@ module ActiveRecord
true
end
+ def supports_validate_constraints?
+ true
+ end
+
def supports_views?
true
end
@@ -166,7 +174,7 @@ module ActiveRecord
{ concurrently: "CONCURRENTLY" }
end
- class StatementPool < ConnectionAdapters::StatementPool
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
def initialize(connection, max)
super(max)
@connection = connection
@@ -182,7 +190,6 @@ module ActiveRecord
end
private
-
def dealloc(key)
@connection.query "DEALLOCATE #{key}" if connection_active?
rescue PG::Error
@@ -273,6 +280,11 @@ module ActiveRecord
end
end
+ def discard! # :nodoc:
+ @connection.socket_io.reopen(IO::NULL) rescue nil
+ @connection = nil
+ end
+
def native_database_types #:nodoc:
NATIVE_DATABASE_TYPES
end
@@ -306,6 +318,10 @@ module ActiveRecord
postgresql_version >= 90300
end
+ def supports_foreign_tables?
+ postgresql_version >= 90300
+ end
+
def supports_pgcrypto_uuid?
postgresql_version >= 90400
end
@@ -382,7 +398,6 @@ module ActiveRecord
end
private
-
# See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
VALUE_LIMIT_VIOLATION = "22001"
NUMERIC_VALUE_OUT_OF_RANGE = "22003"
@@ -392,6 +407,7 @@ module ActiveRecord
SERIALIZATION_FAILURE = "40001"
DEADLOCK_DETECTED = "40P01"
LOCK_NOT_AVAILABLE = "55P03"
+ QUERY_CANCELED = "57014"
def translate_exception(exception, message)
return exception unless exception.respond_to?(:result)
@@ -412,7 +428,9 @@ module ActiveRecord
when DEADLOCK_DETECTED
Deadlocked.new(message)
when LOCK_NOT_AVAILABLE
- TransactionTimeout.new(message)
+ LockWaitTimeout.new(message)
+ when QUERY_CANCELED
+ QueryCanceled.new(message)
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index f34b6733da..c29cf1f9a1 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -28,7 +28,7 @@ module ActiveRecord
coder["columns_hash"] = @columns_hash
coder["primary_keys"] = @primary_keys
coder["data_sources"] = @data_sources
- coder["version"] = ActiveRecord::Migrator.current_version
+ coder["version"] = connection.migration_context.current_version
end
def init_with(coder)
@@ -100,7 +100,7 @@ module ActiveRecord
def marshal_dump
# if we get current version during initialization, it happens stack over flow.
- @version = ActiveRecord::Migrator.current_version
+ @version = connection.migration_context.current_version
[@version, @columns, @columns_hash, @primary_keys, @data_sources]
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 670afa3684..a958600446 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -60,7 +60,7 @@ module ActiveRecord
include SQLite3::SchemaStatements
NATIVE_DATABASE_TYPES = {
- primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL",
+ primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
string: { name: "varchar" },
text: { name: "text" },
integer: { name: "integer" },
@@ -70,7 +70,8 @@ module ActiveRecord
time: { name: "time" },
date: { name: "date" },
binary: { name: "blob" },
- boolean: { name: "boolean" }
+ boolean: { name: "boolean" },
+ json: { name: "json" },
}
##
@@ -90,9 +91,8 @@ module ActiveRecord
# Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
class_attribute :represent_boolean_as_integer, default: false
- class StatementPool < ConnectionAdapters::StatementPool
+ class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
private
-
def dealloc(stmt)
stmt[:stmt].close unless stmt[:stmt].closed?
end
@@ -101,7 +101,7 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
- @active = nil
+ @active = true
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
configure_connection
@@ -135,12 +135,16 @@ module ActiveRecord
true
end
+ def supports_json?
+ true
+ end
+
def supports_multi_insert?
sqlite_version >= "3.7.11"
end
def active?
- @active != false
+ @active
end
# Disconnects from the database if already connected. Otherwise, this
@@ -286,19 +290,18 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
- # See: https://www.sqlite.org/lang_altertable.html
- # SQLite has an additional restriction on the ALTER TABLE statement
- def valid_alter_table_type?(type)
- type.to_sym != :primary_key
+ def valid_alter_table_type?(type, options = {})
+ !invalid_alter_table_type?(type, options)
end
+ deprecate :valid_alter_table_type?
def add_column(table_name, column_name, type, options = {}) #:nodoc:
- if valid_alter_table_type?(type)
- super(table_name, column_name, type, options)
- else
+ if invalid_alter_table_type?(type, options)
alter_table(table_name) do |definition|
definition.column(column_name, type, options)
end
+ else
+ super
end
end
@@ -364,12 +367,30 @@ module ActiveRecord
end
def insert_fixtures(rows, table_name)
- rows.each do |row|
- insert_fixture(row, table_name)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `insert_fixtures` is deprecated and will be removed in the next version of Rails.
+ Consider using `insert_fixtures_set` for performance improvement.
+ MSG
+ insert_fixtures_set(table_name => rows)
+ end
+
+ def insert_fixtures_set(fixture_set, tables_to_delete = [])
+ disable_referential_integrity do
+ transaction(requires_new: true) do
+ tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
+
+ fixture_set.each do |table_name, rows|
+ rows.each { |row| insert_fixture(row, table_name) }
+ end
+ end
end
end
private
+ def initialize_type_map(m = type_map)
+ super
+ register_class_with_limit m, %r(int)i, SQLite3Integer
+ end
def table_structure(table_name)
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
@@ -378,6 +399,12 @@ module ActiveRecord
end
alias column_definitions table_structure
+ # See: https://www.sqlite.org/lang_altertable.html
+ # SQLite has an additional restriction on the ALTER TABLE statement
+ def invalid_alter_table_type?(type, options)
+ type.to_sym == :primary_key || options[:primary_key]
+ end
+
def alter_table(table_name, options = {})
altered_table_name = "a#{table_name}"
caller = lambda { |definition| yield definition if block_given? }
@@ -399,18 +426,21 @@ module ActiveRecord
options[:id] = false
create_table(to, options) do |definition|
@definition = definition
- @definition.primary_key(from_primary_key) if from_primary_key.present?
+ if from_primary_key.is_a?(Array)
+ @definition.primary_keys from_primary_key
+ end
columns(from).each do |column|
column_name = options[:rename] ?
(options[:rename][column.name] ||
options[:rename][column.name.to_sym] ||
column.name) : column.name
- next if column_name == from_primary_key
@definition.column(column_name, column.type,
limit: column.limit, default: column.default,
precision: column.precision, scale: column.scale,
- null: column.null, collation: column.collation)
+ null: column.null, collation: column.collation,
+ primary_key: column_name == from_primary_key
+ )
end
yield @definition if block_given?
end
@@ -423,6 +453,9 @@ module ActiveRecord
def copy_table_indexes(from, to, rename = {})
indexes(from).each do |index|
name = index.name
+ # indexes sqlite creates for internal use start with `sqlite_` and
+ # don't need to be copied
+ next if name.starts_with?("sqlite_")
if to == "a#{from}"
name = "t#{name}"
elsif from == "a#{to}"
@@ -438,6 +471,7 @@ module ActiveRecord
# index name can't be the same
opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
opts[:unique] = true if index.unique
+ opts[:where] = index.where if index.where
add_index(to, columns, opts)
end
end
@@ -525,6 +559,17 @@ module ActiveRecord
def configure_connection
execute("PRAGMA foreign_keys = ON", "SCHEMA")
end
+
+ class SQLite3Integer < Type::Integer # :nodoc:
+ private
+ def _limit
+ # INTEGER storage class can be stored 8 bytes value.
+ # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
+ limit || 8
+ end
+ end
+
+ ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
end
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 9a47edfba4..88d28dc52a 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -140,6 +140,6 @@ module ActiveRecord
end
delegate :clear_active_connections!, :clear_reloadable_connections!,
- :clear_all_connections!, to: :connection_handler
+ :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 0f7a503c90..e1a0b2ecf8 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -18,6 +18,13 @@ module ActiveRecord
mattr_accessor :logger, instance_writer: false
##
+ # :singleton-method:
+ #
+ # Specifies if the methods calling database queries should be logged below
+ # their relevant queries. Defaults to false.
+ mattr_accessor :verbose_query_logs, instance_writer: false, default: false
+
+ ##
# Contains the database configuration - as is typically stored in config/database.yml -
# as a Hash.
#
@@ -76,6 +83,14 @@ module ActiveRecord
# scope being ignored is error-worthy, rather than a warning.
mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
+ # :singleton-method:
+ # Specify the behavior for unsafe raw query methods. Values are as follows
+ # deprecated - Warnings are logged when unsafe raw SQL is passed to
+ # query methods.
+ # disabled - Unsafe raw SQL passed to query methods results in
+ # UnknownAttributeReference exception.
+ mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated
+
##
# :singleton-method:
# Specify whether or not to use timestamps for migration versions
@@ -266,10 +281,11 @@ module ActiveRecord
end
def relation
- relation = Relation.create(self, arel_table, predicate_builder)
+ relation = Relation.create(self)
if finder_needs_type_condition? && !ignore_default_scope?
- relation.where(type_condition).create_with(inheritance_column.to_s => sti_name)
+ relation.where!(type_condition)
+ relation.create_with!(inheritance_column.to_s => sti_name)
else
relation
end
@@ -366,8 +382,10 @@ module ActiveRecord
_run_initialize_callbacks
- @new_record = true
- @destroyed = false
+ @new_record = true
+ @destroyed = false
+ @_start_transaction_state = {}
+ @transaction_state = nil
super
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index f373b98035..1a3e6e4d09 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -221,6 +221,8 @@ module ActiveRecord
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
if klass_method && dangerous_class_method?(method_name)
raise_conflict_error(enum_name, method_name, type: "class")
+ elsif klass_method && method_defined_within?(method_name, Relation)
+ raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
elsif !klass_method && dangerous_attribute_method?(method_name)
raise_conflict_error(enum_name, method_name)
elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 9ef3316393..efcbd44776 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -335,8 +335,40 @@ module ActiveRecord
class IrreversibleOrderError < ActiveRecordError
end
- # TransactionTimeout will be raised when lock wait timeout expires.
- # Wait time value is set by innodb_lock_wait_timeout.
- class TransactionTimeout < StatementInvalid
+ # LockWaitTimeout will be raised when lock wait timeout exceeded.
+ class LockWaitTimeout < StatementInvalid
+ end
+
+ # StatementTimeout will be raised when statement timeout exceeded.
+ class StatementTimeout < StatementInvalid
+ end
+
+ # QueryCanceled will be raised when canceling statement due to user request.
+ class QueryCanceled < StatementInvalid
+ end
+
+ # UnknownAttributeReference is raised when an unknown and potentially unsafe
+ # value is passed to a query method when allow_unsafe_raw_sql is set to
+ # :disabled. For example, passing a non column name value to a relation's
+ # #order method might cause this exception.
+ #
+ # When working around this exception, caution should be taken to avoid SQL
+ # injection vulnerabilities when passing user-provided values to query
+ # methods. Known-safe values can be passed to query methods by wrapping them
+ # in Arel.sql.
+ #
+ # For example, with allow_unsafe_raw_sql set to :disabled, the following
+ # code would raise this exception:
+ #
+ # Post.order("length(title)").first
+ #
+ # The desired result can be accomplished by wrapping the known-safe string
+ # in Arel.sql:
+ #
+ # Post.order(Arel.sql("length(title)")).first
+ #
+ # Again, such a workaround should *not* be used when passing user-provided
+ # values, such as request parameters or model attributes to query methods.
+ class UnknownAttributeReference < ActiveRecordError
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 86f13d75d5..d9a75d9ad6 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -169,13 +169,13 @@ module ActiveRecord
# self.use_transactional_tests = true
#
# test "godzilla" do
- # assert !Foo.all.empty?
+ # assert_not_empty Foo.all
# Foo.destroy_all
- # assert Foo.all.empty?
+ # assert_empty Foo.all
# end
#
# test "godzilla aftermath" do
- # assert !Foo.all.empty?
+ # assert_not_empty Foo.all
# end
# end
#
@@ -540,47 +540,38 @@ module ActiveRecord
}
unless files_to_read.empty?
- connection.disable_referential_integrity do
- fixtures_map = {}
-
- fixture_sets = files_to_read.map do |fs_name|
- klass = class_names[fs_name]
- conn = klass ? klass.connection : connection
- fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
- conn,
- fs_name,
- klass,
- ::File.join(fixtures_directory, fs_name))
- end
-
- update_all_loaded_fixtures fixtures_map
-
- connection.transaction(requires_new: true) do
- deleted_tables = Hash.new { |h, k| h[k] = Set.new }
- fixture_sets.each do |fs|
- conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
- table_rows = fs.table_rows
+ fixtures_map = {}
+
+ fixture_sets = files_to_read.map do |fs_name|
+ klass = class_names[fs_name]
+ conn = klass ? klass.connection : connection
+ fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
+ conn,
+ fs_name,
+ klass,
+ ::File.join(fixtures_directory, fs_name))
+ end
- table_rows.each_key do |table|
- unless deleted_tables[conn].include? table
- conn.delete "DELETE FROM #{conn.quote_table_name(table)}", "Fixture Delete"
- end
- deleted_tables[conn] << table
- end
+ update_all_loaded_fixtures fixtures_map
+ fixture_sets_by_connection = fixture_sets.group_by { |fs| fs.model_class ? fs.model_class.connection : connection }
- table_rows.each do |fixture_set_name, rows|
- conn.insert_fixtures(rows, fixture_set_name)
- end
+ fixture_sets_by_connection.each do |conn, set|
+ table_rows_for_connection = Hash.new { |h, k| h[k] = [] }
- # Cap primary key sequences to max(pk).
- if conn.respond_to?(:reset_pk_sequence!)
- conn.reset_pk_sequence!(fs.table_name)
- end
+ set.each do |fs|
+ fs.table_rows.each do |table, rows|
+ table_rows_for_connection[table].unshift(*rows)
end
end
+ conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
- cache_fixtures(connection, fixtures_map)
+ # Cap primary key sequences to max(pk).
+ if conn.respond_to?(:reset_pk_sequence!)
+ set.each { |fs| conn.reset_pk_sequence!(fs.table_name) }
+ end
end
+
+ cache_fixtures(connection, fixtures_map)
end
cached_fixtures(connection, fixture_set_names)
end
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index 7ccb57b305..72035a986b 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -7,8 +7,8 @@ module ActiveRecord
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 0715c11473..f3fe610c09 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -47,14 +47,13 @@ module ActiveRecord
# Determines if one of the attributes passed in is the inheritance column,
# and if the inheritance column is attr accessible, it initializes an
# instance of the given subclass instead of the base class.
- def new(*args, &block)
+ def new(attributes = nil, &block)
if abstract_class? || self == Base
raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
end
- attrs = args.first
if has_attribute?(inheritance_column)
- subclass = subclass_from_attributes(attrs)
+ subclass = subclass_from_attributes(attributes)
if subclass.nil? && base_class == self
subclass = subclass_from_attributes(column_defaults)
@@ -62,7 +61,7 @@ module ActiveRecord
end
if subclass && subclass != self
- subclass.new(*args, &block)
+ subclass.new(attributes, &block)
else
super
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 405f3a30c6..9234029c22 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -90,6 +90,47 @@ module ActiveRecord
def logger
ActiveRecord::Base.logger
end
+
+ def debug(progname = nil, &block)
+ return unless super
+
+ if ActiveRecord::Base.verbose_query_logs
+ log_query_source
+ end
+ end
+
+ def log_query_source
+ source_line, line_number = extract_callstack(caller_locations)
+
+ if source_line
+ if defined?(::Rails.root)
+ app_root = "#{::Rails.root.to_s}/".freeze
+ source_line = source_line.sub(app_root, "")
+ end
+
+ logger.debug(" ↳ #{ source_line }:#{ line_number }")
+ end
+ end
+
+ def extract_callstack(callstack)
+ line = callstack.find do |frame|
+ frame.absolute_path && !ignored_callstack(frame.absolute_path)
+ end
+
+ offending_line = line || callstack.first
+
+ [
+ offending_line.path,
+ offending_line.lineno
+ ]
+ end
+
+ RAILS_GEM_ROOT = File.expand_path("../../../..", __FILE__) + "/"
+
+ def ignored_callstack(path)
+ path.start_with?(RAILS_GEM_ROOT) ||
+ path.start_with?(RbConfig::CONFIG["rubylibdir"])
+ end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index d12a979a7f..5322571820 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -550,7 +550,7 @@ module ActiveRecord
end
def call(env)
- mtime = ActiveRecord::Migrator.last_migration.mtime.to_i
+ mtime = ActiveRecord::Base.connection.migration_context.last_migration.mtime.to_i
if @last_check < mtime
ActiveRecord::Migration.check_pending!(connection)
@last_check = mtime
@@ -575,13 +575,14 @@ module ActiveRecord
# Raises <tt>ActiveRecord::PendingMigrationError</tt> error if any migrations are pending.
def check_pending!(connection = Base.connection)
- raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration?(connection)
+ raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration?
end
def load_schema_if_pending!
- if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations?
+ if Base.connection.migration_context.needs_migration? || !Base.connection.migration_context.any_migrations?
# Roundtrip to Rake to allow plugins to hook into database initialization.
- FileUtils.cd Rails.root do
+ root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
+ FileUtils.cd(root) do
current_config = Base.connection_config
Base.clear_all_connections!
system("bin/rails db:test:prepare")
@@ -733,6 +734,24 @@ module ActiveRecord
execute_block { yield helper }
end
+ # Used to specify an operation that is only run when migrating up
+ # (for example, populating a new column with its initial values).
+ #
+ # In the following example, the new column +published+ will be given
+ # the value +true+ for all existing records.
+ #
+ # class AddPublishedToPosts < ActiveRecord::Migration[5.2]
+ # def change
+ # add_column :posts, :published, :boolean, default: false
+ # up_only do
+ # execute "update posts set published = 'true'"
+ # end
+ # end
+ # end
+ def up_only
+ execute_block { yield } unless reverting?
+ end
+
# Runs the given migration classes.
# Last argument can specify options:
# - :direction (default is :up)
@@ -857,10 +876,10 @@ module ActiveRecord
FileUtils.mkdir_p(destination) unless File.exist?(destination)
- destination_migrations = ActiveRecord::Migrator.migrations(destination)
+ destination_migrations = ActiveRecord::MigrationContext.new(destination).migrations
last = destination_migrations.last
sources.each do |scope, path|
- source_migrations = ActiveRecord::Migrator.migrations(path)
+ source_migrations = ActiveRecord::MigrationContext.new(path).migrations
source_migrations.each do |migration|
source = File.binread(migration.filename)
@@ -978,131 +997,147 @@ module ActiveRecord
end
end
- class Migrator#:nodoc:
- class << self
- attr_writer :migrations_paths
- alias :migrations_path= :migrations_paths=
-
- def migrate(migrations_paths, target_version = nil, &block)
- case
- when target_version.nil?
- up(migrations_paths, target_version, &block)
- when current_version == 0 && target_version == 0
- []
- when current_version > target_version
- down(migrations_paths, target_version, &block)
- else
- up(migrations_paths, target_version, &block)
- end
- end
+ class MigrationContext # :nodoc:
+ attr_reader :migrations_paths
- def rollback(migrations_paths, steps = 1)
- move(:down, migrations_paths, steps)
- end
+ def initialize(migrations_paths)
+ @migrations_paths = migrations_paths
+ end
- def forward(migrations_paths, steps = 1)
- move(:up, migrations_paths, steps)
+ def migrate(target_version = nil, &block)
+ case
+ when target_version.nil?
+ up(target_version, &block)
+ when current_version == 0 && target_version == 0
+ []
+ when current_version > target_version
+ down(target_version, &block)
+ else
+ up(target_version, &block)
end
+ end
+
+ def rollback(steps = 1)
+ move(:down, steps)
+ end
- def up(migrations_paths, target_version = nil)
- migrations = migrations(migrations_paths)
- migrations.select! { |m| yield m } if block_given?
+ def forward(steps = 1)
+ move(:up, steps)
+ end
- new(:up, migrations, target_version).migrate
+ def up(target_version = nil)
+ selected_migrations = if block_given?
+ migrations.select { |m| yield m }
+ else
+ migrations
end
- def down(migrations_paths, target_version = nil)
- migrations = migrations(migrations_paths)
- migrations.select! { |m| yield m } if block_given?
+ Migrator.new(:up, selected_migrations, target_version).migrate
+ end
- new(:down, migrations, target_version).migrate
+ def down(target_version = nil)
+ selected_migrations = if block_given?
+ migrations.select { |m| yield m }
+ else
+ migrations
end
- def run(direction, migrations_paths, target_version)
- new(direction, migrations(migrations_paths), target_version).run
- end
+ Migrator.new(:down, selected_migrations, target_version).migrate
+ end
- def open(migrations_paths)
- new(:up, migrations(migrations_paths), nil)
- end
+ def run(direction, target_version)
+ Migrator.new(direction, migrations, target_version).run
+ end
- def get_all_versions(connection = Base.connection)
- if SchemaMigration.table_exists?
- SchemaMigration.all_versions.map(&:to_i)
- else
- []
- end
- end
+ def open
+ Migrator.new(:up, migrations, nil)
+ end
- def current_version(connection = Base.connection)
- get_all_versions(connection).max || 0
+ def get_all_versions
+ if SchemaMigration.table_exists?
+ SchemaMigration.all_versions.map(&:to_i)
+ else
+ []
end
+ end
- def needs_migration?(connection = Base.connection)
- (migrations(migrations_paths).collect(&:version) - get_all_versions(connection)).size > 0
- end
+ def current_version
+ get_all_versions.max || 0
+ rescue ActiveRecord::NoDatabaseError
+ end
- def any_migrations?
- migrations(migrations_paths).any?
- end
+ def needs_migration?
+ (migrations.collect(&:version) - get_all_versions).size > 0
+ end
- def last_migration #:nodoc:
- migrations(migrations_paths).last || NullMigration.new
- end
+ def any_migrations?
+ migrations.any?
+ end
- def migrations_paths
- @migrations_paths ||= ["db/migrate"]
- # just to not break things if someone uses: migrations_path = some_string
- Array(@migrations_paths)
- end
+ def last_migration #:nodoc:
+ migrations.last || NullMigration.new
+ end
- def parse_migration_filename(filename) # :nodoc:
- File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
+ def parse_migration_filename(filename) # :nodoc:
+ File.basename(filename).scan(Migration::MigrationFilenameRegexp).first
+ end
+
+ def migrations
+ migrations = migration_files.map do |file|
+ version, name, scope = parse_migration_filename(file)
+ raise IllegalMigrationNameError.new(file) unless version
+ version = version.to_i
+ name = name.camelize
+
+ MigrationProxy.new(name, version, file, scope)
end
- def migrations(paths)
- paths = Array(paths)
+ migrations.sort_by(&:version)
+ end
- migrations = migration_files(paths).map do |file|
- version, name, scope = parse_migration_filename(file)
- raise IllegalMigrationNameError.new(file) unless version
- version = version.to_i
- name = name.camelize
+ def migrations_status
+ db_list = ActiveRecord::SchemaMigration.normalized_versions
- MigrationProxy.new(name, version, file, scope)
- end
+ file_list = migration_files.map do |file|
+ version, name, scope = parse_migration_filename(file)
+ raise IllegalMigrationNameError.new(file) unless version
+ version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
+ status = db_list.delete(version) ? "up" : "down"
+ [status, version, (name + scope).humanize]
+ end.compact
- migrations.sort_by(&:version)
+ db_list.map! do |version|
+ ["up", version, "********** NO FILE **********"]
end
- def migrations_status(paths)
- paths = Array(paths)
-
- db_list = ActiveRecord::SchemaMigration.normalized_versions
+ (db_list + file_list).sort_by { |_, version, _| version }
+ end
- file_list = migration_files(paths).map do |file|
- version, name, scope = parse_migration_filename(file)
- raise IllegalMigrationNameError.new(file) unless version
- version = ActiveRecord::SchemaMigration.normalize_migration_number(version)
- status = db_list.delete(version) ? "up" : "down"
- [status, version, (name + scope).humanize]
- end.compact
+ def migration_files
+ paths = Array(migrations_paths)
+ Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
+ end
- db_list.map! do |version|
- ["up", version, "********** NO FILE **********"]
- end
+ def current_environment
+ ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ end
- (db_list + file_list).sort_by { |_, version, _| version }
- end
+ def protected_environment?
+ ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
+ end
- def migration_files(paths)
- Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }]
- end
+ def last_stored_environment
+ return nil if current_version == 0
+ raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
- private
+ environment = ActiveRecord::InternalMetadata[:environment]
+ raise NoEnvironmentInSchemaError unless environment
+ environment
+ end
- def move(direction, migrations_paths, steps)
- migrator = new(direction, migrations(migrations_paths))
+ private
+ def move(direction, steps)
+ migrator = Migrator.new(direction, migrations)
if current_version != 0 && !migrator.current_migration
raise UnknownMigrationVersionError.new(current_version)
@@ -1117,10 +1152,29 @@ module ActiveRecord
finish = migrator.migrations[start_index + steps]
version = finish ? finish.version : 0
- send(direction, migrations_paths, version)
+ send(direction, version)
+ end
+ end
+
+ class Migrator # :nodoc:
+ class << self
+ attr_accessor :migrations_paths
+
+ def migrations_path=(path)
+ ActiveSupport::Deprecation.warn \
+ "ActiveRecord::Migrator.migrations_paths= is now deprecated and will be removed in Rails 6.0." \
+ "You can set the `migrations_paths` on the `connection` instead through the `database.yml`."
+ self.migrations_paths = [path]
+ end
+
+ # For cases where a table doesn't exist like loading from schema cache
+ def current_version
+ MigrationContext.new(migrations_paths).current_version
end
end
+ self.migrations_paths = ["db/migrate"]
+
def initialize(direction, migrations, target_version = nil)
@direction = direction
@target_version = target_version
@@ -1183,7 +1237,7 @@ module ActiveRecord
end
def load_migrated
- @migrated_versions = Set.new(self.class.get_all_versions)
+ @migrated_versions = Set.new(Base.connection.migration_context.get_all_versions)
end
private
@@ -1215,7 +1269,7 @@ module ActiveRecord
# Stores the current environment in the database.
def record_environment
return if down?
- ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
end
def ran?(migration)
@@ -1257,10 +1311,10 @@ module ActiveRecord
end
def validate(migrations)
- name , = migrations.group_by(&:name).find { |_, v| v.length > 1 }
+ name, = migrations.group_by(&:name).find { |_, v| v.length > 1 }
raise DuplicateMigrationNameError.new(name) if name
- version , = migrations.group_by(&:version).find { |_, v| v.length > 1 }
+ version, = migrations.group_by(&:version).find { |_, v| v.length > 1 }
raise DuplicateMigrationVersionError.new(version) if version
end
@@ -1274,23 +1328,6 @@ module ActiveRecord
end
end
- def self.last_stored_environment
- return nil if current_version == 0
- raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists?
-
- environment = ActiveRecord::InternalMetadata[:environment]
- raise NoEnvironmentInSchemaError unless environment
- environment
- end
-
- def self.current_environment
- ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
- end
-
- def self.protected_environment?
- ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment
- end
-
def up?
@direction == :up
end
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index ac7d506fd1..81ef4828f8 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -110,7 +110,7 @@ module ActiveRecord
private
- module StraightReversions
+ module StraightReversions # :nodoc:
private
{ transaction: :transaction,
execute_block: :execute_block,
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index c979aaf0a0..0edaaa0cf9 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -13,9 +13,32 @@ module ActiveRecord
const_get(name)
end
- V5_2 = Current
+ V6_0 = Current
+
+ class V5_2 < V6_0
+ end
class V5_1 < V5_2
+ def change_column(table_name, column_name, type, options = {})
+ if adapter_name == "PostgreSQL"
+ clear_cache!
+ sql = connection.send(:change_column_sql, table_name, column_name, type, options)
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{sql}"
+ change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
+ else
+ super
+ end
+ end
+
+ def create_table(table_name, options = {})
+ if adapter_name == "Mysql2"
+ super(table_name, options: "ENGINE=InnoDB", **options)
+ else
+ super
+ end
+ end
end
class V5_0 < V5_1
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 12ee4a4137..b04dc04899 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -87,19 +87,6 @@ module ActiveRecord
# Sets the name of the internal metadata table.
##
- # :singleton-method: protected_environments
- # :call-seq: protected_environments
- #
- # The array of names of environments where destructive actions should be prohibited. By default,
- # the value is <tt>["production"]</tt>.
-
- ##
- # :singleton-method: protected_environments=
- # :call-seq: protected_environments=(environments)
- #
- # Sets an array of names of environments where destructive actions should be prohibited.
-
- ##
# :singleton-method: pluralize_table_names
# :call-seq: pluralize_table_names
#
@@ -122,9 +109,9 @@ module ActiveRecord
class_attribute :table_name_suffix, instance_writer: false, default: ""
class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations"
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
+ self.protected_environments = ["production"]
self.inheritance_column = "type"
self.ignored_columns = [].freeze
@@ -238,6 +225,21 @@ module ActiveRecord
(parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
end
+ # The array of names of environments where destructive actions should be prohibited. By default,
+ # the value is <tt>["production"]</tt>.
+ def protected_environments
+ if defined?(@protected_environments)
+ @protected_environments
+ else
+ superclass.protected_environments
+ end
+ end
+
+ # Sets an array of names of environments where destructive actions should be prohibited.
+ def protected_environments=(environments)
+ @protected_environments = environments.map(&:to_s)
+ end
+
# Defines the name of the table column which will store the class name on single-table
# inheritance situations.
#
@@ -323,11 +325,11 @@ module ActiveRecord
end
def attributes_builder # :nodoc:
- @attributes_builder ||= ActiveModel::AttributeSet::Builder.new(attribute_types, primary_key) do |name|
- unless columns_hash.key?(name)
- _default_attributes[name].dup
- end
+ unless defined?(@attributes_builder) && @attributes_builder
+ defaults = _default_attributes.except(*(column_names - [primary_key]))
+ @attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
end
+ @attributes_builder
end
def columns_hash # :nodoc:
@@ -359,8 +361,9 @@ module ActiveRecord
# it).
#
# +attr_name+ The name of the attribute to retrieve the type for. Must be
- # a string
+ # a string or a symbol.
def type_for_attribute(attr_name, &block)
+ attr_name = attr_name.to_s
if block
attribute_types.fetch(attr_name, &block)
else
@@ -376,6 +379,7 @@ module ActiveRecord
end
def _default_attributes # :nodoc:
+ load_schema
@default_attributes ||= ActiveModel::AttributeSet.new({})
end
@@ -423,7 +427,7 @@ module ActiveRecord
# end
def reset_column_information
connection.clear_cache!
- undefine_attribute_methods
+ ([self] + descendants).each(&:undefine_attribute_methods)
connection.schema_cache.clear_data_source_cache!(table_name)
reload_schema_from_cache
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 435c81c153..fa20bce3a9 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -63,6 +63,18 @@ module ActiveRecord
# member.update params[:member]
# member.avatar.icon # => 'sad'
#
+ # If you want to update the current avatar without providing the id, you must add <tt>:update_only</tt> option.
+ #
+ # class Member < ActiveRecord::Base
+ # has_one :avatar
+ # accepts_nested_attributes_for :avatar, update_only: true
+ # end
+ #
+ # params = { member: { avatar_attributes: { icon: 'sad' } } }
+ # member.update params[:member]
+ # member.avatar.id # => 2
+ # member.avatar.icon # => 'sad'
+ #
# By default you will only be able to set and update attributes on the
# associated model. If you want to destroy the associated model through the
# attributes hash, you have to enable it first using the
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index a57c60ffac..a45d011d75 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -99,7 +99,9 @@ module ActiveRecord
# 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
+ id.map { |one_id| find(one_id) }.each_with_index { |object, idx|
+ object.update(attributes[idx])
+ }
elsif id == :all
all.each { |record| record.update(attributes) }
else
@@ -112,7 +114,6 @@ module ActiveRecord
object.update(attributes)
object
end
- rescue RecordNotFound
end
# Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
@@ -136,11 +137,10 @@ module ActiveRecord
# Todo.destroy(todos)
def destroy(id)
if id.is_a?(Array)
- id.map { |one_id| destroy(one_id) }.compact
+ find(id).each(&:destroy)
else
find(id).destroy
end
- rescue RecordNotFound
end
# Deletes the row with a primary key matching the +id+ argument, using a
@@ -165,6 +165,38 @@ module ActiveRecord
where(primary_key => id_or_array).delete_all
end
+ def _insert_record(values) # :nodoc:
+ primary_key_value = nil
+
+ if primary_key && Hash === values
+ arel_primary_key = arel_attribute(primary_key)
+ primary_key_value = values[arel_primary_key]
+
+ if !primary_key_value && prefetch_primary_key?
+ primary_key_value = next_sequence_value
+ values[arel_primary_key] = primary_key_value
+ end
+ end
+
+ if values.empty?
+ im = arel_table.compile_insert(connection.empty_insert_statement_value)
+ im.into arel_table
+ else
+ im = arel_table.compile_insert(_substitute_values(values))
+ end
+
+ connection.insert(im, "#{self} Create", primary_key || false, primary_key_value)
+ end
+
+ def _update_record(values, id, id_was) # :nodoc:
+ bind = predicate_builder.build_bind_attribute(primary_key, id_was || id)
+ um = arel_table.where(
+ arel_attribute(primary_key).eq(bind)
+ ).compile_update(_substitute_values(values), primary_key)
+
+ connection.update(um, "#{self} Update")
+ end
+
private
# Called by +instantiate+ to decide which class to use for a new
# record instance.
@@ -174,6 +206,13 @@ module ActiveRecord
def discriminate_class_for_record(record)
self
end
+
+ def _substitute_values(values)
+ values.map do |attr, value|
+ bind = predicate_builder.build_bind_attribute(attr.name, value)
+ [attr, bind]
+ end
+ end
end
# Returns true if this object hasn't been saved yet -- that is, a record
@@ -320,10 +359,10 @@ module ActiveRecord
# Any change to the attributes on either instance will affect both instances.
# If you want to change the sti column as well, use #becomes! instead.
def becomes(klass)
- became = klass.new
+ became = klass.allocate
+ became.send(:initialize)
became.instance_variable_set("@attributes", @attributes)
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?)
became.errors.copy!(errors)
@@ -363,11 +402,7 @@ module ActiveRecord
verify_readonly_attribute(name)
public_send("#{name}=", value)
- if has_changes_to_save?
- save(validate: false)
- else
- true
- end
+ save(validate: false)
end
# Updates the attributes of the model from the passed-in hash and saves the
@@ -659,6 +694,7 @@ module ActiveRecord
def create_or_update(*args, &block)
_raise_readonly_record_error if readonly?
+ return false if destroyed?
result = new_record? ? _create_record(&block) : _update_record(*args, &block)
result != false
end
@@ -671,7 +707,7 @@ module ActiveRecord
rows_affected = 0
@_trigger_update_callback = true
else
- rows_affected = self.class.unscoped._update_record attributes_values, id, id_in_database
+ rows_affected = self.class._update_record(attributes_values, id, id_in_database)
@_trigger_update_callback = rows_affected > 0
end
@@ -685,7 +721,7 @@ module ActiveRecord
def _create_record(attribute_names = self.attribute_names)
attributes_values = arel_attributes_with_values_for_create(attribute_names)
- new_id = self.class.unscoped.insert attributes_values
+ new_id = self.class._insert_record(attributes_values)
self.id ||= new_id if self.class.primary_key
@new_record = false
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 3d5babb8b7..8e23128333 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -26,16 +26,19 @@ module ActiveRecord
end
def self.run
- caching_pool = ActiveRecord::Base.connection_pool
- caching_was_enabled = caching_pool.query_cache_enabled
+ ActiveRecord::Base.connection_handler.connection_pool_list.map do |pool|
+ caching_was_enabled = pool.query_cache_enabled
- caching_pool.enable_query_cache!
+ pool.enable_query_cache!
- [caching_pool, caching_was_enabled]
+ [pool, caching_was_enabled]
+ end
end
- def self.complete((caching_pool, caching_was_enabled))
- caching_pool.disable_query_cache! unless caching_was_enabled
+ def self.complete(caching_pools)
+ caching_pools.each do |pool, caching_was_enabled|
+ pool.disable_query_cache! unless caching_was_enabled
+ end
ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool|
pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 812e1d7a00..ade5946dd5 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -59,6 +59,7 @@ module ActiveRecord
console = ActiveSupport::Logger.new(STDERR)
Rails.logger.extend ActiveSupport::Logger.broadcast console
end
+ ActiveRecord::Base.verbose_query_logs = false
end
runner do
@@ -90,12 +91,16 @@ module ActiveRecord
filename = File.join(app.config.paths["db"].first, "schema_cache.yml")
if File.file?(filename)
+ current_version = ActiveRecord::Migrator.current_version
+
+ next if current_version.nil?
+
cache = YAML.load(File.read(filename))
- if cache.version == ActiveRecord::Migrator.current_version
+ if cache.version == current_version
connection.schema_cache = cache
connection_pool.schema_cache = cache.dup
else
- warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."
+ warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}."
end
end
end
@@ -177,7 +182,16 @@ end_warning
initializer "active_record.clear_active_connections" do
config.after_initialize do
ActiveSupport.on_load(:active_record) do
+ # Ideally the application doesn't connect to the database during boot,
+ # but sometimes it does. In case it did, we want to empty out the
+ # connection pools so that a non-database-using process (e.g. a master
+ # process in a forking server model) doesn't retain a needless
+ # connection. If it was needed, the incremental cost of reestablishing
+ # this connection is trivial: the rest of the pool would need to be
+ # populated anyway.
+
clear_active_connections!
+ flush_idle_connections!
end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 3bca2982e0..2e55713311 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -4,16 +4,16 @@ require "active_record"
db_namespace = namespace :db do
desc "Set the environment value for the database"
- task "environment:set" => [:environment, :load_config] do
+ task "environment:set" => :load_config do
ActiveRecord::InternalMetadata.create_table
- ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
end
- task check_protected_environments: [:environment, :load_config] do
+ task check_protected_environments: :load_config do
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
end
- task :load_config do
+ task load_config: :environment do
ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {}
ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
end
@@ -56,7 +56,7 @@ db_namespace = namespace :db do
end
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
- task migrate: [:environment, :load_config] do
+ task migrate: :load_config do
ActiveRecord::Tasks::DatabaseTasks.migrate
db_namespace["_dump"].invoke
end
@@ -78,7 +78,7 @@ db_namespace = namespace :db do
namespace :migrate do
# desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
- task redo: [:environment, :load_config] do
+ task redo: :load_config do
raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty?
if ENV["VERSION"]
@@ -94,35 +94,33 @@ db_namespace = namespace :db do
task reset: ["db:drop", "db:create", "db:migrate"]
# desc 'Runs the "up" for a given migration VERSION.'
- task up: [:environment, :load_config] do
+ task up: :load_config do
raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty?
ActiveRecord::Tasks::DatabaseTasks.check_target_version
- ActiveRecord::Migrator.run(
+ ActiveRecord::Base.connection.migration_context.run(
:up,
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths,
ActiveRecord::Tasks::DatabaseTasks.target_version
)
db_namespace["_dump"].invoke
end
# desc 'Runs the "down" for a given migration VERSION.'
- task down: [:environment, :load_config] do
+ task down: :load_config do
raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty?
ActiveRecord::Tasks::DatabaseTasks.check_target_version
- ActiveRecord::Migrator.run(
+ ActiveRecord::Base.connection.migration_context.run(
:down,
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths,
ActiveRecord::Tasks::DatabaseTasks.target_version
)
db_namespace["_dump"].invoke
end
desc "Display status of migrations"
- task status: [:environment, :load_config] do
+ task status: :load_config do
unless ActiveRecord::SchemaMigration.table_exists?
abort "Schema migrations table does not exist yet."
end
@@ -131,8 +129,7 @@ db_namespace = namespace :db do
puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
puts "-" * 50
- paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths
- ActiveRecord::Migrator.migrations_status(paths).each do |status, version, name|
+ ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
puts "#{status.center(8)} #{version.ljust(14)} #{name}"
end
puts
@@ -140,16 +137,16 @@ db_namespace = namespace :db do
end
desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)."
- task rollback: [:environment, :load_config] do
+ task rollback: :load_config do
step = ENV["STEP"] ? ENV["STEP"].to_i : 1
- ActiveRecord::Migrator.rollback(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
+ ActiveRecord::Base.connection.migration_context.rollback(step)
db_namespace["_dump"].invoke
end
# desc 'Pushes the schema to the next version (specify steps w/ STEP=n).'
- task forward: [:environment, :load_config] do
+ task forward: :load_config do
step = ENV["STEP"] ? ENV["STEP"].to_i : 1
- ActiveRecord::Migrator.forward(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step)
+ ActiveRecord::Base.connection.migration_context.forward(step)
db_namespace["_dump"].invoke
end
@@ -157,12 +154,12 @@ db_namespace = namespace :db do
task reset: [ "db:drop", "db:setup" ]
# desc "Retrieves the charset for the current environment's database"
- task charset: [:environment, :load_config] do
+ task charset: :load_config do
puts ActiveRecord::Tasks::DatabaseTasks.charset_current
end
# desc "Retrieves the collation for the current environment's database"
- task collation: [:environment, :load_config] do
+ task collation: :load_config do
begin
puts ActiveRecord::Tasks::DatabaseTasks.collation_current
rescue NoMethodError
@@ -171,13 +168,13 @@ db_namespace = namespace :db do
end
desc "Retrieves the current schema version number"
- task version: [:environment, :load_config] do
- puts "Current version: #{ActiveRecord::Migrator.current_version}"
+ task version: :load_config do
+ puts "Current version: #{ActiveRecord::Base.connection.migration_context.current_version}"
end
# desc "Raises an error if there are pending migrations"
- task abort_if_pending_migrations: [:environment, :load_config] do
- pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Tasks::DatabaseTasks.migrations_paths).pending_migrations
+ task abort_if_pending_migrations: :load_config do
+ pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations
if pending_migrations.any?
puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}"
@@ -199,7 +196,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
+ task load: :load_config do
require "active_record/fixtures"
base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path
@@ -221,7 +218,7 @@ db_namespace = namespace :db do
end
# 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
+ task identify: :load_config do
require "active_record/fixtures"
label, id = ENV["LABEL"], ENV["ID"]
@@ -247,7 +244,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
+ task dump: :load_config do
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|
@@ -257,7 +254,7 @@ db_namespace = namespace :db do
end
desc "Loads a schema.rb file into the database"
- task load: [:environment, :load_config, :check_protected_environments] do
+ task load: [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV["SCHEMA"])
end
@@ -267,14 +264,14 @@ db_namespace = namespace :db do
namespace :cache do
desc "Creates a db/schema_cache.yml file."
- task dump: [:environment, :load_config] do
+ task dump: :load_config do
conn = ActiveRecord::Base.connection
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml")
ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(conn, filename)
end
desc "Clears a db/schema_cache.yml file."
- task clear: [:environment, :load_config] do
+ task clear: :load_config do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml")
rm_f filename, verbose: false
end
@@ -284,7 +281,7 @@ db_namespace = namespace :db do
namespace :structure do
desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql"
- task dump: [:environment, :load_config] do
+ task dump: :load_config do
filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
@@ -299,7 +296,7 @@ db_namespace = namespace :db do
end
desc "Recreates the databases from the structure.sql file"
- task load: [:environment, :load_config, :check_protected_environments] do
+ task load: [:load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV["SCHEMA"])
end
@@ -338,12 +335,12 @@ db_namespace = namespace :db do
end
# desc "Empty the test database"
- task purge: %w(environment load_config check_protected_environments) do
+ task purge: %w(load_config check_protected_environments) do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"]
end
# desc 'Load the test schema'
- task prepare: %w(environment load_config) do
+ task prepare: :load_config do
unless ActiveRecord::Base.configurations.blank?
db_namespace["test:load"].invoke
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 87bfd75bca..e640d75d2f 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -34,7 +34,8 @@ module ActiveRecord
def self.add_reflection(ar, name, reflection)
ar.clear_reflections_cache
- ar._reflections = ar._reflections.merge(name.to_s => reflection)
+ name = name.to_s
+ ar._reflections = ar._reflections.except(name).merge!(name => reflection)
end
def self.add_aggregate_reflection(ar, name, reflection)
@@ -290,10 +291,14 @@ module ActiveRecord
end
def build_scope(table, predicate_builder = predicate_builder(table))
- Relation.create(klass, table, predicate_builder)
+ Relation.create(
+ klass,
+ table: table,
+ predicate_builder: predicate_builder
+ )
end
- def join_primary_key(_)
+ def join_primary_key(*)
foreign_key
end
@@ -458,10 +463,6 @@ module ActiveRecord
options[:primary_key] || primary_key(klass || self.klass)
end
- def association_primary_key_type
- klass.type_for_attribute(association_primary_key.to_s)
- end
-
def active_record_primary_key
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
end
@@ -567,7 +568,7 @@ module ActiveRecord
end
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
- INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :foreign_key]
+ INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key]
def add_as_source(seed)
seed
@@ -722,7 +723,7 @@ module ActiveRecord
end
end
- def join_primary_key(klass)
+ def join_primary_key(klass = nil)
polymorphic? ? association_primary_key(klass) : association_primary_key
end
@@ -859,10 +860,6 @@ module ActiveRecord
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
end
- def association_primary_key_type
- klass.type_for_attribute(association_primary_key.to_s)
- end
-
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
#
# class Post < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index e2d2f45503..d71c430045 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,7 +10,7 @@ module ActiveRecord
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
:reverse_order, :distinct, :create_with, :skip_query_cache]
CLAUSE_METHODS = [:where, :having, :from]
- INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]
+ INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having]
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
@@ -22,7 +22,7 @@ module ActiveRecord
alias :loaded? :loaded
alias :locked? :lock_value
- def initialize(klass, table, predicate_builder, values = {})
+ def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
@klass = klass
@table = table
@values = values
@@ -36,67 +36,6 @@ module ActiveRecord
reset
end
- def insert(values) # :nodoc:
- primary_key_value = nil
-
- if primary_key && Hash === values
- primary_key_value = values[values.keys.find { |k|
- k.name == primary_key
- }]
-
- if !primary_key_value && klass.prefetch_primary_key?
- primary_key_value = klass.next_sequence_value
- values[arel_attribute(klass.primary_key)] = primary_key_value
- end
- end
-
- im = arel.create_insert
- im.into @table
-
- substitutes = substitute_values values
-
- if values.empty? # empty insert
- im.values = Arel.sql(connection.empty_insert_statement_value)
- else
- im.insert substitutes
- end
-
- @klass.connection.insert(
- im,
- "#{@klass} Create",
- primary_key || false,
- primary_key_value,
- nil,
- )
- end
-
- def _update_record(values, id, id_was) # :nodoc:
- substitutes = substitute_values values
-
- scope = @klass.unscoped
-
- if @klass.finder_needs_type_condition?
- scope.unscope!(where: @klass.inheritance_column)
- end
-
- relation = scope.where(@klass.primary_key => (id_was || id))
- um = relation
- .arel
- .compile_update(substitutes, @klass.primary_key)
-
- @klass.connection.update(
- um,
- "#{@klass} Update",
- )
- end
-
- def substitute_values(values) # :nodoc:
- values.map do |arel_attr, value|
- bind = predicate_builder.build_bind_attribute(arel_attr.name, value)
- [arel_attr, bind]
- end
- end
-
def arel_attribute(name) # :nodoc:
klass.arel_attribute(name, table)
end
@@ -113,8 +52,8 @@ module ActiveRecord
#
# user = users.new { |user| user.name = 'Oscar' }
# user.name # => Oscar
- def new(*args, &block)
- scoping { @klass.new(*args, &block) }
+ def new(attributes = nil, &block)
+ scoping { klass.new(scope_for_create(attributes), &block) }
end
alias build new
@@ -138,8 +77,12 @@ module ActiveRecord
#
# users.create(name: nil) # validation on name
# # => #<User id: nil, name: nil, ...>
- def create(*args, &block)
- scoping { @klass.create(*args, &block) }
+ def create(attributes = nil, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr, &block) }
+ else
+ scoping { klass.create(scope_for_create(attributes), &block) }
+ end
end
# Similar to #create, but calls
@@ -148,8 +91,12 @@ module ActiveRecord
#
# Expects arguments in the same format as
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!].
- def create!(*args, &block)
- scoping { @klass.create!(*args, &block) }
+ def create!(attributes = nil, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create!(attr, &block) }
+ else
+ scoping { klass.create!(scope_for_create(attributes), &block) }
+ end
end
def first_or_create(attributes = nil, &block) # :nodoc:
@@ -367,10 +314,10 @@ module ActiveRecord
stmt = Arel::UpdateManager.new
- stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
+ stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates))
stmt.table(table)
- if has_join_values?
+ if has_join_values? || offset_value
@klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key))
else
stmt.key = arel_attribute(primary_key)
@@ -418,8 +365,8 @@ module ActiveRecord
#
# If an invalid method is supplied, #delete_all raises an ActiveRecordError:
#
- # Post.limit(100).delete_all
- # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit
+ # Post.distinct.delete_all
+ # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
def delete_all
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
value = get_value(method)
@@ -437,7 +384,7 @@ module ActiveRecord
stmt = Arel::DeleteManager.new
stmt.from(table)
- if has_join_values?
+ if has_join_values? || has_limit_or_offset?
@klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key))
else
stmt.wheres = arel.constraints
@@ -483,7 +430,7 @@ module ActiveRecord
relation = self
if eager_loading?
- find_with_associations { |rel, _| relation = rel }
+ apply_join_dependency { |rel, _| relation = rel }
end
conn = klass.connection
@@ -501,8 +448,10 @@ module ActiveRecord
where_clause.to_h(relation_table_name)
end
- def scope_for_create
- where_values_hash.merge!(create_with_value.stringify_keys)
+ def scope_for_create(attributes = nil)
+ scope = where_values_hash.merge!(create_with_value.stringify_keys)
+ scope.merge!(attributes) if attributes
+ scope
end
# Returns true if relation needs eager loading.
@@ -584,7 +533,7 @@ module ActiveRecord
skip_query_cache_if_necessary do
@records =
if eager_loading?
- find_with_associations do |relation, join_dependency|
+ apply_join_dependency do |relation, join_dependency|
if ActiveRecord::NullRelation === relation
[]
else
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 11256ab3d9..6a63eb70b1 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -131,7 +131,14 @@ module ActiveRecord
def calculate(operation, column_name)
if has_include?(column_name)
relation = apply_join_dependency
- relation.distinct! if operation.to_s.downcase == "count"
+
+ if operation.to_s.downcase == "count" && !distinct_value
+ relation.distinct!
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
+ if (column_name == :all || column_name.nil?) && select_values.empty?
+ relation.order_values = []
+ end
+ end
relation.calculate(operation, column_name)
else
@@ -183,6 +190,7 @@ module ActiveRecord
relation = apply_join_dependency
relation.pluck(*column_names)
else
+ enforce_raw_sql_whitelist(column_names)
relation = spawn
relation.select_values = column_names.map { |cn|
@klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
@@ -192,6 +200,24 @@ module ActiveRecord
end
end
+ # Pick the value(s) from the named column(s) in the current relation.
+ # This is short-hand for <tt>relation.limit(1).pluck(*column_names).first</tt>, and is primarily useful
+ # when you have a relation that's already narrowed down to a single row.
+ #
+ # Just like #pluck, #pick will only load the actual value, not the entire record object, so it's also
+ # more efficient. The value is, again like with pluck, typecast by the column type.
+ #
+ # Person.where(id: 1).pick(:name)
+ # # SELECT people.name FROM people WHERE id = 1 LIMIT 1
+ # # => 'David'
+ #
+ # Person.where(id: 1).pick(:name, :email_address)
+ # # SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
+ # # => [ 'David', 'david@loudthinking.com' ]
+ def pick(*column_names)
+ limit(1).pluck(*column_names).first
+ end
+
# Pluck all the ID's for the relation using the table's primary key
#
# Person.ids # SELECT people.id FROM people
@@ -216,7 +242,7 @@ module ActiveRecord
if operation == "count"
column_name ||= select_for_count
if column_name == :all
- if distinct && (group_values.any? || !(has_limit_or_offset? && order_values.any?))
+ if distinct && (group_values.any? || select_values.empty? && order_values.empty?)
column_name = primary_key
end
elsif column_name =~ /\s*DISTINCT[\s(]+/i
@@ -234,7 +260,7 @@ module ActiveRecord
def aggregate_column(column_name)
return column_name if Arel::Expressions === column_name
- if @klass.has_attribute?(column_name.to_s) || @klass.attribute_alias?(column_name.to_s)
+ if @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
@klass.arel_attribute(column_name)
else
Arel.sql(column_name == :all ? "*" : column_name.to_s)
@@ -248,7 +274,7 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
column_alias = column_name
- if operation == "count" && has_limit_or_offset?
+ if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
# Shortcut when limit is zero.
return 0 if limit_value == 0
@@ -390,14 +416,12 @@ module ActiveRecord
end
def build_count_subquery(relation, column_name, distinct)
- relation.select_values = [
- if column_name == :all
- distinct ? table[Arel.star] : Arel.sql(FinderMethods::ONE_AS_ONE)
- else
- column_alias = Arel.sql("count_column")
- aggregate_column(column_name).as(column_alias)
- end
- ]
+ if column_name == :all
+ relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
+ else
+ column_alias = Arel.sql("count_column")
+ relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
+ end
subquery = relation.arel.as(Arel.sql("subquery_for_count"))
select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 18566b5662..5f959af5dc 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -18,9 +18,10 @@ module ActiveRecord
# Person.find([1]) # returns an array for the object with ID = 1
# Person.where("administrator = 1").order("created_on DESC").find(1)
#
- # NOTE: The returned records may not be in the same order as the ids you
- # provide since database rows are unordered. You will need to provide an explicit QueryMethods#order
- # option if you want the results to be sorted.
+ # NOTE: The returned records are in the same order as the ids you provide.
+ # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
+ # method and provide an explicit ActiveRecord::QueryMethods#order option.
+ # But ActiveRecord::QueryMethods#where method doesn't raise ActiveRecord::RecordNotFound.
#
# ==== Find with lock
#
@@ -88,7 +89,7 @@ module ActiveRecord
where(arg, *args).take!
rescue ::RangeError
raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value",
- @klass.name)
+ @klass.name, @klass.primary_key)
end
# Gives a record (or N records if a parameter is supplied) without any implied
@@ -147,7 +148,7 @@ module ActiveRecord
#
# [#<Person id:4>, #<Person id:3>, #<Person id:2>]
def last(limit = nil)
- return find_last(limit) if loaded? || limit_value
+ return find_last(limit) if loaded? || has_limit_or_offset?
result = ordered_relation.limit(limit)
result = result.reverse_order!
@@ -284,7 +285,7 @@ module ActiveRecord
# * Hash - Finds the record that matches these +find+-style conditions
# (such as <tt>{name: 'David'}</tt>).
# * +false+ - Returns always +false+.
- # * No args - Returns +false+ if the table is empty, +true+ otherwise.
+ # * No args - Returns +false+ if the relation is empty, +true+ otherwise.
#
# For more information about specifying conditions as a hash or array,
# see the Conditions section in the introduction to ActiveRecord::Base.
@@ -300,6 +301,7 @@ module ActiveRecord
# Person.exists?(name: 'David')
# Person.exists?(false)
# Person.exists?
+ # Person.where(name: 'Spartacus', rating: 4).exists?
def exists?(conditions = :none)
if Base === conditions
raise ArgumentError, <<-MSG.squish
@@ -311,7 +313,7 @@ module ActiveRecord
return false if !conditions || limit_value == 0
if eager_loading?
- relation = apply_join_dependency(construct_join_dependency(eager_loading: false))
+ relation = apply_join_dependency(eager_loading: false)
return relation.exists?(conditions)
end
@@ -338,7 +340,7 @@ module ActiveRecord
if ids.nil?
error = "Couldn't find #{name}".dup
error << " with#{conditions}" if conditions
- raise RecordNotFound.new(error, name)
+ raise RecordNotFound.new(error, name, key)
elsif Array(ids).size == 1
error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
raise RecordNotFound.new(error, name, key, ids)
@@ -346,7 +348,7 @@ module ActiveRecord
error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
- raise RecordNotFound.new(error, name, primary_key, ids)
+ raise RecordNotFound.new(error, name, key, ids)
end
end
@@ -356,24 +358,6 @@ module ActiveRecord
offset_value || 0
end
- def find_with_associations
- # NOTE: the JoinDependency constructed here needs to know about
- # any joins already present in `self`, so pass them in
- #
- # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136
- # incorrect SQL is generated. In that case, the join dependency for
- # SpecialCategorizations is constructed without knowledge of the
- # preexisting join in joins_values to categorizations (by way of
- # the `has_many :through` for categories).
- #
- join_dependency = construct_join_dependency
-
- relation = apply_join_dependency(join_dependency)
- relation._select!(join_dependency.aliases.columns)
-
- yield relation, join_dependency
- end
-
def construct_relation_for_exists(conditions)
relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
@@ -389,22 +373,29 @@ module ActiveRecord
def construct_join_dependency(eager_loading: true)
including = eager_load_values + includes_values
+ joins = joins_values.select { |join| join.is_a?(Arel::Nodes::Join) }
ActiveRecord::Associations::JoinDependency.new(
- klass, table, including, alias_tracker(joins_values), eager_loading: eager_loading
+ klass, table, including, alias_tracker(joins), eager_loading: eager_loading
)
end
- def apply_join_dependency(join_dependency = construct_join_dependency)
+ def apply_join_dependency(eager_loading: true)
+ join_dependency = construct_join_dependency(eager_loading: eager_loading)
relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
- if using_limitable_reflections?(join_dependency.reflections)
- relation
- else
- if relation.limit_value
+ if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
+ if has_limit_or_offset?
limited_ids = limited_ids_for(relation)
limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
end
- relation.except(:limit, :offset)
+ relation.limit_value = relation.offset_value = nil
+ end
+
+ if block_given?
+ relation._select!(join_dependency.aliases.columns)
+ yield relation, join_dependency
+ else
+ relation
end
end
@@ -432,9 +423,12 @@ module ActiveRecord
ids = ids.flatten.compact.uniq
+ model_name = @klass.name
+
case ids.size
when 0
- raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
+ error_message = "Couldn't find #{model_name} without an ID"
+ raise RecordNotFound.new(error_message, model_name, primary_key)
when 1
result = find_one(ids.first)
expects_array ? [ result ] : result
@@ -442,7 +436,8 @@ module ActiveRecord
find_some(ids)
end
rescue ::RangeError
- raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"
+ error_message = "Couldn't find #{model_name} with an out of range ID"
+ raise RecordNotFound.new(error_message, model_name, primary_key, ids)
end
def find_one(id)
@@ -526,7 +521,11 @@ module ActiveRecord
else
relation = ordered_relation
- if limit_value.nil? || index < limit_value
+ if limit_value
+ limit = [limit_value - index, limit].min
+ end
+
+ if limit > 0
relation = relation.offset(offset_index + index) unless index.zero?
relation.limit(limit).to_a
else
@@ -541,12 +540,11 @@ module ActiveRecord
else
relation = ordered_relation
- relation.to_a[-index]
- # TODO: can be made more performant on large result sets by
- # for instance, last(index)[-index] (which would require
- # refactoring the last(n) finder method to make test suite pass),
- # or by using a combination of reverse_order, limit, and offset,
- # e.g., reverse_order.offset(index-1).first
+ if equal?(relation) || has_limit_or_offset?
+ relation.records[-index]
+ else
+ relation.last(index)[-index]
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index b736b21525..25510d4a57 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -23,7 +23,11 @@ module ActiveRecord
# build a relation to merge in rather than directly merging
# the values.
def other
- other = Relation.create(relation.klass, relation.table, relation.predicate_builder)
+ other = Relation.create(
+ relation.klass,
+ table: relation.table,
+ predicate_builder: relation.predicate_builder
+ )
hash.each { |k, v|
if k == :joins
if Hash === v
@@ -52,7 +56,7 @@ module ActiveRecord
NORMAL_VALUES = Relation::VALUE_METHODS -
Relation::CLAUSE_METHODS -
- [:includes, :preload, :joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
+ [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc:
def normal_values
NORMAL_VALUES
@@ -79,6 +83,7 @@ module ActiveRecord
merge_clauses
merge_preloads
merge_joins
+ merge_outer_joins
relation
end
@@ -129,6 +134,29 @@ module ActiveRecord
end
end
+ def merge_outer_joins
+ return if other.left_outer_joins_values.blank?
+
+ if other.klass == relation.klass
+ relation.left_outer_joins!(*other.left_outer_joins_values)
+ else
+ alias_tracker = nil
+ joins_dependency = other.left_outer_joins_values.map do |join|
+ case join
+ when Hash, Symbol, Array
+ alias_tracker ||= other.alias_tracker
+ ActiveRecord::Associations::JoinDependency.new(
+ other.klass, other.table, join, alias_tracker
+ )
+ else
+ join
+ end
+ end
+
+ relation.left_outer_joins!(*joins_dependency)
+ end
+ end
+
def merge_multi_values
if other.reordering_value
# override any order specified in the original relation
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 885c26d7aa..f3286846d2 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -86,6 +86,18 @@ module ActiveRecord
expand_from_hash(query).reduce(&:and)
end
queries.reduce(&:or)
+ elsif table.aggregated_with?(key)
+ mapping = table.reflect_on_aggregation(key).mapping
+ queries = Array.wrap(value).map do |object|
+ mapping.map do |field_attr, aggregate_attr|
+ if mapping.size == 1 && !object.respond_to?(aggregate_attr)
+ build(table.arel_attribute(field_attr), object)
+ else
+ build(table.arel_attribute(field_attr), object.send(aggregate_attr))
+ end
+ end.reduce(&:and)
+ end
+ queries.reduce(&:or)
# FIXME: Deprecate this and provide a public API to force equality
elsif (value.is_a?(Range) || value.is_a?(Array)) &&
table.type(key.to_s).respond_to?(:subtype)
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
index 0255a65bfe..28c7483c95 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb
@@ -30,7 +30,7 @@ module ActiveRecord
end
def primary_key
- associated_table.association_join_keys.key
+ associated_table.association_join_primary_key
end
def convert_to_id(value)
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
index b87b5c36dd..e8e2f2c626 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb
@@ -29,7 +29,7 @@ module ActiveRecord
end
def primary_key(value)
- associated_table.association_primary_key(base_class(value))
+ associated_table.association_join_primary_key(base_class(value))
end
def base_class(value)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index eec907e9cf..3afa368575 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -296,6 +296,7 @@ module ActiveRecord
spawn.order!(*args)
end
+ # Same as #order but operates on relation in-place instead of copying.
def order!(*args) # :nodoc:
preprocess_order_args(args)
@@ -317,6 +318,7 @@ module ActiveRecord
spawn.reorder!(*args)
end
+ # Same as #reorder but operates on relation in-place instead of copying.
def reorder!(*args) # :nodoc:
preprocess_order_args(args)
@@ -326,8 +328,8 @@ module ActiveRecord
end
VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
- :limit, :offset, :joins, :includes, :from,
- :readonly, :having])
+ :limit, :offset, :joins, :left_outer_joins,
+ :includes, :from, :readonly, :having])
# Removes an unwanted relation that is already defined on a chain of relations.
# This is useful when passing around chains of relations and would like to
@@ -374,10 +376,11 @@ module ActiveRecord
args.each do |scope|
case scope
when Symbol
+ scope = :left_outer_joins if scope == :left_joins
if !VALID_UNSCOPING_VALUES.include?(scope)
raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
end
- set_value(scope, nil)
+ set_value(scope, DEFAULT_VALUES[scope])
when Hash
scope.each do |key, target_value|
if key != :where
@@ -443,15 +446,13 @@ module ActiveRecord
#
def left_outer_joins(*args)
check_if_method_has_arguments!(__callee__, args)
-
- args.compact!
- args.flatten!
-
spawn.left_outer_joins!(*args)
end
alias :left_joins :left_outer_joins
def left_outer_joins!(*args) # :nodoc:
+ args.compact!
+ args.flatten!
self.left_outer_joins_values += args
self
end
@@ -906,7 +907,7 @@ module ActiveRecord
protected
# Returns a relation value with a given name
def get_value(name) # :nodoc:
- @values[name] || default_value_for(name)
+ @values.fetch(name, DEFAULT_VALUES[name])
end
# Sets the relation value with the given name
@@ -979,6 +980,8 @@ module ActiveRecord
case join
when Hash, Symbol, Array
:association_join
+ when ActiveRecord::Associations::JoinDependency
+ :stashed_join
else
raise ArgumentError, "only Hash, Symbol and Array are allowed"
end
@@ -1014,7 +1017,7 @@ module ActiveRecord
join_nodes = buckets[:join_node].uniq
string_joins = buckets[:string_join].map(&:strip).uniq
- join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins)
+ join_list = join_nodes + convert_join_strings_to_ast(string_joins)
alias_tracker = alias_tracker(join_list, aliases)
join_dependency = ActiveRecord::Associations::JoinDependency.new(
@@ -1029,7 +1032,7 @@ module ActiveRecord
alias_tracker.aliases
end
- def convert_join_strings_to_ast(table, joins)
+ def convert_join_strings_to_ast(joins)
joins
.flatten
.reject(&:blank?)
@@ -1039,6 +1042,8 @@ module ActiveRecord
def build_select(arel)
if select_values.any?
arel.project(*arel_columns(select_values.uniq))
+ elsif klass.ignored_columns.any?
+ arel.project(*klass.column_names.map { |field| arel_attribute(field) })
else
arel.project(table[Arel.star])
end
@@ -1075,7 +1080,7 @@ module ActiveRecord
end
o.split(",").map! do |s|
s.strip!
- s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || s.concat(" DESC")
+ s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC")
end
else
o
@@ -1084,6 +1089,10 @@ module ActiveRecord
end
def does_not_support_reverse?(order)
+ # Account for String subclasses like Arel::Nodes::SqlLiteral that
+ # override methods like #count.
+ order = String.new(order) unless order.instance_of?(String)
+
# Uses SQL function with multiple arguments.
(order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
# Uses "nulls first" like construction.
@@ -1114,9 +1123,15 @@ module ActiveRecord
def preprocess_order_args(order_args)
order_args.map! do |arg|
- klass.send(:sanitize_sql_for_order, arg)
+ klass.sanitize_sql_for_order(arg)
end
order_args.flatten!
+
+ @klass.enforce_raw_sql_whitelist(
+ order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
+ whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
+ )
+
validate_order_args(order_args)
references = order_args.grep(String)
@@ -1179,7 +1194,6 @@ module ActiveRecord
DEFAULT_VALUES = {
create_with: FROZEN_EMPTY_HASH,
- readonly: false,
where: Relation::WhereClause.empty,
having: Relation::WhereClause.empty,
from: Relation::FromClause.empty
@@ -1188,15 +1202,5 @@ module ActiveRecord
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)
- DEFAULT_VALUES.fetch(name) do
- raise ArgumentError, "unknown relation value #{name.inspect}"
- end
- end
end
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 617d8de8b2..562e04194c 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -69,7 +69,7 @@ module ActiveRecord
private
def relation_with(values)
- result = Relation.create(klass, table, predicate_builder, values)
+ result = Relation.create(klass, values: values)
result.extend(*extending_values) if extending_values.any?
result
end
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 752bb38481..a502713e56 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -47,7 +47,7 @@ module ActiveRecord
end
def to_h(table_name = nil)
- equalities = predicates.grep(Arel::Nodes::Equality)
+ equalities = equalities(predicates)
if table_name
equalities = equalities.select do |node|
node.left.relation.name == table_name
@@ -90,6 +90,20 @@ module ActiveRecord
end
private
+ def equalities(predicates)
+ equalities = []
+
+ predicates.each do |node|
+ case node
+ when Arel::Nodes::Equality
+ equalities << node
+ when Arel::Nodes::And
+ equalities.concat equalities(node.children)
+ end
+ end
+
+ equalities
+ end
def predicates_unreferenced_by(other)
predicates.reject do |n|
@@ -121,7 +135,7 @@ module ActiveRecord
end
def except_predicates(columns)
- self.predicates.reject do |node|
+ predicates.reject do |node|
case node
when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index 1374785354..92b720068c 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -11,10 +11,9 @@ module ActiveRecord
def build(opts, other)
case opts
when String, Array
- parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
+ parts = [klass.sanitize_sql(other.empty? ? opts : ([opts] + other))]
when Hash
attributes = predicate_builder.resolve_column_aliases(opts)
- attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
attributes.stringify_keys!
parts = predicate_builder.build_from_hash(attributes)
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 1c3099f55c..c6c268855e 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -5,70 +5,135 @@ module ActiveRecord
extend ActiveSupport::Concern
module ClassMethods
- private
+ # Accepts an array or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a WHERE clause.
+ #
+ # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
+ # # => "name='foo''bar' and group_id='4'"
+ #
+ # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id='4'"
+ #
+ # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
+ # # => "name='foo''bar' and group_id='4'"
+ def sanitize_sql_for_conditions(condition)
+ return nil if condition.blank?
- # Accepts an array or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a WHERE clause.
- #
- # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
- # # => "name='foo''bar' and group_id=4"
- #
- # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
- # # => "name='foo''bar' and group_id='4'"
- #
- # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
- # # => "name='foo''bar' and group_id='4'"
- #
- # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
- # # => "name='foo''bar' and group_id='4'"
- def sanitize_sql_for_conditions(condition) # :doc:
- return nil if condition.blank?
-
- case condition
- when Array; sanitize_sql_array(condition)
- else condition
- end
+ case condition
+ when Array; sanitize_sql_array(condition)
+ else condition
end
- alias :sanitize_sql :sanitize_sql_for_conditions
+ end
+ alias :sanitize_sql :sanitize_sql_for_conditions
- # Accepts an array, hash, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a SET clause.
- #
- # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
- # # => "name=NULL and group_id=4"
- #
- # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
- # # => "name=NULL and group_id=4"
- #
- # Post.send(:sanitize_sql_for_assignment, { name: nil, group_id: 4 })
- # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
- #
- # sanitize_sql_for_assignment("name=NULL and group_id='4'")
- # # => "name=NULL and group_id='4'"
- def sanitize_sql_for_assignment(assignments, default_table_name = table_name) # :doc:
- case assignments
- when Array; sanitize_sql_array(assignments)
- when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
- else assignments
- end
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a SET clause.
+ #
+ # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
+ # # => "name=NULL and group_id=4"
+ #
+ # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
+ # # => "name=NULL and group_id=4"
+ #
+ # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
+ # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
+ #
+ # sanitize_sql_for_assignment("name=NULL and group_id='4'")
+ # # => "name=NULL and group_id='4'"
+ def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
+ case assignments
+ when Array; sanitize_sql_array(assignments)
+ when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
+ else assignments
end
+ end
- # Accepts an array, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for an ORDER clause.
- #
- # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
- # # => "field(id, 1,3,2)"
- #
- # sanitize_sql_for_order("id ASC")
- # # => "id ASC"
- def sanitize_sql_for_order(condition) # :doc:
- if condition.is_a?(Array) && condition.first.to_s.include?("?")
- sanitize_sql_array(condition)
- else
- condition
+ # Accepts an array, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for an ORDER clause.
+ #
+ # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
+ # # => "field(id, 1,3,2)"
+ #
+ # sanitize_sql_for_order("id ASC")
+ # # => "id ASC"
+ def sanitize_sql_for_order(condition)
+ if condition.is_a?(Array) && condition.first.to_s.include?("?")
+ enforce_raw_sql_whitelist([condition.first],
+ whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST
+ )
+
+ # Ensure we aren't dealing with a subclass of String that might
+ # override methods we use (eg. Arel::Nodes::SqlLiteral).
+ if condition.first.kind_of?(String) && !condition.first.instance_of?(String)
+ condition = [String.new(condition.first), *condition[1..-1]]
end
+
+ Arel.sql(sanitize_sql_array(condition))
+ else
+ condition
+ end
+ end
+
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
+ #
+ # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
+ # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
+ def sanitize_sql_hash_for_assignment(attrs, table)
+ c = connection
+ attrs.map do |attr, value|
+ type = type_for_attribute(attr)
+ value = type.serialize(type.cast(value))
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
+ end.join(", ")
+ end
+
+ # Sanitizes a +string+ so that it is safe to use within an SQL
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
+ #
+ # sanitize_sql_like("100%")
+ # # => "100\\%"
+ #
+ # sanitize_sql_like("snake_cased_string")
+ # # => "snake\\_cased\\_string"
+ #
+ # sanitize_sql_like("100%", "!")
+ # # => "100!%"
+ #
+ # sanitize_sql_like("snake_cased_string", "!")
+ # # => "snake!_cased!_string"
+ def sanitize_sql_like(string, escape_character = "\\")
+ pattern = Regexp.union(escape_character, "%", "_")
+ string.gsub(pattern) { |x| [escape_character, x].join }
+ end
+
+ # Accepts an array of conditions. The array has each value
+ # sanitized and interpolated into the SQL statement.
+ #
+ # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
+ # # => "name='foo''bar' and group_id=4"
+ #
+ # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
+ # # => "name='foo''bar' and group_id='4'"
+ def sanitize_sql_array(ary)
+ statement, *values = ary
+ if values.first.is_a?(Hash) && /:\w+/.match?(statement)
+ replace_named_bind_variables(statement, values.first)
+ elsif statement.include?("?")
+ replace_bind_variables(statement, values)
+ elsif statement.blank?
+ statement
+ else
+ statement % values.collect { |value| connection.quote_string(value.to_s) }
end
+ end
+ private
# Accepts a hash of SQL conditions and replaces those attributes
# that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of]
# relationship with their expanded aggregate attribute values.
@@ -90,10 +155,12 @@ module ActiveRecord
if aggregation = reflect_on_aggregation(attr.to_sym)
mapping = aggregation.mapping
mapping.each do |field_attr, aggregate_attr|
- if mapping.size == 1 && !value.respond_to?(aggregate_attr)
- expanded_attrs[field_attr] = value
+ expanded_attrs[field_attr] = if value.is_a?(Array)
+ value.map { |it| it.send(aggregate_attr) }
+ elsif mapping.size == 1 && !value.respond_to?(aggregate_attr)
+ value
else
- expanded_attrs[field_attr] = value.send(aggregate_attr)
+ value.send(aggregate_attr)
end
end
else
@@ -102,61 +169,7 @@ module ActiveRecord
end
expanded_attrs
end
-
- # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
- #
- # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
- # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1"
- def sanitize_sql_hash_for_assignment(attrs, table) # :doc:
- c = connection
- attrs.map do |attr, value|
- value = type_for_attribute(attr.to_s).serialize(value)
- "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
- end.join(", ")
- end
-
- # Sanitizes a +string+ so that it is safe to use within an SQL
- # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
- #
- # sanitize_sql_like("100%")
- # # => "100\\%"
- #
- # sanitize_sql_like("snake_cased_string")
- # # => "snake\\_cased\\_string"
- #
- # sanitize_sql_like("100%", "!")
- # # => "100!%"
- #
- # sanitize_sql_like("snake_cased_string", "!")
- # # => "snake!_cased!_string"
- def sanitize_sql_like(string, escape_character = "\\") # :doc:
- pattern = Regexp.union(escape_character, "%", "_")
- string.gsub(pattern) { |x| [escape_character, x].join }
- end
-
- # Accepts an array of conditions. The array has each value
- # sanitized and interpolated into the SQL statement.
- #
- # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
- # # => "name='foo''bar' and group_id=4"
- #
- # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
- # # => "name='foo''bar' and group_id=4"
- #
- # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
- # # => "name='foo''bar' and group_id='4'"
- def sanitize_sql_array(ary) # :doc:
- statement, *values = ary
- if values.first.is_a?(Hash) && /:\w+/.match?(statement)
- replace_named_bind_variables(statement, values.first)
- elsif statement.include?("?")
- replace_bind_variables(statement, values)
- elsif statement.blank?
- statement
- else
- statement % values.collect { |value| connection.quote_string(value.to_s) }
- end
- end
+ deprecate :expand_hash_conditions_for_aggregates
def replace_bind_variables(statement, values)
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 1e121f2a09..216359867c 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -55,7 +55,7 @@ module ActiveRecord
end
ActiveRecord::InternalMetadata.create_table
- ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
end
private
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 66f7d29886..b8d848b999 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -44,7 +44,7 @@ module ActiveRecord
def initialize(connection, options = {})
@connection = connection
- @version = Migrator::current_version rescue nil
+ @version = connection.migration_context.current_version rescue nil
@options = options
end
@@ -184,8 +184,9 @@ HEADER
"name: #{index.name.inspect}",
]
index_parts << "unique: true" if index.unique
- index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present?
- index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present?
+ index_parts << "length: #{format_index_parts(index.lengths)}" if index.lengths.present?
+ index_parts << "order: #{format_index_parts(index.orders)}" if index.orders.present?
+ index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present?
index_parts << "where: #{index.where.inspect}" if index.where
index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
index_parts << "type: #{index.type.inspect}" if index.type
@@ -231,6 +232,14 @@ HEADER
options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
end
+ def format_index_parts(options)
+ if options.is_a?(Hash)
+ "{ #{format_options(options)} }"
+ else
+ options.inspect
+ end
+ end
+
def remove_prefix_and_suffix(table)
prefix = Regexp.escape(@options[:table_name_prefix].to_s)
suffix = Regexp.escape(@options[:table_name_suffix].to_s)
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index da585a9562..01ac56570a 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -11,23 +11,23 @@ module ActiveRecord
include Named
end
- module ClassMethods
- def current_scope(skip_inherited_scope = false) # :nodoc:
+ module ClassMethods # :nodoc:
+ def current_scope(skip_inherited_scope = false)
ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
end
- def current_scope=(scope) #:nodoc:
+ def current_scope=(scope)
ScopeRegistry.set_value_for(:current_scope, self, scope)
end
# Collects attributes from scopes that should be applied when creating
# an AR instance for the particular class this is called on.
- def scope_attributes # :nodoc:
+ def scope_attributes
all.scope_for_create
end
# Are there attributes associated with this scope?
- def scope_attributes? # :nodoc:
+ def scope_attributes?
current_scope
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 86ae374318..8c612df27a 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -111,7 +111,7 @@ module ActiveRecord
# The user has defined their own default scope method, so call that
evaluate_default_scope do
if scope = default_scope
- (base_rel ||= relation).merge(scope)
+ (base_rel ||= relation).merge!(scope)
end
end
elsif default_scopes.any?
@@ -119,7 +119,7 @@ module ActiveRecord
evaluate_default_scope do
default_scopes.inject(base_rel) do |default_scope, scope|
scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
- default_scope.merge(base_rel.instance_exec(&scope))
+ default_scope.merge!(base_rel.instance_exec(&scope))
end
end
end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 310af72c41..752655aa05 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -171,6 +171,12 @@ module ActiveRecord
"a class method with the same name."
end
+ if method_defined_within?(name, Relation)
+ raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
+ "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \
+ "an instance method with the same name."
+ end
+
valid_scope_name?(name)
extension = Module.new(&block) if block
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 202b82fa61..6dbc977f9a 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -135,7 +135,7 @@ module ActiveRecord
end
def store_accessor_for(store_attribute)
- type_for_attribute(store_attribute.to_s).accessor
+ type_for_attribute(store_attribute).accessor
end
class HashAccessor # :nodoc:
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index a5187efc84..d3b9171159 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -2,8 +2,7 @@
module ActiveRecord
class TableMetadata # :nodoc:
- delegate :foreign_type, :foreign_key, :join_keys, :join_foreign_key, to: :association, prefix: true
- delegate :association_primary_key, to: :association
+ delegate :foreign_type, :foreign_key, :join_primary_key, :join_foreign_key, to: :association, prefix: true
def initialize(klass, arel_table, association = nil)
@klass = klass
@@ -31,7 +30,7 @@ module ActiveRecord
def type(column_name)
if klass
- klass.type_for_attribute(column_name.to_s)
+ klass.type_for_attribute(column_name)
else
Type.default_value
end
@@ -66,6 +65,14 @@ module ActiveRecord
association && association.polymorphic?
end
+ def aggregated_with?(aggregation_name)
+ klass && reflect_on_aggregation(aggregation_name)
+ end
+
+ def reflect_on_aggregation(aggregation_name)
+ klass.reflect_on_aggregation(aggregation_name)
+ end
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
# Workaround for Ruby 2.2 "private attribute?" warning.
protected
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 4657e51e6d..d8e0cd1e30 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -54,10 +54,10 @@ module ActiveRecord
def check_protected_environments!
unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"]
- current = ActiveRecord::Migrator.current_environment
- stored = ActiveRecord::Migrator.last_stored_environment
+ current = ActiveRecord::Base.connection.migration_context.current_environment
+ stored = ActiveRecord::Base.connection.migration_context.last_stored_environment
- if ActiveRecord::Migrator.protected_environment?
+ if ActiveRecord::Base.connection.migration_context.protected_environment?
raise ActiveRecord::ProtectedEnvironmentError.new(stored)
end
@@ -117,9 +117,9 @@ module ActiveRecord
def create(*arguments)
configuration = arguments.first
class_for_adapter(configuration["adapter"]).new(*arguments).create
- $stdout.puts "Created database '#{configuration['database']}'"
+ $stdout.puts "Created database '#{configuration['database']}'" if verbose?
rescue DatabaseAlreadyExists
- $stderr.puts "Database '#{configuration['database']}' already exists"
+ $stderr.puts "Database '#{configuration['database']}' already exists" if verbose?
rescue Exception => error
$stderr.puts error
$stderr.puts "Couldn't create database for #{configuration.inspect}"
@@ -144,7 +144,7 @@ module ActiveRecord
def drop(*arguments)
configuration = arguments.first
class_for_adapter(configuration["adapter"]).new(*arguments).drop
- $stdout.puts "Dropped database '#{configuration['database']}'"
+ $stdout.puts "Dropped database '#{configuration['database']}'" if verbose?
rescue ActiveRecord::NoDatabaseError
$stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
@@ -163,13 +163,17 @@ module ActiveRecord
}
end
+ def verbose?
+ ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
+ end
+
def migrate
check_target_version
- verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true
+ verbose = verbose?
scope = ENV["SCOPE"]
verbose_was, Migration.verbose = Migration.verbose, verbose
- Migrator.migrate(migrations_paths, target_version) do |migration|
+ Base.connection.migration_context.migrate(target_version) do |migration|
scope.blank? || scope == migration.scope
end
ActiveRecord::Base.clear_cache!
diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb
index d51350ba83..c5cfdba8b1 100644
--- a/activerecord/lib/active_record/type_caster/map.rb
+++ b/activerecord/lib/active_record/type_caster/map.rb
@@ -9,7 +9,7 @@ module ActiveRecord
def type_cast_for_database(attr_name, value)
return value if value.is_a?(Arel::Nodes::BindParam)
- type = types.type_for_attribute(attr_name.to_s)
+ type = types.type_for_attribute(attr_name)
type.serialize(value)
end
diff --git a/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb b/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt
index 60050e0bf8..60050e0bf8 100644
--- a/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb
+++ b/activerecord/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt
index 5f7201cfe1..5f7201cfe1 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt
index 481c70201b..481c70201b 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt
index 55dc65c8ad..55dc65c8ad 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb.tt
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/module.rb b/activerecord/lib/rails/generators/active_record/model/templates/module.rb.tt
index a3bf1c37b6..a3bf1c37b6 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/module.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/module.rb.tt
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 9aaa2852d0..0092146e09 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -78,7 +78,7 @@ module ActiveRecord
idx_name = "accounts_idx"
indexes = @connection.indexes("accounts")
- assert indexes.empty?
+ assert_empty indexes
@connection.add_index :accounts, :firm_id, name: idx_name
indexes = @connection.indexes("accounts")
@@ -368,16 +368,16 @@ module ActiveRecord
unless in_memory_db?
test "transaction state is reset after a reconnect" do
@connection.begin_transaction
- assert @connection.transaction_open?
+ assert_predicate @connection, :transaction_open?
@connection.reconnect!
- assert !@connection.transaction_open?
+ assert_not_predicate @connection, :transaction_open?
end
test "transaction state is reset after a disconnect" do
@connection.begin_transaction
- assert @connection.transaction_open?
+ assert_predicate @connection, :transaction_open?
@connection.disconnect!
- assert !@connection.transaction_open?
+ assert_not_predicate @connection, :transaction_open?
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 4e73c557ed..9ae2c42368 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -68,14 +68,14 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def (ActiveRecord::Base.connection).data_source_exists?(*); false; end
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
- expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`)) ENGINE=InnoDB"
+ expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`))"
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, type: type
end
assert_equal expected, actual
end
- expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10))) ENGINE=InnoDB"
+ expected = "CREATE TABLE `people` ( INDEX `index_people_on_last_name` USING btree (`last_name`(10)))"
actual = ActiveRecord::Base.connection.create_table(:people, id: false) do |t|
t.index :last_name, length: 10, using: :btree
end
@@ -108,7 +108,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
def test_create_mysql_database_with_encoding
assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, charset: "latin1")
- assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, charset: :big5, collation: :big5_chinese_ci)
+ assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT COLLATE `utf8mb4_bin`", create_database(:matt_aimonetti, collation: "utf8mb4_bin")
end
def test_recreate_mysql_database_with_encoding
@@ -160,7 +160,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false)
ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false)
- expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query"
+ expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`)) AS SELECT id, name, zip FROM a_really_complicated_query"
actual = ActiveRecord::Base.connection.create_table(:temp, temporary: true, as: "SELECT id, name, zip FROM a_really_complicated_query") do |t|
t.index :zip
end
diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
index fd5f712f1a..aa870349be 100644
--- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb
@@ -14,8 +14,8 @@ class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase
end
def test_case_sensitive
- assert !CollationTest.columns_hash["string_ci_column"].case_sensitive?
- assert CollationTest.columns_hash["string_cs_column"].case_sensitive?
+ assert_not_predicate CollationTest.columns_hash["string_ci_column"], :case_sensitive?
+ assert_predicate CollationTest.columns_hash["string_cs_column"], :case_sensitive?
end
def test_case_insensitive_comparison_for_ci_column
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index e61c70848a..726f58d58e 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -40,29 +40,29 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
def test_no_automatic_reconnection_after_timeout
- assert @connection.active?
+ assert_predicate @connection, :active?
@connection.update("set @@wait_timeout=1")
sleep 2
- assert !@connection.active?
+ assert_not_predicate @connection, :active?
ensure
# Repair all fixture connections so other tests won't break.
@fixture_connections.each(&:verify!)
end
def test_successful_reconnection_after_timeout_with_manual_reconnect
- assert @connection.active?
+ assert_predicate @connection, :active?
@connection.update("set @@wait_timeout=1")
sleep 2
@connection.reconnect!
- assert @connection.active?
+ assert_predicate @connection, :active?
end
def test_successful_reconnection_after_timeout_with_verify
- assert @connection.active?
+ assert_predicate @connection, :active?
@connection.update("set @@wait_timeout=1")
sleep 2
@connection.verify!
- assert @connection.active?
+ assert_predicate @connection, :active?
end
def test_execute_after_disconnect
@@ -174,10 +174,10 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
- def test_logs_name_rename_column_sql
+ def test_logs_name_rename_column_for_alter
@connection.execute "CREATE TABLE `bar_baz` (`foo` varchar(255))"
@subscriber.logged.clear
- @connection.send(:rename_column_sql, "bar_baz", "foo", "foo2")
+ @connection.send(:rename_column_for_alter, "bar_baz", "foo", "foo2")
assert_equal "SCHEMA", @subscriber.logged[0][1]
ensure
@connection.execute "DROP TABLE `bar_baz`"
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index 108bec832c..832f5d61d1 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -13,11 +13,11 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
def test_should_not_be_unsigned
column = EnumTest.columns_hash["enum_column"]
- assert_not column.unsigned?
+ assert_not_predicate column, :unsigned?
end
def test_should_not_be_bigint
column = EnumTest.columns_hash["enum_column"]
- assert_not column.bigint?
+ assert_not_predicate column, :bigint?
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
index 2736f7cf0e..b8e778f0b0 100644
--- a/activerecord/test/cases/adapters/mysql2/explain_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -1,23 +1,23 @@
# frozen_string_literal: true
require "cases/helper"
-require "models/developer"
-require "models/computer"
+require "models/author"
+require "models/post"
class Mysql2ExplainTest < ActiveRecord::Mysql2TestCase
- fixtures :developers
+ fixtures :authors
def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
+ explain = Author.where(id: 1).explain
+ assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain
+ assert_match %r(authors |.* const), explain
end
def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
- assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
- assert_match %r(developers |.* const), explain
- assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain
- assert_match %r(audit_logs |.* ALL), explain
+ explain = Author.where(id: 1).includes(:posts).explain
+ assert_match %(EXPLAIN for: SELECT `authors`.* FROM `authors` WHERE `authors`.`id` = 1), explain
+ assert_match %r(authors |.* const), explain
+ assert_match %(EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`author_id` = 1), explain
+ assert_match %r(posts |.* ALL), explain
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
index 62abd694bb..d7d9a2d732 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -36,7 +36,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
assert connection.column_exists?(table_name, :key, :string)
end
ensure
- ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
end
private
diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
index 9f6bd38a8c..1c92df940f 100644
--- a/activerecord/test/cases/adapters/mysql2/table_options_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb
@@ -42,3 +42,78 @@ class Mysql2TableOptionsTest < ActiveRecord::Mysql2TestCase
assert_match %r{COLLATE=utf8mb4_bin}, options
end
end
+
+class Mysql2DefaultEngineOptionSchemaDumpTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+ self.use_transactional_tests = false
+
+ def setup
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+ end
+
+ def teardown
+ ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ end
+
+ test "schema dump includes ENGINE=InnoDB if not provided" do
+ ActiveRecord::Base.connection.create_table "mysql_table_options", force: true
+
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=InnoDB}, options
+ end
+
+ test "schema dump includes ENGINE=InnoDB in legacy migrations" do
+ migration = Class.new(ActiveRecord::Migration[5.1]) do
+ def migrate(x)
+ create_table "mysql_table_options", force: true
+ end
+ end.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ output = dump_table_schema("mysql_table_options")
+ options = %r{create_table "mysql_table_options", options: "(?<options>.*)"}.match(output)[:options]
+ assert_match %r{ENGINE=InnoDB}, options
+ end
+end
+
+class Mysql2DefaultEngineOptionSqlOutputTest < ActiveRecord::Mysql2TestCase
+ self.use_transactional_tests = false
+
+ def setup
+ @logger_was = ActiveRecord::Base.logger
+ @log = StringIO.new
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(@log)
+ ActiveRecord::Migration.verbose = false
+ end
+
+ def teardown
+ ActiveRecord::Base.logger = @logger_was
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::Base.connection.drop_table "mysql_table_options", if_exists: true
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ end
+
+ test "new migrations do not contain default ENGINE=InnoDB option" do
+ ActiveRecord::Base.connection.create_table "mysql_table_options", force: true
+
+ assert_no_match %r{ENGINE=InnoDB}, @log.string
+ end
+
+ test "legacy migrations contain default ENGINE=InnoDB option" do
+ migration = Class.new(ActiveRecord::Migration[5.1]) do
+ def migrate(x)
+ create_table "mysql_table_options", force: true
+ end
+ end.new
+
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ assert_match %r{ENGINE=InnoDB}, @log.string
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
index ac9a8d9dfb..f921515c10 100644
--- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb
@@ -13,6 +13,7 @@ module ActiveRecord
setup do
@abort, Thread.abort_on_exception = Thread.abort_on_exception, false
+ Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception if Thread.respond_to?(:report_on_exception)
@connection = ActiveRecord::Base.connection
@connection.clear_cache!
@@ -31,6 +32,7 @@ module ActiveRecord
@connection.drop_table "samples", if_exists: true
Thread.abort_on_exception = @abort
+ Thread.report_on_exception = @original_report_on_exception if Thread.respond_to?(:report_on_exception)
end
test "raises Deadlocked when a deadlock is encountered" do
@@ -60,8 +62,8 @@ module ActiveRecord
end
end
- test "raises TransactionTimeout when lock wait timeout exceeded" do
- assert_raises(ActiveRecord::TransactionTimeout) do
+ test "raises LockWaitTimeout when lock wait timeout exceeded" do
+ assert_raises(ActiveRecord::LockWaitTimeout) do
s = Sample.create!(value: 1)
latch1 = Concurrent::CountDownLatch.new
latch2 = Concurrent::CountDownLatch.new
@@ -87,5 +89,61 @@ module ActiveRecord
end
end
end
+
+ test "raises StatementTimeout when statement timeout exceeded" do
+ skip unless ActiveRecord::Base.connection.show_variable("max_execution_time")
+ assert_raises(ActiveRecord::StatementTimeout) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET max_execution_time = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET max_execution_time = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
+ test "raises QueryCanceled when canceling statement due to user request" do
+ assert_raises(ActiveRecord::QueryCanceled) do
+ s = Sample.create!(value: 1)
+ latch = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch.count_down
+ sleep(0.5)
+ conn = Sample.connection
+ pid = conn.query_value("SELECT id FROM information_schema.processlist WHERE info LIKE '% FOR UPDATE'")
+ conn.execute("KILL QUERY #{pid}")
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch.wait
+ Sample.lock.find(s.id)
+ end
+ ensure
+ thread.join
+ end
+ end
+ end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
index b01f5d7f5a..97da96003d 100644
--- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb
@@ -54,7 +54,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase
end
@connection.columns("unsigned_types").select { |c| /^unsigned_/.match?(c.name) }.each do |column|
- assert column.unsigned?
+ assert_predicate column, :unsigned?
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 9929237546..308ad1d854 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -59,6 +59,15 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase
assert_equal expected, add_index(:people, "lower(last_name)", using: type, unique: true)
end
+ expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name" bpchar_pattern_ops))
+ assert_equal expected, add_index(:people, :last_name, using: :gist, opclass: { last_name: :bpchar_pattern_ops })
+
+ expected = %(CREATE INDEX "index_people_on_last_name_and_first_name" ON "people" ("last_name" DESC NULLS LAST, "first_name" ASC))
+ assert_equal expected, add_index(:people, [:last_name, :first_name], order: { last_name: "DESC NULLS LAST", first_name: :asc })
+
+ expected = %(CREATE INDEX "index_people_on_last_name" ON "people" ("last_name" NULLS FIRST))
+ assert_equal expected, add_index(:people, :last_name, order: "NULLS FIRST")
+
assert_raise ArgumentError do
add_index(:people, :last_name, algorithm: :copy)
end
diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb
index 0e9e86f425..58fa7532a2 100644
--- a/activerecord/test/cases/adapters/postgresql/array_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/array_test.rb
@@ -39,12 +39,12 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
def test_column
assert_equal :string, @column.type
assert_equal "character varying(255)", @column.sql_type
- assert @column.array?
- assert_not @type.binary?
+ assert_predicate @column, :array?
+ assert_not_predicate @type, :binary?
ratings_column = PgArray.columns_hash["ratings"]
assert_equal :integer, ratings_column.type
- assert ratings_column.array?
+ assert_predicate ratings_column, :array?
end
def test_not_compatible_with_serialize_array
@@ -109,7 +109,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
assert_equal :text, column.type
assert_equal [], PgArray.column_defaults["snippets"]
- assert column.array?
+ assert_predicate column, :array?
end
def test_change_column_cant_make_non_array_column_to_array
@@ -228,7 +228,9 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
def test_insert_fixtures
tag_values = ["val1", "val2", "val3_with_'_multiple_quote_'_chars"]
- @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays")
+ assert_deprecated do
+ @connection.insert_fixtures([{ "tags" => tag_values }], "pg_arrays")
+ end
assert_equal(PgArray.last.tags, tag_values)
end
@@ -255,7 +257,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
x = PgArray.create!(tags: tags)
x.reload
- refute x.changed?
+ assert_not_predicate x, :changed?
end
def test_quoting_non_standard_delimiters
@@ -277,7 +279,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
x.reload
assert_equal %w(one two three), x.tags
- assert_not x.changed?
+ assert_not_predicate x, :changed?
end
def test_mutate_value_in_array
@@ -288,7 +290,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase
x.reload
assert_equal [{ "a" => "c" }, { "b" => "b" }], x.hstores
- assert_not x.changed?
+ assert_not_predicate x, :changed?
end
def test_datetime_with_timezone_awareness
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index df04299569..c8e728bbb6 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -29,20 +29,20 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlBitString.columns_hash["a_bit"]
assert_equal :bit, column.type
assert_equal "bit(8)", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlBitString.type_for_attribute("a_bit")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_bit_string_varying_column
column = PostgresqlBitString.columns_hash["a_bit_varying"]
assert_equal :bit_varying, column.type
assert_equal "bit varying(4)", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlBitString.type_for_attribute("a_bit_varying")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_default
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index a6bee113ff..64bb6906cd 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -75,7 +75,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
def test_write_value
data = "\u001F"
record = ByteaDataType.create(payload: data)
- assert_not record.new_record?
+ assert_not_predicate record, :new_record?
assert_equal(data, record.payload)
end
@@ -101,14 +101,14 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
data = File.read(File.join(__dir__, "..", "..", "..", "assets", "example.log"))
assert(data.size > 1)
record = ByteaDataType.create(payload: data)
- assert_not record.new_record?
+ assert_not_predicate record, :new_record?
assert_equal(data, record.payload)
assert_equal(data, ByteaDataType.where(id: record.id).first.payload)
end
def test_write_nil
record = ByteaDataType.create(payload: nil)
- assert_not record.new_record?
+ assert_not_predicate record, :new_record?
assert_nil(record.payload)
assert_nil(ByteaDataType.where(id: record.id).first.payload)
end
diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
index adf461a9cc..6dba4f3e14 100644
--- a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb
@@ -33,7 +33,7 @@ module ActiveRecord
connection.change_column :strings, :somedate, :timestamp, array: true, cast_as: :timestamp
column = connection.columns(:strings).find { |c| c.name == "somedate" }
assert_equal :datetime, column.type
- assert column.array?
+ assert_predicate column, :array?
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb
index a25f102bad..9eb0b7d99c 100644
--- a/activerecord/test/cases/adapters/postgresql/citext_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb
@@ -32,10 +32,10 @@ class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase
column = Citext.columns_hash["cival"]
assert_equal :citext, column.type
assert_equal "citext", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = Citext.type_for_attribute("cival")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_change_table_supports_json
diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb
index 5da95f7e2c..b0ce2694a3 100644
--- a/activerecord/test/cases/adapters/postgresql/composite_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb
@@ -51,10 +51,10 @@ class PostgresqlCompositeTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlComposite.columns_hash["address"]
assert_nil column.type
assert_equal "full_address", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlComposite.type_for_attribute("address")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_composite_mapping
@@ -113,10 +113,10 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlComposite.columns_hash["address"]
assert_equal :full_address, column.type
assert_equal "full_address", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlComposite.type_for_attribute("address")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_composite_mapping
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 81358b8fc4..d1b3c434e1 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -157,7 +157,7 @@ module ActiveRecord
original_connection_pid = @connection.query("select pg_backend_pid()")
# Sanity check.
- assert @connection.active?
+ assert_predicate @connection, :active?
if @connection.send(:postgresql_version) >= 90200
secondary_connection = ActiveRecord::Base.connection_pool.checkout
@@ -176,7 +176,7 @@ module ActiveRecord
@connection.verify!
- assert @connection.active?
+ assert_predicate @connection, :active?
# If we get no exception here, then either we re-connected successfully, or
# we never actually got disconnected.
diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb
index 9c3817e2ad..eeaad94c27 100644
--- a/activerecord/test/cases/adapters/postgresql/domain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb
@@ -30,10 +30,10 @@ class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlDomain.columns_hash["price"]
assert_equal :decimal, column.type
assert_equal "custom_money", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlDomain.type_for_attribute("price")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_domain_acts_like_basetype
@@ -44,6 +44,6 @@ class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase
record.price = "34.15"
record.save!
- assert_equal BigDecimal.new("34.15"), record.reload.price
+ assert_equal BigDecimal("34.15"), record.reload.price
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb
index 3d3cbe11a3..6789ff63e7 100644
--- a/activerecord/test/cases/adapters/postgresql/enum_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb
@@ -32,10 +32,10 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlEnum.columns_hash["current_mood"]
assert_equal :enum, column.type
assert_equal "mood", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlEnum.type_for_attribute("current_mood")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_enum_defaults
@@ -73,7 +73,7 @@ class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase
@connection.execute "INSERT INTO postgresql_enums VALUES (1, 'sad');"
stderr_output = capture(:stderr) { PostgresqlEnum.first }
- assert stderr_output.blank?
+ assert_predicate stderr_output, :blank?
end
def test_enum_type_cast
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
index 16fec94ede..be525383e9 100644
--- a/activerecord/test/cases/adapters/postgresql/explain_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -1,22 +1,22 @@
# frozen_string_literal: true
require "cases/helper"
-require "models/developer"
-require "models/computer"
+require "models/author"
+require "models/post"
class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase
- fixtures :developers
+ fixtures :authors
def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
+ explain = Author.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
assert_match %(QUERY PLAN), explain
end
def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
+ explain = Author.where(id: 1).includes(:posts).explain
assert_match %(QUERY PLAN), explain
- assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
- assert_match %r(EXPLAIN for: SELECT "audit_logs"\.\* FROM "audit_logs" WHERE "audit_logs"\."developer_id" = (?:\$1 \[\["developer_id", 1\]\]|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\$1 \[\["id", 1\]\]|1)), explain
+ assert_match %r(EXPLAIN for: SELECT "posts"\.\* FROM "posts" WHERE "posts"\."author_id" = (?:\$1 \[\["author_id", 1\]\]|1)), explain
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb b/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb
new file mode 100644
index 0000000000..4fa315ad23
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/foreign_table_test.rb
@@ -0,0 +1,109 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/professor"
+
+if ActiveRecord::Base.connection.supports_foreign_tables?
+ class ForeignTableTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ class ForeignProfessor < ActiveRecord::Base
+ self.table_name = "foreign_professors"
+ end
+
+ class ForeignProfessorWithPk < ForeignProfessor
+ self.primary_key = "id"
+ end
+
+ def setup
+ @professor = Professor.create(name: "Nicola")
+
+ @connection = ActiveRecord::Base.connection
+ enable_extension!("postgres_fdw", @connection)
+
+ foreign_db_config = ARTest.connection_config["arunit2"]
+ @connection.execute <<-SQL
+ CREATE SERVER foreign_server
+ FOREIGN DATA WRAPPER postgres_fdw
+ OPTIONS (dbname '#{foreign_db_config["database"]}')
+ SQL
+
+ @connection.execute <<-SQL
+ CREATE USER MAPPING FOR CURRENT_USER
+ SERVER foreign_server
+ SQL
+
+ @connection.execute <<-SQL
+ CREATE FOREIGN TABLE foreign_professors (
+ id int,
+ name character varying NOT NULL
+ ) SERVER foreign_server OPTIONS (
+ table_name 'professors'
+ )
+ SQL
+ end
+
+ def teardown
+ disable_extension!("postgres_fdw", @connection)
+ @connection.execute <<-SQL
+ DROP SERVER IF EXISTS foreign_server CASCADE
+ SQL
+ end
+
+ def test_table_exists
+ table_name = ForeignProfessor.table_name
+ assert_not ActiveRecord::Base.connection.table_exists?(table_name)
+ end
+
+ def test_foreign_tables_are_valid_data_sources
+ table_name = ForeignProfessor.table_name
+ assert @connection.data_source_exists?(table_name), "'#{table_name}' should be a data source"
+ end
+
+ def test_foreign_tables
+ assert_equal ["foreign_professors"], @connection.foreign_tables
+ end
+
+ def test_foreign_table_exists
+ assert @connection.foreign_table_exists?("foreign_professors")
+ assert @connection.foreign_table_exists?(:foreign_professors)
+ assert_not @connection.foreign_table_exists?("nonexistingtable")
+ assert_not @connection.foreign_table_exists?("'")
+ assert_not @connection.foreign_table_exists?(nil)
+ end
+
+ def test_attribute_names
+ assert_equal ["id", "name"], ForeignProfessor.attribute_names
+ end
+
+ def test_attributes
+ professor = ForeignProfessorWithPk.find(@professor.id)
+ assert_equal @professor.attributes, professor.attributes
+ end
+
+ def test_does_not_have_a_primary_key
+ assert_nil ForeignProfessor.primary_key
+ end
+
+ def test_insert_record
+ # Explicit `id` here to avoid complex configurations to implicitly work with remote table
+ ForeignProfessorWithPk.create!(id: 100, name: "Leonardo")
+
+ professor = ForeignProfessorWithPk.last
+ assert_equal "Leonardo", professor.name
+ end
+
+ def test_update_record
+ professor = ForeignProfessorWithPk.find(@professor.id)
+ professor.name = "Albert"
+ professor.save!
+ professor.reload
+ assert_equal "Albert", professor.name
+ end
+
+ def test_delete_record
+ professor = ForeignProfessorWithPk.find(@professor.id)
+ assert_difference("ForeignProfessor.count", -1) { professor.destroy }
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
index c6f1e1727f..95dee3bf44 100644
--- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb
@@ -22,10 +22,10 @@ class PostgresqlFullTextTest < ActiveRecord::PostgreSQLTestCase
column = Tsvector.columns_hash["text_vector"]
assert_equal :tsvector, column.type
assert_equal "tsvector", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = Tsvector.type_for_attribute("text_vector")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_update_tsvector
diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
index e1ba00e07b..8c6f046553 100644
--- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb
@@ -39,10 +39,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlPoint.columns_hash["x"]
assert_equal :point, column.type
assert_equal "point", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlPoint.type_for_attribute("x")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_default
@@ -79,7 +79,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
p.reload
assert_equal ActiveRecord::Point.new(10.0, 25.0), p.x
- assert_not p.changed?
+ assert_not_predicate p, :changed?
end
def test_array_assignment
@@ -117,10 +117,10 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlPoint.columns_hash["legacy_x"]
assert_equal :point, column.type
assert_equal "point", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlPoint.type_for_attribute("legacy_x")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_legacy_default
@@ -157,7 +157,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase
p.reload
assert_equal [10.0, 25.0], p.legacy_x
- assert_not p.changed?
+ assert_not_predicate p, :changed?
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
index f09e34b5f2..4b061a9375 100644
--- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -40,7 +40,7 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
end
def test_hstore_included_in_extensions
- assert @connection.respond_to?(:extensions), "connection should have a list of extensions"
+ assert_respond_to @connection, :extensions
assert_includes @connection.extensions, "hstore", "extension list should include hstore"
end
@@ -58,9 +58,9 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
def test_column
assert_equal :hstore, @column.type
assert_equal "hstore", @column.sql_type
- assert_not @column.array?
+ assert_not_predicate @column, :array?
- assert_not @type.binary?
+ assert_not_predicate @type, :binary?
end
def test_default
@@ -165,7 +165,7 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
hstore.reload
assert_equal "four", hstore.settings["three"]
- assert_not hstore.changed?
+ assert_not_predicate hstore, :changed?
end
def test_dirty_from_user_equal
@@ -174,7 +174,7 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
hstore.settings = { "key" => "value", "alongkey" => "anything" }
assert_equal settings, hstore.settings
- refute hstore.changed?
+ assert_not_predicate hstore, :changed?
end
def test_hstore_dirty_from_database_equal
@@ -184,7 +184,7 @@ class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase
assert_equal settings, hstore.settings
hstore.settings = settings
- refute hstore.changed?
+ assert_not_predicate hstore, :changed?
end
def test_gen1
diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
index eca29f2892..8349ee6ee2 100644
--- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb
@@ -31,10 +31,10 @@ class PostgresqlLtreeTest < ActiveRecord::PostgreSQLTestCase
column = Ltree.columns_hash["path"]
assert_equal :ltree, column.type
assert_equal "ltree", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = Ltree.type_for_attribute("path")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_write
diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb
index 563f0bbfae..be3590e8dd 100644
--- a/activerecord/test/cases/adapters/postgresql/money_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/money_test.rb
@@ -26,15 +26,15 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
assert_equal :money, column.type
assert_equal "money", column.sql_type
assert_equal 2, column.scale
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlMoney.type_for_attribute("wealth")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_default
- assert_equal BigDecimal.new("150.55"), PostgresqlMoney.column_defaults["depth"]
- assert_equal BigDecimal.new("150.55"), PostgresqlMoney.new.depth
+ assert_equal BigDecimal("150.55"), PostgresqlMoney.column_defaults["depth"]
+ assert_equal BigDecimal("150.55"), PostgresqlMoney.new.depth
end
def test_money_values
@@ -65,7 +65,7 @@ class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase
money = PostgresqlMoney.create(wealth: "987.65".dup)
assert_equal 987.65, money.wealth
- new_value = BigDecimal.new("123.45")
+ new_value = BigDecimal("123.45")
money.wealth = new_value
money.save!
money.reload
diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb
index f461544a85..736570451b 100644
--- a/activerecord/test/cases/adapters/postgresql/network_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/network_test.rb
@@ -24,30 +24,30 @@ class PostgresqlNetworkTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlNetworkAddress.columns_hash["cidr_address"]
assert_equal :cidr, column.type
assert_equal "cidr", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlNetworkAddress.type_for_attribute("cidr_address")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_inet_column
column = PostgresqlNetworkAddress.columns_hash["inet_address"]
assert_equal :inet, column.type
assert_equal "inet", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlNetworkAddress.type_for_attribute("inet_address")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_macaddr_column
column = PostgresqlNetworkAddress.columns_hash["mac_address"]
assert_equal :macaddr, column.type
assert_equal "macaddr", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = PostgresqlNetworkAddress.type_for_attribute("mac_address")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_network_types
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index f199519d86..1951230c8a 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -248,12 +248,12 @@ module ActiveRecord
def test_index_with_opclass
with_example_table do
- @connection.add_index "ex", "data varchar_pattern_ops"
- index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
- assert_equal "data varchar_pattern_ops", index.columns
+ @connection.add_index "ex", "data", opclass: "varchar_pattern_ops"
+ index = @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data" }
+ assert_equal ["data"], index.columns
- @connection.remove_index "ex", "data varchar_pattern_ops"
- assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
+ @connection.remove_index "ex", "data"
+ assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data" }
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb
index a75fdef698..261c24634e 100644
--- a/activerecord/test/cases/adapters/postgresql/range_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/range_test.rb
@@ -134,10 +134,10 @@ _SQL
end
def test_numrange_values
- assert_equal BigDecimal.new("0.1")..BigDecimal.new("0.2"), @first_range.num_range
- assert_equal BigDecimal.new("0.1")...BigDecimal.new("0.2"), @second_range.num_range
- assert_equal BigDecimal.new("0.1")...BigDecimal.new("Infinity"), @third_range.num_range
- assert_equal BigDecimal.new("-Infinity")...BigDecimal.new("Infinity"), @fourth_range.num_range
+ assert_equal BigDecimal("0.1")..BigDecimal("0.2"), @first_range.num_range
+ assert_equal BigDecimal("0.1")...BigDecimal("0.2"), @second_range.num_range
+ assert_equal BigDecimal("0.1")...BigDecimal("Infinity"), @third_range.num_range
+ assert_equal BigDecimal("-Infinity")...BigDecimal("Infinity"), @fourth_range.num_range
assert_nil @empty_range.num_range
end
@@ -285,14 +285,14 @@ _SQL
def test_create_numrange
assert_equal_round_trip(@new_range, :num_range,
- BigDecimal.new("0.5")...BigDecimal.new("1"))
+ BigDecimal("0.5")...BigDecimal("1"))
end
def test_update_numrange
assert_equal_round_trip(@first_range, :num_range,
- BigDecimal.new("0.5")...BigDecimal.new("1"))
+ BigDecimal("0.5")...BigDecimal("1"))
assert_nil_round_trip(@first_range, :num_range,
- BigDecimal.new("0.5")...BigDecimal.new("0.5"))
+ BigDecimal("0.5")...BigDecimal("0.5"))
end
def test_create_daterange
@@ -358,6 +358,18 @@ _SQL
end
end
+ def test_infinity_values
+ PostgresqlRange.create!(int4_range: 1..Float::INFINITY,
+ int8_range: -Float::INFINITY..0,
+ float_range: -Float::INFINITY..Float::INFINITY)
+
+ record = PostgresqlRange.first
+
+ assert_equal(1...Float::INFINITY, record.int4_range)
+ assert_equal(-Float::INFINITY...1, record.int8_range)
+ assert_equal(-Float::INFINITY...Float::INFINITY, record.float_range)
+ end
+
private
def assert_equal_round_trip(range, attribute, value)
round_trip(range, attribute, value)
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 5a64da028b..7ad03c194f 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -459,7 +459,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
assert_equal :btree, index_d.using
assert_equal :gin, index_e.using
- assert_equal :desc, index_d.orders[INDEX_D_COLUMN]
+ assert_equal :desc, index_d.orders
end
end
@@ -500,6 +500,66 @@ class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase
end
end
+class SchemaIndexOpclassTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table "trains" do |t|
+ t.string :name
+ t.text :description
+ end
+ end
+
+ teardown do
+ @connection.drop_table "trains", if_exists: true
+ end
+
+ def test_string_opclass_is_dumped
+ @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name text_pattern_ops, description text_pattern_ops)"
+
+ output = dump_table_schema "trains"
+
+ assert_match(/opclass: :text_pattern_ops/, output)
+ end
+
+ def test_non_default_opclass_is_dumped
+ @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name, description text_pattern_ops)"
+
+ output = dump_table_schema "trains"
+
+ assert_match(/opclass: \{ description: :text_pattern_ops \}/, output)
+ end
+end
+
+class SchemaIndexNullsOrderTest < ActiveRecord::PostgreSQLTestCase
+ include SchemaDumpingHelper
+
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table "trains" do |t|
+ t.string :name
+ t.text :description
+ end
+ end
+
+ teardown do
+ @connection.drop_table "trains", if_exists: true
+ end
+
+ def test_nulls_order_is_dumped
+ @connection.execute "CREATE INDEX trains_name_and_description ON trains USING btree(name NULLS FIRST, description)"
+ output = dump_table_schema "trains"
+ assert_match(/order: \{ name: "NULLS FIRST" \}/, output)
+ end
+
+ def test_non_default_order_with_nulls_is_dumped
+ @connection.execute "CREATE INDEX trains_name_and_desc ON trains USING btree(name DESC NULLS LAST, description)"
+ output = dump_table_schema "trains"
+ assert_match(/order: \{ name: "DESC NULLS LAST" \}/, output)
+ end
+end
+
class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCase
setup do
@connection = ActiveRecord::Base.connection
@@ -534,7 +594,7 @@ class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCa
end
def test_decimal_defaults_in_new_schema_when_overriding_domain
- assert_equal BigDecimal.new("3.14159265358979323846"), Default.new.decimal_col, "Default of decimal column was not correctly parsed"
+ assert_equal BigDecimal("3.14159265358979323846"), Default.new.decimal_col, "Default of decimal column was not correctly parsed"
end
def test_bpchar_defaults_in_new_schema_when_overriding_domain
diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb
index 6a99323be5..83ea86be6d 100644
--- a/activerecord/test/cases/adapters/postgresql/serial_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb
@@ -24,14 +24,14 @@ class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlSerial.columns_hash["seq"]
assert_equal :integer, column.type
assert_equal "integer", column.sql_type
- assert column.serial?
+ assert_predicate column, :serial?
end
def test_not_serial_column
column = PostgresqlSerial.columns_hash["serials_id"]
assert_equal :integer, column.type
assert_equal "integer", column.sql_type
- assert_not column.serial?
+ assert_not_predicate column, :serial?
end
def test_schema_dump_with_shorthand
@@ -66,14 +66,14 @@ class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase
column = PostgresqlBigSerial.columns_hash["seq"]
assert_equal :integer, column.type
assert_equal "bigint", column.sql_type
- assert column.serial?
+ assert_predicate column, :serial?
end
def test_not_bigserial_column
column = PostgresqlBigSerial.columns_hash["serials_id"]
assert_equal :integer, column.type
assert_equal "bigint", column.sql_type
- assert_not column.serial?
+ assert_not_predicate column, :serial?
end
def test_schema_dump_with_shorthand
@@ -111,7 +111,7 @@ module SequenceNameDetectionTestCases
columns = @connection.columns(:foo)
columns.each do |column|
assert_equal :integer, column.type
- assert column.serial?
+ assert_predicate column, :serial?
end
end
@@ -142,7 +142,7 @@ module SequenceNameDetectionTestCases
columns = @connection.columns(@table_name)
columns.each do |column|
assert_equal :integer, column.type
- assert column.serial?
+ assert_predicate column, :serial?
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
index b6aec8e993..9821b103df 100644
--- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb
@@ -14,6 +14,7 @@ module ActiveRecord
setup do
@abort, Thread.abort_on_exception = Thread.abort_on_exception, false
+ Thread.report_on_exception, @original_report_on_exception = false, Thread.report_on_exception if Thread.respond_to?(:report_on_exception)
@connection = ActiveRecord::Base.connection
@@ -31,6 +32,7 @@ module ActiveRecord
@connection.drop_table "samples", if_exists: true
Thread.abort_on_exception = @abort
+ Thread.report_on_exception = @original_report_on_exception if Thread.respond_to?(:report_on_exception)
end
test "raises SerializationFailure when a serialization failure occurs" do
@@ -91,9 +93,9 @@ module ActiveRecord
end
end
- test "raises TransactionTimeout when lock wait timeout exceeded" do
+ test "raises LockWaitTimeout when lock wait timeout exceeded" do
skip unless ActiveRecord::Base.connection.postgresql_version >= 90300
- assert_raises(ActiveRecord::TransactionTimeout) do
+ assert_raises(ActiveRecord::LockWaitTimeout) do
s = Sample.create!(value: 1)
latch1 = Concurrent::CountDownLatch.new
latch2 = Concurrent::CountDownLatch.new
@@ -120,6 +122,61 @@ module ActiveRecord
end
end
+ test "raises QueryCanceled when statement timeout exceeded" do
+ assert_raises(ActiveRecord::QueryCanceled) do
+ s = Sample.create!(value: 1)
+ latch1 = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch1.count_down
+ latch2.wait
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch1.wait
+ Sample.connection.execute("SET statement_timeout = 1")
+ Sample.lock.find(s.id)
+ end
+ ensure
+ Sample.connection.execute("SET statement_timeout = DEFAULT")
+ latch2.count_down
+ thread.join
+ end
+ end
+ end
+
+ test "raises QueryCanceled when canceling statement due to user request" do
+ assert_raises(ActiveRecord::QueryCanceled) do
+ s = Sample.create!(value: 1)
+ latch = Concurrent::CountDownLatch.new
+
+ thread = Thread.new do
+ Sample.transaction do
+ Sample.lock.find(s.id)
+ latch.count_down
+ sleep(0.5)
+ conn = Sample.connection
+ pid = conn.query_value("SELECT pid FROM pg_stat_activity WHERE query LIKE '% FOR UPDATE'")
+ conn.execute("SELECT pg_cancel_backend(#{pid})")
+ end
+ end
+
+ begin
+ Sample.transaction do
+ latch.wait
+ Sample.lock.find(s.id)
+ end
+ ensure
+ thread.join
+ end
+ end
+ end
+
private
def with_warning_suppression
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index c24e0cb330..71d07e2f4c 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -82,7 +82,7 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
UUIDType.reset_column_information
column = UUIDType.columns_hash["thingy"]
- assert column.array?
+ assert_predicate column, :array?
assert_equal "{}", column.default
schema = dump_table_schema "uuid_data_type"
@@ -93,10 +93,10 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
column = UUIDType.columns_hash["guid"]
assert_equal :uuid, column.type
assert_equal "uuid", column.sql_type
- assert_not column.array?
+ assert_not_predicate column, :array?
type = UUIDType.type_for_attribute("guid")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
end
def test_treat_blank_uuid_as_nil
@@ -178,7 +178,7 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase
duplicate = klass.new(guid: record.guid)
assert record.guid.present? # Ensure we actually are testing a UUID
- assert_not duplicate.valid?
+ assert_not_predicate duplicate, :valid?
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
index 3b081d34e1..b6d2ccdb53 100644
--- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -1,23 +1,23 @@
# frozen_string_literal: true
require "cases/helper"
-require "models/developer"
-require "models/computer"
+require "models/author"
+require "models/post"
class SQLite3ExplainTest < ActiveRecord::SQLite3TestCase
- fixtures :developers
+ fixtures :authors
def test_explain_for_one_query
- explain = Developer.where(id: 1).explain
- assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ explain = Author.where(id: 1).explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
+ assert_match(/(SEARCH )?TABLE authors USING (INTEGER )?PRIMARY KEY/, explain)
end
def test_explain_with_eager_loading
- explain = Developer.where(id: 1).includes(:audit_logs).explain
- assert_match %r(EXPLAIN for: SELECT "developers"\.\* FROM "developers" WHERE "developers"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
- assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
- assert_match %r(EXPLAIN for: SELECT "audit_logs"\.\* FROM "audit_logs" WHERE "audit_logs"\."developer_id" = (?:\? \[\["developer_id", 1\]\]|1)), explain
- assert_match(/(SCAN )?TABLE audit_logs/, explain)
+ explain = Author.where(id: 1).includes(:posts).explain
+ assert_match %r(EXPLAIN for: SELECT "authors"\.\* FROM "authors" WHERE "authors"\."id" = (?:\? \[\["id", 1\]\]|1)), explain
+ assert_match(/(SEARCH )?TABLE authors USING (INTEGER )?PRIMARY KEY/, explain)
+ assert_match %r(EXPLAIN for: SELECT "posts"\.\* FROM "posts" WHERE "posts"\."author_id" = (?:\? \[\["author_id", 1\]\]|1)), explain
+ assert_match(/(SCAN )?TABLE posts/, explain)
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/json_test.rb b/activerecord/test/cases/adapters/sqlite3/json_test.rb
index 568a524058..6f247fcd22 100644
--- a/activerecord/test/cases/adapters/sqlite3/json_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/json_test.rb
@@ -9,8 +9,8 @@ class SQLite3JSONTest < ActiveRecord::SQLite3TestCase
def setup
super
@connection.create_table("json_data_type") do |t|
- t.column "payload", :json, default: {}
- t.column "settings", :json
+ t.json "payload", default: {}
+ t.json "settings"
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index de422fad23..6fdb353368 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -38,7 +38,7 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase
end
def test_type_cast_bigdecimal
- bd = BigDecimal.new "10.0"
+ bd = BigDecimal "10.0"
assert_equal bd.to_f, @conn.type_cast(bd)
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 1f057fe5c6..7e0ce3a28e 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -360,16 +360,126 @@ module ActiveRecord
end
end
+ class Barcode < ActiveRecord::Base
+ self.primary_key = "code"
+ end
+
+ def test_copy_table_with_existing_records_have_custom_primary_key
+ connection = Barcode.connection
+ connection.create_table(:barcodes, primary_key: "code", id: :string, limit: 42, force: true) do |t|
+ t.text :other_attr
+ end
+ code = "214fe0c2-dd47-46df-b53b-66090b3c1d40"
+ Barcode.create!(code: code, other_attr: "xxx")
+
+ connection.remove_column("barcodes", "other_attr")
+
+ assert_equal code, Barcode.first.id
+ ensure
+ Barcode.reset_column_information
+ end
+
+ def test_copy_table_with_composite_primary_keys
+ connection = Barcode.connection
+ connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t|
+ t.string :region
+ t.string :code
+ t.text :other_attr
+ end
+ region = "US"
+ code = "214fe0c2-dd47-46df-b53b-66090b3c1d40"
+ Barcode.create!(region: region, code: code, other_attr: "xxx")
+
+ connection.remove_column("barcodes", "other_attr")
+
+ assert_equal ["region", "code"], connection.primary_keys("barcodes")
+
+ barcode = Barcode.first
+ assert_equal region, barcode.region
+ assert_equal code, barcode.code
+ ensure
+ Barcode.reset_column_information
+ end
+
+ def test_custom_primary_key_in_create_table
+ connection = Barcode.connection
+ connection.create_table :barcodes, id: false, force: true do |t|
+ t.primary_key :id, :string
+ end
+
+ assert_equal "id", connection.primary_key("barcodes")
+
+ custom_pk = Barcode.columns_hash["id"]
+
+ assert_equal :string, custom_pk.type
+ assert_not custom_pk.null
+ ensure
+ Barcode.reset_column_information
+ end
+
+ def test_custom_primary_key_in_change_table
+ connection = Barcode.connection
+ connection.create_table :barcodes, id: false, force: true do |t|
+ t.integer :dummy
+ end
+ connection.change_table :barcodes do |t|
+ t.primary_key :id, :string
+ end
+
+ assert_equal "id", connection.primary_key("barcodes")
+
+ custom_pk = Barcode.columns_hash["id"]
+
+ assert_equal :string, custom_pk.type
+ assert_not custom_pk.null
+ ensure
+ Barcode.reset_column_information
+ end
+
+ def test_add_column_with_custom_primary_key
+ connection = Barcode.connection
+ connection.create_table :barcodes, id: false, force: true do |t|
+ t.integer :dummy
+ end
+ connection.add_column :barcodes, :id, :string, primary_key: true
+
+ assert_equal "id", connection.primary_key("barcodes")
+
+ custom_pk = Barcode.columns_hash["id"]
+
+ assert_equal :string, custom_pk.type
+ assert_not custom_pk.null
+ ensure
+ Barcode.reset_column_information
+ end
+
+ def test_remove_column_preserves_partial_indexes
+ connection = Barcode.connection
+ connection.create_table :barcodes, force: true do |t|
+ t.string :code
+ t.string :region
+ t.boolean :bool_attr
+
+ t.index :code, unique: true, where: :bool_attr, name: "partial"
+ end
+ connection.remove_column :barcodes, :region
+
+ index = connection.indexes("barcodes").find { |idx| idx.name == "partial" }
+ assert_equal "bool_attr", index.where
+ ensure
+ Barcode.reset_column_information
+ end
+
def test_supports_extensions
assert_not @conn.supports_extensions?, "does not support extensions"
end
def test_respond_to_enable_extension
- assert @conn.respond_to?(:enable_extension)
+ assert_respond_to @conn, :enable_extension
end
def test_respond_to_disable_extension
- assert @conn.respond_to?(:disable_extension)
+ assert_respond_to @conn, :disable_extension
end
def test_statement_closed
@@ -390,6 +500,10 @@ module ActiveRecord
end
end
+ def test_deprecate_valid_alter_table_type
+ assert_deprecated { @conn.valid_alter_table_type?(:string) }
+ end
+
private
def assert_logged(logs)
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 7f654ec6f6..fbdf2ada4b 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -27,7 +27,7 @@ class AggregationsTest < ActiveRecord::TestCase
def test_immutable_value_objects
customers(:david).balance = Money.new(100)
- assert_raise(RuntimeError) { customers(:david).balance.instance_eval { @amount = 20 } }
+ assert_raise(frozen_error_class) { customers(:david).balance.instance_eval { @amount = 20 } }
end
def test_inferred_mapping
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 83974f327e..140d7cbcae 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -48,7 +48,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" }
- assert_equal 7, ActiveRecord::Migrator::current_version
+ assert_equal 7, @connection.migration_context.current_version
end
def test_schema_define_w_table_name_prefix
@@ -64,7 +64,7 @@ class ActiveRecordSchemaTest < ActiveRecord::TestCase
t.column :flavor, :string
end
end
- assert_equal 7, ActiveRecord::Migrator::current_version
+ assert_equal 7, @connection.migration_context.current_version
ensure
ActiveRecord::Base.table_name_prefix = old_table_name_prefix
ActiveRecord::SchemaMigration.table_name = table_name
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 0f7a249bf3..9e6d94191b 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -79,7 +79,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
account = model.new
- assert account.valid?
+ assert_predicate account, :valid?
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
end
@@ -95,7 +95,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
account = model.new
- assert_not account.valid?
+ assert_not_predicate account, :valid?
assert_equal [{ error: :blank }], account.errors.details[:company]
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
@@ -112,7 +112,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
account = model.new
- assert_not account.valid?
+ assert_not_predicate account, :valid?
assert_equal [{ error: :blank }], account.errors.details[:company]
ensure
ActiveRecord::Base.belongs_to_required_by_default = original_value
@@ -246,14 +246,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key).first
- assert citibank_result.association(:firm_with_primary_key).loaded?
+ assert_predicate citibank_result.association(:firm_with_primary_key), :loaded?
end
def test_eager_loading_with_primary_key_as_symbol
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.all.merge!(where: { name: "Citibank" }, includes: :firm_with_primary_key_symbols).first
- assert citibank_result.association(:firm_with_primary_key_symbols).loaded?
+ assert_predicate citibank_result.association(:firm_with_primary_key_symbols), :loaded?
end
def test_creating_the_belonging_object
@@ -320,7 +320,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
client = Client.create!(name: "Jimmy")
account = client.create_account!(credit_limit: 10)
assert_equal account, client.account
- assert account.persisted?
+ assert_predicate account, :persisted?
client.save
client.reload
assert_equal account, client.account
@@ -330,7 +330,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
client = Client.create!(name: "Jimmy")
assert_raise(ActiveRecord::RecordInvalid) { client.create_account! }
assert_not_nil client.account
- assert client.account.new_record?
+ assert_predicate client.account, :new_record?
end
def test_reloading_the_belonging_object
@@ -627,10 +627,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
final_cut = Client.new("name" => "Final Cut")
firm = Firm.find(1)
final_cut.firm = firm
- assert !final_cut.persisted?
+ assert_not_predicate final_cut, :persisted?
assert final_cut.save
- assert final_cut.persisted?
- assert firm.persisted?
+ assert_predicate final_cut, :persisted?
+ assert_predicate firm, :persisted?
assert_equal firm, final_cut.firm
final_cut.association(:firm).reload
assert_equal firm, final_cut.firm
@@ -640,10 +640,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
final_cut = Client.new("name" => "Final Cut")
firm = Firm.find(1)
final_cut.firm_with_primary_key = firm
- assert !final_cut.persisted?
+ assert_not_predicate final_cut, :persisted?
assert final_cut.save
- assert final_cut.persisted?
- assert firm.persisted?
+ assert_predicate final_cut, :persisted?
+ assert_predicate firm, :persisted?
assert_equal firm, final_cut.firm_with_primary_key
final_cut.association(:firm_with_primary_key).reload
assert_equal firm, final_cut.firm_with_primary_key
@@ -790,7 +790,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_cant_save_readonly_association
assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_client).readonly_firm.save! }
- assert companies(:first_client).readonly_firm.readonly?
+ assert_predicate companies(:first_client).readonly_firm, :readonly?
end
def test_polymorphic_assignment_foreign_key_type_string
@@ -949,15 +949,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
firm_proxy = client.send(:association_instance_get, :firm)
firm_with_condition_proxy = client.send(:association_instance_get, :firm_with_condition)
- assert !firm_proxy.stale_target?
- assert !firm_with_condition_proxy.stale_target?
+ assert_not_predicate firm_proxy, :stale_target?
+ assert_not_predicate firm_with_condition_proxy, :stale_target?
assert_equal companies(:first_firm), client.firm
assert_equal companies(:first_firm), client.firm_with_condition
client.client_of = companies(:another_firm).id
- assert firm_proxy.stale_target?
- assert firm_with_condition_proxy.stale_target?
+ assert_predicate firm_proxy, :stale_target?
+ assert_predicate firm_with_condition_proxy, :stale_target?
assert_equal companies(:another_firm), client.firm
assert_equal companies(:another_firm), client.firm_with_condition
end
@@ -968,12 +968,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable
proxy = sponsor.send(:association_instance_get, :sponsorable)
- assert !proxy.stale_target?
+ assert_not_predicate proxy, :stale_target?
assert_equal members(:groucho), sponsor.sponsorable
sponsor.sponsorable_id = members(:some_other_guy).id
- assert proxy.stale_target?
+ assert_predicate proxy, :stale_target?
assert_equal members(:some_other_guy), sponsor.sponsorable
end
@@ -983,12 +983,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable
proxy = sponsor.send(:association_instance_get, :sponsorable)
- assert !proxy.stale_target?
+ assert_not_predicate proxy, :stale_target?
assert_equal members(:groucho), sponsor.sponsorable
sponsor.sponsorable_type = "Firm"
- assert proxy.stale_target?
+ assert_predicate proxy, :stale_target?
assert_equal companies(:first_firm), sponsor.sponsorable
end
@@ -1122,7 +1122,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
comment.post_id = 9223372036854775808 # out of range in the bigint
assert_nil comment.post
- assert_not comment.valid?
+ assert_not_predicate comment, :valid?
assert_equal [{ error: :blank }], comment.errors.details[:post]
end
@@ -1142,7 +1142,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
citibank.firm_id = apple.id.to_s
- assert !citibank.association(:firm).stale_target?
+ assert_not_predicate citibank.association(:firm), :stale_target?
end
def test_reflect_the_most_recent_change
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index e096cd4a0b..25d55dc4c9 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -15,7 +15,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
@david = authors(:david)
@thinking = posts(:thinking)
@authorless = posts(:authorless)
- assert @david.post_log.empty?
+ assert_empty @david.post_log
end
def test_adding_macro_callbacks
@@ -96,7 +96,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
def test_has_and_belongs_to_many_add_callback
david = developers(:david)
ar = projects(:active_record)
- assert ar.developers_log.empty?
+ assert_empty ar.developers_log
ar.developers_with_callbacks << david
assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log
ar.developers_with_callbacks << david
@@ -122,12 +122,12 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
assert_equal alice, dev
assert_not_nil new_dev
assert new_dev, "record should not have been saved"
- assert_not alice.new_record?
+ assert_not_predicate alice, :new_record?
end
def test_has_and_belongs_to_many_after_add_called_after_save
ar = projects(:active_record)
- assert ar.developers_log.empty?
+ assert_empty ar.developers_log
alice = Developer.new(name: "alice")
ar.developers_with_callbacks << alice
assert_equal "after_adding#{alice.id}", ar.developers_log.last
@@ -143,7 +143,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
david = developers(:david)
jamis = developers(:jamis)
activerecord = projects(:active_record)
- assert activerecord.developers_log.empty?
+ assert_empty activerecord.developers_log
activerecord.developers_with_callbacks.delete(david)
assert_equal ["before_removing#{david.id}", "after_removing#{david.id}"], activerecord.developers_log
@@ -154,7 +154,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
def test_has_and_belongs_to_many_does_not_fire_callbacks_on_clear
activerecord = projects(:active_record)
- assert activerecord.developers_log.empty?
+ assert_empty activerecord.developers_log
if activerecord.developers_with_callbacks.size == 0
activerecord.developers << developers(:david)
activerecord.developers << developers(:jamis)
@@ -163,7 +163,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
end
activerecord.developers_with_callbacks.flat_map { |d| ["before_removing#{d.id}", "after_removing#{d.id}"] }.sort
assert activerecord.developers_with_callbacks.clear
- assert_predicate activerecord.developers_log, :empty?
+ assert_empty activerecord.developers_log
end
def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent
@@ -183,7 +183,7 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
@david.unchangeable_posts << @authorless
rescue Exception
end
- assert @david.post_log.empty?
+ assert_empty @david.post_log
assert_not_includes @david.unchangeable_posts, @authorless
@david.reload
assert_not_includes @david.unchangeable_posts, @authorless
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 829e12fbc8..e717621928 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -136,7 +136,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_multiple_stis_and_order
- author = Author.all.merge!(includes: { posts: [ :special_comments , :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first
+ author = Author.all.merge!(includes: { posts: [ :special_comments, :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first
assert_equal authors(:david), author
assert_no_queries do
author.posts.first.special_comments
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 9afe6a893c..cb07ca5cd3 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -284,7 +284,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_loading_from_an_association_that_has_a_hash_of_conditions
- assert !Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty?
+ assert_not_empty Author.all.merge!(includes: :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts
end
def test_loading_with_no_associations
@@ -427,7 +427,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name
quoted_posts_id = Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id")
assert_nothing_raised do
- Comment.includes(:post).references(:posts).order(quoted_posts_id)
+ Comment.includes(:post).references(:posts).order(Arel.sql(quoted_posts_id))
end
end
@@ -787,7 +787,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
Tagging.create!(taggable_type: "Post", taggable_id: post2.id, tag: tag)
tag_with_includes = OrderedTag.includes(:tagged_posts).find(tag.id)
- assert_equal(tag_with_includes.taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title))
+ assert_equal tag_with_includes.ordered_taggings.map(&:taggable).map(&:title), tag_with_includes.tagged_posts.map(&:title)
end
def test_eager_has_many_through_multiple_with_order
@@ -874,14 +874,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts(:thinking, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: "UPPER(posts.title)", limit: 2, offset: 1
+ order: Arel.sql("UPPER(posts.title)"), limit: 2, offset: 1
).to_a
)
assert_equal(
posts(:sti_post_and_comments, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: "UPPER(posts.title) DESC", limit: 2, offset: 1
+ order: Arel.sql("UPPER(posts.title) DESC"), limit: 2, offset: 1
).to_a
)
end
@@ -891,14 +891,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
posts(:thinking, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: ["UPPER(posts.title)", "posts.id"], limit: 2, offset: 1
+ order: [Arel.sql("UPPER(posts.title)"), "posts.id"], limit: 2, offset: 1
).to_a
)
assert_equal(
posts(:sti_post_and_comments, :sti_comments),
Post.all.merge!(
includes: [:author, :comments], where: { "authors.name" => "David" },
- order: ["UPPER(posts.title) DESC", "posts.id"], limit: 2, offset: 1
+ order: [Arel.sql("UPPER(posts.title) DESC"), "posts.id"], limit: 2, offset: 1
).to_a
)
end
@@ -1073,7 +1073,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_load_with_sti_sharing_association
- assert_queries(2) do #should not do 1 query per subclass
+ assert_queries(2) do # should not do 1 query per subclass
Comment.includes(:post).to_a
end
end
@@ -1444,51 +1444,51 @@ class EagerAssociationTest < ActiveRecord::TestCase
test "preloading readonly association" do
# has-one
firm = Firm.where(id: "1").preload(:readonly_account).first!
- assert firm.readonly_account.readonly?
+ assert_predicate firm.readonly_account, :readonly?
# has_and_belongs_to_many
project = Project.where(id: "2").preload(:readonly_developers).first!
- assert project.readonly_developers.first.readonly?
+ assert_predicate project.readonly_developers.first, :readonly?
# has-many :through
david = Author.where(id: "1").preload(:readonly_comments).first!
- assert david.readonly_comments.first.readonly?
+ assert_predicate david.readonly_comments.first, :readonly?
end
test "eager-loading non-readonly association" do
# has_one
firm = Firm.where(id: "1").eager_load(:account).first!
- assert_not firm.account.readonly?
+ assert_not_predicate firm.account, :readonly?
# has_and_belongs_to_many
project = Project.where(id: "2").eager_load(:developers).first!
- assert_not project.developers.first.readonly?
+ assert_not_predicate project.developers.first, :readonly?
# has_many :through
david = Author.where(id: "1").eager_load(:comments).first!
- assert_not david.comments.first.readonly?
+ assert_not_predicate david.comments.first, :readonly?
# belongs_to
post = Post.where(id: "1").eager_load(:author).first!
- assert_not post.author.readonly?
+ assert_not_predicate post.author, :readonly?
end
test "eager-loading readonly association" do
# has-one
firm = Firm.where(id: "1").eager_load(:readonly_account).first!
- assert firm.readonly_account.readonly?
+ assert_predicate firm.readonly_account, :readonly?
# has_and_belongs_to_many
project = Project.where(id: "2").eager_load(:readonly_developers).first!
- assert project.readonly_developers.first.readonly?
+ assert_predicate project.readonly_developers.first, :readonly?
# has-many :through
david = Author.where(id: "1").eager_load(:readonly_comments).first!
- assert david.readonly_comments.first.readonly?
+ assert_predicate david.readonly_comments.first, :readonly?
# belongs_to
post = Post.where(id: "1").eager_load(:readonly_author).first!
- assert post.readonly_author.readonly?
+ assert_predicate post.readonly_author, :readonly?
end
test "preloading a polymorphic association with references to the associated table" do
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 c817d7267b..d9e05cf7e2 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
@@ -180,11 +180,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_has_and_belongs_to_many
david = Developer.find(1)
- assert !david.projects.empty?
+ assert_not_empty david.projects
assert_equal 2, david.projects.size
active_record = Project.find(1)
- assert !active_record.developers.empty?
+ assert_not_empty active_record.developers
assert_equal 3, active_record.developers.size
assert_includes active_record.developers, david
end
@@ -262,10 +262,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
no_of_projects = Project.count
aredridel = Developer.new("name" => "Aredridel")
aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")])
- assert !aredridel.persisted?
- assert !p.persisted?
+ assert_not_predicate aredridel, :persisted?
+ assert_not_predicate p, :persisted?
assert aredridel.save
- assert aredridel.persisted?
+ assert_predicate aredridel, :persisted?
assert_equal no_of_devels + 1, Developer.count
assert_equal no_of_projects + 1, Project.count
assert_equal 2, aredridel.projects.size
@@ -311,14 +311,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_build
devel = Developer.find(1)
proj = assert_no_queries(ignore_none: false) { devel.projects.build("name" => "Projekt") }
- assert !devel.projects.loaded?
+ assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
- assert devel.projects.loaded?
+ assert_predicate devel.projects, :loaded?
- assert !proj.persisted?
+ assert_not_predicate proj, :persisted?
devel.save
- assert proj.persisted?
+ assert_predicate proj, :persisted?
assert_equal devel.projects.last, proj
assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
end
@@ -326,14 +326,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
devel = Developer.find(1)
proj = assert_no_queries(ignore_none: false) { devel.projects.new("name" => "Projekt") }
- assert !devel.projects.loaded?
+ assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
- assert devel.projects.loaded?
+ assert_predicate devel.projects, :loaded?
- assert !proj.persisted?
+ assert_not_predicate proj, :persisted?
devel.save
- assert proj.persisted?
+ assert_predicate proj, :persisted?
assert_equal devel.projects.last, proj
assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
end
@@ -343,10 +343,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
devel.projects.build(name: "Make bed")
proj2 = devel.projects.build(name: "Lie in it")
assert_equal devel.projects.last, proj2
- assert !proj2.persisted?
+ assert_not_predicate proj2, :persisted?
devel.save
- assert devel.persisted?
- assert proj2.persisted?
+ assert_predicate devel, :persisted?
+ assert_predicate proj2, :persisted?
assert_equal devel.projects.last, proj2
assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated
end
@@ -354,12 +354,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_create
devel = Developer.find(1)
proj = devel.projects.create("name" => "Projekt")
- assert !devel.projects.loaded?
+ assert_not_predicate devel.projects, :loaded?
assert_equal devel.projects.last, proj
- assert !devel.projects.loaded?
+ assert_not_predicate devel.projects, :loaded?
- assert proj.persisted?
+ assert_predicate proj, :persisted?
assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
end
@@ -367,14 +367,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
# in Oracle '' is saved as null therefore need to save ' ' in not null column
post = categories(:general).post_with_conditions.build(body: " ")
- assert post.save
- assert_equal "Yet Another Testing Title", post.title
+ assert post.save
+ assert_equal "Yet Another Testing Title", post.title
# in Oracle '' is saved as null therefore need to save ' ' in not null column
another_post = categories(:general).post_with_conditions.create(body: " ")
- assert another_post.persisted?
- assert_equal "Yet Another Testing Title", another_post.title
+ assert_predicate another_post, :persisted?
+ assert_equal "Yet Another Testing Title", another_post.title
end
def test_distinct_after_the_fact
@@ -441,10 +441,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_removing_associations_on_destroy
david = DeveloperWithBeforeDestroyRaise.find(1)
- assert !david.projects.empty?
+ assert_not_empty david.projects
david.destroy
- assert david.projects.empty?
- assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty?
+ assert_empty david.projects
+ assert_empty DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1")
end
def test_destroying
@@ -459,7 +459,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id} AND project_id = #{project.id}")
- assert join_records.empty?
+ assert_empty join_records
assert_equal 1, david.reload.projects.size
assert_equal 1, david.projects.reload.size
@@ -475,7 +475,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id}")
- assert join_records.empty?
+ assert_empty join_records
assert_equal 0, david.reload.projects.size
assert_equal 0, david.projects.reload.size
@@ -484,23 +484,23 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_destroy_all
david = Developer.find(1)
david.projects.reload
- assert !david.projects.empty?
+ assert_not_empty david.projects
assert_no_difference "Project.count" do
david.projects.destroy_all
end
join_records = Developer.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = #{david.id}")
- assert join_records.empty?
+ assert_empty join_records
- assert david.projects.empty?
- assert david.projects.reload.empty?
+ assert_empty david.projects
+ assert_empty david.projects.reload
end
def test_destroy_associations_destroys_multiple_associations
george = parrots(:george)
- assert !george.pirates.empty?
- assert !george.treasures.empty?
+ assert_not_empty george.pirates
+ assert_not_empty george.treasures
assert_no_difference "Pirate.count" do
assert_no_difference "Treasure.count" do
@@ -509,12 +509,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}")
- assert join_records.empty?
- assert george.pirates.reload.empty?
+ assert_empty join_records
+ assert_empty george.pirates.reload
join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}")
- assert join_records.empty?
- assert george.treasures.reload.empty?
+ assert_empty join_records
+ assert_empty george.treasures.reload
end
def test_associations_with_conditions
@@ -547,7 +547,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer = project.developers.first
assert_no_queries(ignore_none: false) do
- assert project.developers.loaded?
+ assert_predicate project.developers, :loaded?
assert_includes project.developers, developer
end
end
@@ -557,18 +557,18 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer = project.developers.first
project.reload
- assert ! project.developers.loaded?
+ assert_not_predicate project.developers, :loaded?
assert_queries(1) do
assert_includes project.developers, developer
end
- assert ! project.developers.loaded?
+ assert_not_predicate project.developers, :loaded?
end
def test_include_returns_false_for_non_matching_record_to_verify_scoping
project = projects(:active_record)
developer = Developer.create name: "Bryan", salary: 50_000
- assert ! project.developers.loaded?
+ assert_not_predicate project.developers, :loaded?
assert ! project.developers.include?(developer)
end
@@ -763,9 +763,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_unloaded_associations_does_not_load_them
developer = developers(:david)
- assert !developer.projects.loaded?
+ assert_not_predicate developer.projects, :loaded?
assert_equal projects(:active_record, :action_controller).map(&:id).sort, developer.project_ids.sort
- assert !developer.projects.loaded?
+ assert_not_predicate developer.projects, :loaded?
end
def test_assign_ids
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 4ca11af791..9ee42cef0f 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -57,7 +57,7 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
def test_custom_primary_key_on_new_record_should_fetch_with_query
subscriber = Subscriber.new(nick: "webster132")
- assert !subscriber.subscriptions.loaded?
+ assert_not_predicate subscriber.subscriptions, :loaded?
assert_queries 1 do
assert_equal 2, subscriber.subscriptions.size
@@ -68,7 +68,7 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
def test_association_primary_key_on_new_record_should_fetch_with_query
author = Author.new(name: "David")
- assert !author.essays.loaded?
+ assert_not_predicate author.essays, :loaded?
assert_queries 1 do
assert_equal 1, author.essays.size
@@ -103,7 +103,7 @@ class HasManyAssociationsTestPrimaryKeys < ActiveRecord::TestCase
def test_blank_custom_primary_key_on_new_record_should_not_run_queries
author = Author.new
- assert !author.essays.loaded?
+ assert_not_predicate author.essays, :loaded?
assert_queries 0 do
assert_equal 0, author.essays.size
@@ -201,7 +201,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
part.reload
assert_nil part.ship
- assert !part.updated_at_changed?
+ assert_not_predicate part, :updated_at_changed?
end
def test_create_from_association_should_respect_default_scope
@@ -444,7 +444,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
new_clients << company.clients_of_firm.build(name: "Another Client III")
end
- assert_not company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
assert_queries(1) do
assert_same new_clients[0], company.clients_of_firm.third
assert_same new_clients[1], company.clients_of_firm.fourth
@@ -464,7 +464,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
new_clients << company.clients_of_firm.build(name: "Another Client III")
end
- assert_not company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
assert_queries(1) do
assert_same new_clients[0], company.clients_of_firm.third!
assert_same new_clients[1], company.clients_of_firm.fourth!
@@ -587,14 +587,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# taking from unloaded Relation
bob = klass.find(authors(:bob).id)
new_post = bob.posts.build
- assert_not bob.posts.loaded?
+ assert_not_predicate bob.posts, :loaded?
assert_equal [posts(:misc_by_bob)], bob.posts.take(1)
assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2)
assert_equal [posts(:misc_by_bob), posts(:other_by_bob), new_post], bob.posts.take(3)
# taking from loaded Relation
bob.posts.load
- assert bob.posts.loaded?
+ assert_predicate bob.posts, :loaded?
assert_equal [posts(:misc_by_bob)], bob.posts.take(1)
assert_equal [posts(:misc_by_bob), posts(:other_by_bob)], bob.posts.take(2)
assert_equal [posts(:misc_by_bob), posts(:other_by_bob), new_post], bob.posts.take(3)
@@ -713,13 +713,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_find_each
firm = companies(:first_firm)
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries(4) do
firm.clients.find_each(batch_size: 1) { |c| assert_equal firm.id, c.firm_id }
end
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_find_each_with_conditions
@@ -732,13 +732,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_find_in_batches
firm = companies(:first_firm)
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries(2) do
firm.clients.find_in_batches(batch_size: 2) do |clients|
@@ -746,7 +746,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_find_all_sanitized
@@ -955,20 +955,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_new_aliased_to_build
company = companies(:first_firm)
new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.new("name" => "Another Client") }
- assert !company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
- assert !new_client.persisted?
+ assert_not_predicate new_client, :persisted?
assert_equal new_client, company.clients_of_firm.last
end
def test_build
company = companies(:first_firm)
new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
- assert !company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
- assert !new_client.persisted?
+ assert_not_predicate new_client, :persisted?
assert_equal new_client, company.clients_of_firm.last
end
@@ -982,9 +982,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_collection_not_empty_after_building
company = companies(:first_firm)
- assert_predicate company.contracts, :empty?
+ assert_empty company.contracts
company.contracts.build
- assert_not_predicate company.contracts, :empty?
+ assert_not_empty company.contracts
end
def test_collection_size_twice_for_regressions
@@ -1008,7 +1008,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_followed_by_save_does_not_load_target
companies(:first_firm).clients_of_firm.build("name" => "Another Client")
assert companies(:first_firm).save
- assert !companies(:first_firm).clients_of_firm.loaded?
+ assert_not_predicate companies(:first_firm).clients_of_firm, :loaded?
end
def test_build_without_loading_association
@@ -1028,10 +1028,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_build_via_block
company = companies(:first_firm)
new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
- assert !company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
assert_equal "Another Client", new_client.name
- assert !new_client.persisted?
+ assert_not_predicate new_client, :persisted?
assert_equal new_client, company.clients_of_firm.last
end
@@ -1069,7 +1069,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_predicate companies(:first_firm).clients_of_firm, :loaded?
new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
- assert new_client.persisted?
+ assert_predicate new_client, :persisted?
assert_equal new_client, companies(:first_firm).clients_of_firm.last
assert_equal new_client, companies(:first_firm).clients_of_firm.reload.last
end
@@ -1082,7 +1082,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create_followed_by_save_does_not_load_target
companies(:first_firm).clients_of_firm.create("name" => "Another Client")
assert companies(:first_firm).save
- assert !companies(:first_firm).clients_of_firm.loaded?
+ assert_not_predicate companies(:first_firm).clients_of_firm, :loaded?
end
def test_deleting
@@ -1108,7 +1108,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
# option is not given on the association.
ship = Ship.create(name: "Countless", treasures_count: 10)
- assert_not Ship.reflect_on_association(:treasures).has_cached_counter?
+ assert_not_predicate Ship.reflect_on_association(:treasures), :has_cached_counter?
# Count should come from sql count() of treasures rather than treasures_count attribute
assert_equal ship.treasures.size, 0
@@ -1199,7 +1199,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_empty_with_counter_cache
post = posts(:welcome)
assert_queries(0) do
- assert_not post.comments.empty?
+ assert_not_empty post.comments
end
end
@@ -1441,13 +1441,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_creation_respects_hash_condition
ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build
- assert ms_client.save
- assert_equal "Microsoft", ms_client.name
+ assert ms_client.save
+ assert_equal "Microsoft", ms_client.name
another_ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.create
- assert another_ms_client.persisted?
- assert_equal "Microsoft", another_ms_client.name
+ assert_predicate another_ms_client, :persisted?
+ assert_equal "Microsoft", another_ms_client.name
end
def test_clearing_without_initial_access
@@ -1570,7 +1570,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = companies(:first_firm)
assert_equal 3, firm.clients.size
firm.destroy
- assert Client.all.merge!(where: "firm_id=#{firm.id}").to_a.empty?
+ assert_empty Client.all.merge!(where: "firm_id=#{firm.id}").to_a
end
def test_dependence_for_associations_with_hash_condition
@@ -1633,7 +1633,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = RestrictedWithExceptionFirm.create!(name: "restrict")
firm.companies.create(name: "child")
- assert !firm.companies.empty?
+ assert_not_empty firm.companies
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
assert RestrictedWithExceptionFirm.exists?(name: "restrict")
assert firm.companies.exists?(name: "child")
@@ -1643,11 +1643,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = RestrictedWithErrorFirm.create!(name: "restrict")
firm.companies.create(name: "child")
- assert !firm.companies.empty?
+ assert_not_empty firm.companies
firm.destroy
- assert !firm.errors.empty?
+ assert_not_empty firm.errors
assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first
assert RestrictedWithErrorFirm.exists?(name: "restrict")
@@ -1660,11 +1660,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = RestrictedWithErrorFirm.create!(name: "restrict")
firm.companies.create(name: "child")
- assert !firm.companies.empty?
+ assert_not_empty firm.companies
firm.destroy
- assert !firm.errors.empty?
+ assert_not_empty firm.errors
assert_equal "Cannot delete record because dependent client companies exist", firm.errors[:base].first
assert RestrictedWithErrorFirm.exists?(name: "restrict")
@@ -1716,8 +1716,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
account = Account.new
orig_accounts = firm.accounts.to_a
- assert !account.valid?
- assert !orig_accounts.empty?
+ assert_not_predicate account, :valid?
+ assert_not_empty orig_accounts
error = assert_raise ActiveRecord::RecordNotSaved do
firm.accounts = [account]
end
@@ -1775,9 +1775,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_unloaded_associations_does_not_load_them
company = companies(:first_firm)
- assert !company.clients.loaded?
+ assert_not_predicate company.clients, :loaded?
assert_equal [companies(:first_client).id, companies(:second_client).id, companies(:another_first_firm_client).id], company.client_ids
- assert !company.clients.loaded?
+ assert_not_predicate company.clients, :loaded?
end
def test_counter_cache_on_unloaded_association
@@ -1859,7 +1859,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
client = firm.clients.first
assert_no_queries do
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
assert_equal true, firm.clients.include?(client)
end
end
@@ -1869,18 +1869,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
client = firm.clients.first
firm.reload
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries(1) do
assert_equal true, firm.clients.include?(client)
end
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_include_returns_false_for_non_matching_record_to_verify_scoping
firm = companies(:first_firm)
client = Client.create!(name: "Not Associated")
- assert ! firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_equal false, firm.clients.include?(client)
end
@@ -1889,13 +1889,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.first
firm.clients.second
firm.clients.last
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query
firm = companies(:first_firm)
firm.clients.load_target
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
assert_no_queries(ignore_none: false) do
firm.clients.first
@@ -1908,7 +1908,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_first_or_last_on_existing_record_with_build_should_load_association
firm = companies(:first_firm)
firm.clients.build(name: "Foo")
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries 1 do
firm.clients.first
@@ -1916,13 +1916,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.last
end
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
end
def test_calling_first_nth_or_last_on_existing_record_with_create_should_not_load_association
firm = companies(:first_firm)
firm.clients.create(name: "Foo")
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries 3 do
firm.clients.first
@@ -1930,7 +1930,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.last
end
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_first_nth_or_last_on_new_record_should_not_run_queries
@@ -1946,14 +1946,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_first_or_last_with_integer_on_association_should_not_load_association
firm = companies(:first_firm)
firm.clients.create(name: "Foo")
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
assert_queries 2 do
firm.clients.first(2)
firm.clients.last(2)
end
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_many_should_count_instead_of_loading_association
@@ -1961,7 +1961,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_queries(1) do
firm.clients.many? # use count query
end
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_many_on_loaded_association_should_not_use_query
@@ -1976,22 +1976,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.expects(:size).never
firm.clients.many? { true }
end
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
end
def test_calling_many_should_return_false_if_none_or_one
firm = companies(:another_firm)
- assert !firm.clients_like_ms.many?
+ assert_not_predicate firm.clients_like_ms, :many?
assert_equal 0, firm.clients_like_ms.size
firm = companies(:first_firm)
- assert !firm.limited_clients.many?
+ assert_not_predicate firm.limited_clients, :many?
assert_equal 1, firm.limited_clients.size
end
def test_calling_many_should_return_true_if_more_than_one
firm = companies(:first_firm)
- assert firm.clients.many?
+ assert_predicate firm.clients, :many?
assert_equal 3, firm.clients.size
end
@@ -2000,7 +2000,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_queries(1) do
firm.clients.none? # use count query
end
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_none_on_loaded_association_should_not_use_query
@@ -2015,18 +2015,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.expects(:size).never
firm.clients.none? { true }
end
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
end
def test_calling_none_should_return_true_if_none
firm = companies(:another_firm)
- assert firm.clients_like_ms.none?
+ assert_predicate firm.clients_like_ms, :none?
assert_equal 0, firm.clients_like_ms.size
end
def test_calling_none_should_return_false_if_any
firm = companies(:first_firm)
- assert !firm.limited_clients.none?
+ assert_not_predicate firm.limited_clients, :none?
assert_equal 1, firm.limited_clients.size
end
@@ -2035,7 +2035,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_queries(1) do
firm.clients.one? # use count query
end
- assert !firm.clients.loaded?
+ assert_not_predicate firm.clients, :loaded?
end
def test_calling_one_on_loaded_association_should_not_use_query
@@ -2050,24 +2050,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.clients.expects(:size).never
firm.clients.one? { true }
end
- assert firm.clients.loaded?
+ assert_predicate firm.clients, :loaded?
end
def test_calling_one_should_return_false_if_zero
firm = companies(:another_firm)
- assert ! firm.clients_like_ms.one?
+ assert_not_predicate firm.clients_like_ms, :one?
assert_equal 0, firm.clients_like_ms.size
end
def test_calling_one_should_return_true_if_one
firm = companies(:first_firm)
- assert firm.limited_clients.one?
+ assert_predicate firm.limited_clients, :one?
assert_equal 1, firm.limited_clients.size
end
def test_calling_one_should_return_false_if_more_than_one
firm = companies(:first_firm)
- assert ! firm.clients.one?
+ assert_not_predicate firm.clients, :one?
assert_equal 3, firm.clients.size
end
@@ -2287,7 +2287,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
post = posts(:welcome)
assert post.taggings_with_delete_all.count > 0
- assert !post.taggings_with_delete_all.loaded?
+ assert_not_predicate post.taggings_with_delete_all, :loaded?
# 2 queries: one DELETE and another to update the counter cache
assert_queries(2) do
@@ -2309,7 +2309,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "collection proxy respects default scope" do
author = authors(:mary)
- assert !author.first_posts.exists?
+ assert_not_predicate author.first_posts, :exists?
end
test "association with extend option" do
@@ -2350,7 +2350,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
test "does not duplicate associations when used with natural primary keys" do
speedometer = Speedometer.create!(id: "4")
- speedometer.minivans.create!(minivan_id: "a-van-red" , name: "a van", color: "red")
+ speedometer.minivans.create!(minivan_id: "a-van-red", name: "a van", color: "red")
assert_equal 1, speedometer.minivans.to_a.size, "Only one association should be present:\n#{speedometer.minivans.to_a}"
assert_equal 1, speedometer.reload.minivans.to_a.size
@@ -2428,7 +2428,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
pirate = FamousPirate.new
pirate.famous_ships << ship = FamousShip.new
- assert pirate.valid?
+ assert_predicate pirate, :valid?
assert_not pirate.valid?(:conference)
assert_equal "can't be blank", ship.errors[:name].first
end
@@ -2509,6 +2509,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_same car, new_bulb.car
end
+ test "reattach to new objects replaces inverse association and foreign key" do
+ bulb = Bulb.create!(car: Car.create!)
+ assert bulb.car_id
+ car = Car.new
+ car.bulbs << bulb
+ assert_equal car, bulb.car
+ assert_nil bulb.car_id
+ end
+
test "in memory replacement maintains order" do
first_bulb = Bulb.create!
second_bulb = Bulb.create!
@@ -2520,6 +2529,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [first_bulb, second_bulb], car.bulbs
end
+ test "association size calculation works with default scoped selects when not previously fetched" do
+ firm = Firm.create!(name: "Firm")
+ 5.times { firm.developers_with_select << Developer.create!(name: "Developer") }
+
+ same_firm = Firm.find(firm.id)
+ assert_equal 5, same_firm.developers_with_select.size
+ end
+
test "prevent double insertion of new object when the parent association loaded in the after save callback" do
reset_callbacks(:save, Bulb) do
Bulb.after_save { |record| record.car.bulbs.load }
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 046020e310..3b3d4037b9 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -46,6 +46,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Reader.create person_id: 0, post_id: 0
end
+ def test_marshal_dump
+ preloaded = Post.includes(:first_blue_tags).first
+ assert_equal preloaded, Marshal.load(Marshal.dump(preloaded))
+ end
+
def test_preload_sti_rhs_class
developers = Developer.includes(:firms).all.to_a
assert_no_queries do
@@ -353,10 +358,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
assert_queries(1) do
- assert posts(:welcome).people.empty?
+ assert_empty posts(:welcome).people
end
- assert posts(:welcome).reload.people.reload.empty?
+ assert_empty posts(:welcome).reload.people.reload
end
def test_destroy_association
@@ -366,8 +371,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- assert posts(:welcome).reload.people.empty?
- assert posts(:welcome).people.reload.empty?
+ assert_empty posts(:welcome).reload.people
+ assert_empty posts(:welcome).people.reload
end
def test_destroy_all
@@ -377,8 +382,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
- assert posts(:welcome).reload.people.empty?
- assert posts(:welcome).people.reload.empty?
+ assert_empty posts(:welcome).reload.people
+ assert_empty posts(:welcome).people.reload
end
def test_should_raise_exception_for_destroying_mismatching_records
@@ -538,6 +543,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_update_counter_caches_on_destroy_with_indestructible_through_record
+ post = posts(:welcome)
+ tag = post.indestructible_tags.create!(name: "doomed")
+ post.update_columns(indestructible_tags_count: post.indestructible_tags.count)
+
+ assert_no_difference "post.reload.indestructible_tags_count" do
+ posts(:welcome).indestructible_tags.destroy(tag)
+ end
+ end
+
def test_replace_association
assert_queries(4) { posts(:welcome);people(:david);people(:michael); posts(:welcome).people.reload }
@@ -675,10 +690,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
assert_queries(0) do
- assert posts(:welcome).people.empty?
+ assert_empty posts(:welcome).people
end
- assert posts(:welcome).reload.people.reload.empty?
+ assert_empty posts(:welcome).reload.people.reload
end
def test_association_callback_ordering
@@ -760,9 +775,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_unloaded_associations_does_not_load_them
person = people(:michael)
- assert !person.posts.loaded?
+ assert_not_predicate person.posts, :loaded?
assert_equal [posts(:welcome).id, posts(:authorless).id].sort, person.post_ids.sort
- assert !person.posts.loaded?
+ assert_not_predicate person.posts, :loaded?
end
def test_association_proxy_transaction_method_starts_transaction_in_association_class
@@ -852,7 +867,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
category = author.named_categories.create(name: "Primary")
author.named_categories.delete(category)
assert !Categorization.exists?(author_id: author.id, named_category_name: category.name)
- assert author.named_categories.reload.empty?
+ assert_empty author.named_categories.reload
end
def test_collection_singular_ids_getter_with_string_primary_keys
@@ -869,6 +884,14 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [dev], company.developers
end
+ def test_collection_singular_ids_setter_with_required_type_cast
+ company = companies(:rails_core)
+ dev = Developer.first
+
+ company.developer_ids = [dev.id.to_s]
+ assert_equal [dev], company.developers
+ end
+
def test_collection_singular_ids_setter_with_string_primary_keys
assert_nothing_raised do
book = books(:awdr)
@@ -926,8 +949,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_through_association_readonly_should_be_false
- assert !people(:michael).posts.first.readonly?
- assert !people(:michael).posts.to_a.first.readonly?
+ assert_not_predicate people(:michael).posts.first, :readonly?
+ assert_not_predicate people(:michael).posts.to_a.first, :readonly?
end
def test_can_update_through_association
@@ -1016,12 +1039,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post.author_categorizations
proxy = post.send(:association_instance_get, :author_categorizations)
- assert !proxy.stale_target?
+ assert_not_predicate proxy, :stale_target?
assert_equal authors(:mary).categorizations.sort_by(&:id), post.author_categorizations.sort_by(&:id)
post.author_id = authors(:david).id
- assert proxy.stale_target?
+ assert_predicate proxy, :stale_target?
assert_equal authors(:david).categorizations.sort_by(&:id), post.author_categorizations.sort_by(&:id)
end
@@ -1038,7 +1061,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_includes post.author_addresses, address
post.author_addresses.delete(address)
- assert post[:author_count].nil?
+ assert_predicate post[:author_count], :nil?
end
def test_primary_key_option_on_source
@@ -1300,6 +1323,70 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_has_many_through_update_ids_with_conditions
+ author = Author.create!(name: "Bill")
+ category = categories(:general)
+
+ author.update(
+ special_categories_with_condition_ids: [category.id],
+ nonspecial_categories_with_condition_ids: [category.id]
+ )
+
+ assert_equal [category.id], author.special_categories_with_condition_ids
+ assert_equal [category.id], author.nonspecial_categories_with_condition_ids
+
+ author.update(nonspecial_categories_with_condition_ids: [])
+ author.reload
+
+ assert_equal [category.id], author.special_categories_with_condition_ids
+ assert_equal [], author.nonspecial_categories_with_condition_ids
+ end
+
+ def test_single_has_many_through_association_with_unpersisted_parent_instance
+ post_with_single_has_many_through = Class.new(Post) do
+ def self.name; "PostWithSingleHasManyThrough"; end
+ has_many :subscriptions, through: :author
+ end
+ post = post_with_single_has_many_through.new
+
+ post.author = authors(:mary)
+ book1 = Book.create!(name: "essays on single has many through associations 1")
+ post.author.books << book1
+ subscription1 = Subscription.first
+ book1.subscriptions << subscription1
+ assert_equal [subscription1], post.subscriptions.to_a
+
+ post.author = authors(:bob)
+ book2 = Book.create!(name: "essays on single has many through associations 2")
+ post.author.books << book2
+ subscription2 = Subscription.second
+ book2.subscriptions << subscription2
+ assert_equal [subscription2], post.subscriptions.to_a
+ end
+
+ def test_nested_has_many_through_association_with_unpersisted_parent_instance
+ post_with_nested_has_many_through = Class.new(Post) do
+ def self.name; "PostWithNestedHasManyThrough"; end
+ has_many :books, through: :author
+ has_many :subscriptions, through: :books
+ end
+ post = post_with_nested_has_many_through.new
+
+ post.author = authors(:mary)
+ book1 = Book.create!(name: "essays on nested has many through associations 1")
+ post.author.books << book1
+ subscription1 = Subscription.first
+ book1.subscriptions << subscription1
+ assert_equal [subscription1], post.subscriptions.to_a
+
+ post.author = authors(:bob)
+ book2 = Book.create!(name: "essays on nested has many through associations 2")
+ post.author.books << book2
+ subscription2 = Subscription.second
+ book2.subscriptions << subscription2
+ assert_equal [subscription2], post.subscriptions.to_a
+ end
+
private
def make_model(name)
Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index ec5d95080b..1a213ef7e4 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -114,8 +114,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
developer = Developer.create!(name: "Someone")
ship = Ship.create!(name: "Planet Caravan", developer: developer)
ship.destroy
- assert !ship.persisted?
- assert !developer.persisted?
+ assert_not_predicate ship, :persisted?
+ assert_not_predicate developer, :persisted?
end
def test_natural_assignment_to_nil_after_destroy
@@ -186,7 +186,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
assert RestrictedWithExceptionFirm.exists?(name: "restrict")
- assert firm.account.present?
+ assert_predicate firm.account, :present?
end
def test_restrict_with_error
@@ -197,10 +197,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
firm.destroy
- assert !firm.errors.empty?
+ assert_not_empty firm.errors
assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first
assert RestrictedWithErrorFirm.exists?(name: "restrict")
- assert firm.account.present?
+ assert_predicate firm.account, :present?
end
def test_restrict_with_error_with_locale
@@ -213,10 +213,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
firm.destroy
- assert !firm.errors.empty?
+ assert_not_empty firm.errors
assert_equal "Cannot delete record because a dependent firm account exists", firm.errors[:base].first
assert RestrictedWithErrorFirm.exists?(name: "restrict")
- assert firm.account.present?
+ assert_predicate firm.account, :present?
ensure
I18n.backend.reload!
end
@@ -377,7 +377,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_assignment_before_child_saved
firm = Firm.find(1)
firm.account = a = Account.new("credit_limit" => 1000)
- assert a.persisted?
+ assert_predicate a, :persisted?
assert_equal a, firm.account
assert_equal a, firm.account
firm.association(:account).reload
@@ -395,7 +395,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_cant_save_readonly_association
assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_firm).readonly_account.save! }
- assert companies(:first_firm).readonly_account.readonly?
+ assert_predicate companies(:first_firm).readonly_account, :readonly?
end
def test_has_one_proxy_should_not_respond_to_private_methods
@@ -433,7 +433,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_create_respects_hash_condition
account = companies(:first_firm).create_account_limit_500_with_hash_conditions
- assert account.persisted?
+ assert_predicate account, :persisted?
assert_equal 500, account.credit_limit
end
@@ -450,7 +450,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
new_ship = pirate.create_ship
assert_not_equal ships(:black_pearl), new_ship
assert_equal new_ship, pirate.ship
- assert new_ship.new_record?
+ assert_predicate new_ship, :new_record?
assert_nil orig_ship.pirate_id
assert !orig_ship.changed? # check it was saved
end
@@ -460,8 +460,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
orig_ship = pirate.dependent_ship
new_ship = pirate.create_dependent_ship
- assert new_ship.new_record?
- assert orig_ship.destroyed?
+ assert_predicate new_ship, :new_record?
+ assert_predicate orig_ship, :destroyed?
end
def test_creation_failure_due_to_new_record_should_raise_error
@@ -481,7 +481,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
pirate = pirates(:blackbeard)
pirate.ship.name = nil
- assert !pirate.ship.valid?
+ assert_not_predicate pirate.ship, :valid?
error = assert_raise(ActiveRecord::RecordNotSaved) do
pirate.ship = ships(:interceptor)
end
@@ -588,7 +588,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
ship.save!
ship.name = "new name"
- assert ship.changed?
+ assert_predicate ship, :changed?
assert_queries(1) do
# One query for updating name, not triggering query for updating pirate_id
pirate.ship = ship
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index fe24c465b2..9964f084ac 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -42,6 +42,18 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_not_nil new_member.club
end
+ def test_creating_association_builds_through_record
+ new_member = Member.create(name: "Chris")
+ new_club = new_member.association(:club).build
+ assert new_member.current_membership
+ assert_equal new_club, new_member.club
+ assert_predicate new_club, :new_record?
+ assert_predicate new_member.current_membership, :new_record?
+ assert new_member.save
+ assert_predicate new_club, :persisted?
+ assert_predicate new_member.current_membership, :persisted?
+ end
+
def test_creating_association_builds_through_record_for_new
new_member = Member.new(name: "Jane")
new_member.club = clubs(:moustache_club)
@@ -100,7 +112,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_one_through_eager_loading
- members = assert_queries(3) do #base table, through table, clubs table
+ members = assert_queries(3) do # base table, through table, clubs table
Member.all.merge!(includes: :club, where: ["name = ?", "Groucho Marx"]).to_a
end
assert_equal 1, members.size
@@ -108,7 +120,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
def test_has_one_through_eager_loading_through_polymorphic
- members = assert_queries(3) do #base table, through table, clubs table
+ members = assert_queries(3) do # base table, through table, clubs table
Member.all.merge!(includes: :sponsor_club, where: ["name = ?", "Groucho Marx"]).to_a
end
assert_equal 1, members.size
@@ -139,7 +151,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eagerloading
members = assert_queries(1) do
- Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback
+ Member.all.merge!(includes: :club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a # force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries { members[0].club }
@@ -147,7 +159,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eager_loading_through_polymorphic
members = assert_queries(1) do
- Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a #force fallback
+ Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name").to_a # force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries { members[0].sponsor_club }
@@ -156,7 +168,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record
Sponsor.new(sponsor_club: clubs(:crazy_club), sponsorable: members(:groucho)).save!
members = assert_queries(1) do
- Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a #force fallback
+ Member.all.merge!(includes: :sponsor_club, where: ["members.name = ?", "Groucho Marx"], order: "clubs.name DESC").to_a # force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries { members[0].sponsor_club }
@@ -229,7 +241,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
MemberDetail.all.merge!(includes: :member_type).to_a
end
@new_detail = @member_details[0]
- assert @new_detail.send(:association, :member_type).loaded?
+ assert_predicate @new_detail.send(:association, :member_type), :loaded?
assert_no_queries { @new_detail.member_type }
end
@@ -317,12 +329,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
minivan.dashboard
proxy = minivan.send(:association_instance_get, :dashboard)
- assert !proxy.stale_target?
+ assert_not_predicate proxy, :stale_target?
assert_equal dashboards(:cool_first), minivan.dashboard
minivan.speedometer_id = speedometers(:second).id
- assert proxy.stale_target?
+ assert_predicate proxy, :stale_target?
assert_equal dashboards(:second), minivan.dashboard
end
@@ -334,7 +346,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
minivan.speedometer_id = speedometers(:second).id
- assert proxy.stale_target?
+ assert_predicate proxy, :stale_target?
assert_equal dashboards(:second), minivan.dashboard
end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index 7be875fec6..ca0620dc3b 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -115,19 +115,19 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
scope = Post.joins(:special_comments).where(id: posts(:sti_comments).id)
# The join should match SpecialComment and its subclasses only
- assert scope.where("comments.type" => "Comment").empty?
- assert !scope.where("comments.type" => "SpecialComment").empty?
- assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ assert_empty scope.where("comments.type" => "Comment")
+ assert_not_empty scope.where("comments.type" => "SpecialComment")
+ assert_not_empty scope.where("comments.type" => "SubSpecialComment")
end
def test_find_with_conditions_on_reflection
- assert !posts(:welcome).comments.empty?
+ assert_not_empty posts(:welcome).comments
assert Post.joins(:nonexistent_comments).where(id: posts(:welcome).id).empty? # [sic!]
end
def test_find_with_conditions_on_through_reflection
- assert !posts(:welcome).tags.empty?
- assert Post.joins(:misc_tags).where(id: posts(:welcome).id).empty?
+ assert_not_empty posts(:welcome).tags
+ assert_empty Post.joins(:misc_tags).where(id: posts(:welcome).id)
end
test "the default scope of the target is applied when joining associations" do
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index e13cf93dcf..bad1bcdb67 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -129,7 +129,7 @@ class AutomaticInverseFindingTests < ActiveRecord::TestCase
def test_polymorphic_has_one_should_find_inverse_automatically
man_reflection = Man.reflect_on_association(:polymorphic_face_without_inverse)
- assert man_reflection.has_inverse?
+ assert_predicate man_reflection, :has_inverse?
end
end
@@ -150,22 +150,22 @@ class InverseAssociationTests < ActiveRecord::TestCase
def test_should_be_able_to_ask_a_reflection_if_it_has_an_inverse
has_one_with_inverse_ref = Man.reflect_on_association(:face)
- assert has_one_with_inverse_ref.has_inverse?
+ assert_predicate has_one_with_inverse_ref, :has_inverse?
has_many_with_inverse_ref = Man.reflect_on_association(:interests)
- assert has_many_with_inverse_ref.has_inverse?
+ assert_predicate has_many_with_inverse_ref, :has_inverse?
belongs_to_with_inverse_ref = Face.reflect_on_association(:man)
- assert belongs_to_with_inverse_ref.has_inverse?
+ assert_predicate belongs_to_with_inverse_ref, :has_inverse?
has_one_without_inverse_ref = Club.reflect_on_association(:sponsor)
- assert !has_one_without_inverse_ref.has_inverse?
+ assert_not_predicate has_one_without_inverse_ref, :has_inverse?
has_many_without_inverse_ref = Club.reflect_on_association(:memberships)
- assert !has_many_without_inverse_ref.has_inverse?
+ assert_not_predicate has_many_without_inverse_ref, :has_inverse?
belongs_to_without_inverse_ref = Sponsor.reflect_on_association(:sponsor_club)
- assert !belongs_to_without_inverse_ref.has_inverse?
+ assert_not_predicate belongs_to_without_inverse_ref, :has_inverse?
end
def test_inverse_of_method_should_supply_the_actual_reflection_instance_it_is_the_inverse_of
@@ -464,7 +464,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
interest = Interest.create!(man: man)
man.interests.find(interest.id)
- assert_not man.interests.loaded?
+ assert_not_predicate man.interests, :loaded?
end
def test_raise_record_not_found_error_when_invalid_ids_are_passed
@@ -484,7 +484,10 @@ class InverseHasManyTests < ActiveRecord::TestCase
def test_raise_record_not_found_error_when_no_ids_are_passed
man = Man.create!
- assert_raise(ActiveRecord::RecordNotFound) { man.interests.find() }
+ exception = assert_raise(ActiveRecord::RecordNotFound) { man.interests.load.find() }
+
+ assert_equal exception.model, "Interest"
+ assert_equal exception.primary_key, "id"
end
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
@@ -501,16 +504,16 @@ class InverseHasManyTests < ActiveRecord::TestCase
i.man.name = "Charles"
assert_equal i.man.name, man.name
- assert !man.persisted?
+ assert_not_predicate man, :persisted?
end
def test_inverse_instance_should_be_set_before_find_callbacks_are_run
reset_callbacks(Interest, :find) do
Interest.after_find { raise unless association(:man).loaded? && man.present? }
- assert Man.first.interests.reload.any?
- assert Man.includes(:interests).first.interests.any?
- assert Man.joins(:interests).includes(:interests).first.interests.any?
+ assert_predicate Man.first.interests.reload, :any?
+ assert_predicate Man.includes(:interests).first.interests, :any?
+ assert_predicate Man.joins(:interests).includes(:interests).first.interests, :any?
end
end
@@ -518,9 +521,9 @@ class InverseHasManyTests < ActiveRecord::TestCase
reset_callbacks(Interest, :initialize) do
Interest.after_initialize { raise unless association(:man).loaded? && man.present? }
- assert Man.first.interests.reload.any?
- assert Man.includes(:interests).first.interests.any?
- assert Man.joins(:interests).includes(:interests).first.interests.any?
+ assert_predicate Man.first.interests.reload, :any?
+ assert_predicate Man.includes(:interests).first.interests, :any?
+ assert_predicate Man.joins(:interests).includes(:interests).first.interests, :any?
end
end
@@ -672,6 +675,16 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
assert_equal old_inversed_man.object_id, new_inversed_man.object_id
end
+ def test_inversed_instance_should_not_be_reloaded_after_stale_state_changed_with_validation
+ face = Face.new man: Man.new
+
+ old_inversed_man = face.man
+ face.save!
+ new_inversed_man = face.man
+
+ assert_equal old_inversed_man.object_id, new_inversed_man.object_id
+ end
+
def test_should_not_try_to_set_inverse_instances_when_the_inverse_is_a_has_many
i = interests(:llama_wrangling)
m = i.polymorphic_man
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 87694b0788..f19a9f5f7a 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -44,11 +44,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_distinct_through_count
author = authors(:mary)
- assert !authors(:mary).unique_categorized_posts.loaded?
+ assert_not_predicate authors(:mary).unique_categorized_posts, :loaded?
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count }
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count(:title) }
assert_queries(1) { assert_equal 0, author.unique_categorized_posts.where(title: nil).count(:title) }
- assert !authors(:mary).unique_categorized_posts.loaded?
+ assert_not_predicate authors(:mary).unique_categorized_posts, :loaded?
end
def test_has_many_distinct_through_find
@@ -454,8 +454,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_uses_conditions_specified_on_the_has_many_association
author = Author.first
- assert author.comments.present?
- assert author.nonexistent_comments.blank?
+ assert_predicate author.comments, :present?
+ assert_predicate author.nonexistent_comments, :blank?
end
def test_has_many_through_uses_correct_attributes
@@ -467,27 +467,27 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
new_tag = Tag.new(name: "new")
saved_post.tags << new_tag
- assert new_tag.persisted? #consistent with habtm!
- assert saved_post.persisted?
+ assert new_tag.persisted? # consistent with habtm!
+ assert_predicate saved_post, :persisted?
assert_includes saved_post.tags, new_tag
- assert new_tag.persisted?
+ assert_predicate new_tag, :persisted?
assert_includes saved_post.reload.tags.reload, new_tag
new_post = Post.new(title: "Association replacement works!", body: "You best believe it.")
saved_tag = tags(:general)
new_post.tags << saved_tag
- assert !new_post.persisted?
- assert saved_tag.persisted?
+ assert_not_predicate new_post, :persisted?
+ assert_predicate saved_tag, :persisted?
assert_includes new_post.tags, saved_tag
new_post.save!
- assert new_post.persisted?
+ assert_predicate new_post, :persisted?
assert_includes new_post.reload.tags.reload, saved_tag
- assert !posts(:thinking).tags.build.persisted?
- assert !posts(:thinking).tags.new.persisted?
+ assert_not_predicate posts(:thinking).tags.build, :persisted?
+ assert_not_predicate posts(:thinking).tags.new, :persisted?
end
def test_create_associate_when_adding_to_has_many_through
@@ -529,14 +529,14 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_collection_size_doesnt_load_target_if_not_loaded
author = authors(:david)
assert_equal 10, author.comments.size
- assert !author.comments.loaded?
+ assert_not_predicate author.comments, :loaded?
end
def test_has_many_through_collection_size_uses_counter_cache_if_it_exists
c = categories(:general)
c.categorizations_count = 100
assert_equal 100, c.categorizations.size
- assert !c.categorizations.loaded?
+ assert_not_predicate c.categorizations, :loaded?
end
def test_adding_junk_to_has_many_through_should_raise_type_mismatch
@@ -710,7 +710,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
category = david.categories.first
assert_no_queries do
- assert david.categories.loaded?
+ assert_predicate david.categories, :loaded?
assert_includes david.categories, category
end
end
@@ -720,18 +720,18 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
category = david.categories.first
david.reload
- assert ! david.categories.loaded?
+ assert_not_predicate david.categories, :loaded?
assert_queries(1) do
assert_includes david.categories, category
end
- assert ! david.categories.loaded?
+ assert_not_predicate david.categories, :loaded?
end
def test_has_many_through_include_returns_false_for_non_matching_record_to_verify_scoping
david = authors(:david)
category = Category.create!(name: "Not Associated")
- assert ! david.categories.loaded?
+ assert_not_predicate david.categories, :loaded?
assert ! david.categories.include?(category)
end
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index c95d0425cd..7b5c394177 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -69,15 +69,15 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
scope = Post.left_outer_joins(:special_comments).where(id: posts(:sti_comments).id)
# The join should match SpecialComment and its subclasses only
- assert scope.where("comments.type" => "Comment").empty?
- assert !scope.where("comments.type" => "SpecialComment").empty?
- assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ assert_empty scope.where("comments.type" => "Comment")
+ assert_not_empty scope.where("comments.type" => "SpecialComment")
+ assert_not_empty scope.where("comments.type" => "SubSpecialComment")
end
def test_does_not_override_select
authors = Author.select("authors.name, #{%{(authors.author_address_id || ' ' || authors.author_address_extra_id) as addr_id}}").left_outer_joins(:posts)
- assert authors.any?
- assert authors.first.respond_to?(:addr_id)
+ assert_predicate authors, :any?
+ assert_respond_to authors.first, :addr_id
end
test "the default scope of the target is applied when joining associations" do
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index 65d30d011b..03ed1c1d47 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -78,7 +78,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
# This ensures that the polymorphism of taggings is being observed correctly
authors = Author.joins(:tags).where("taggings.taggable_type" => "FakeModel")
- assert authors.empty?
+ assert_empty authors
end
# has_many through
@@ -177,7 +177,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
members = Member.joins(:organization_member_details).
where("member_details.id" => 9)
- assert members.empty?
+ assert_empty members
end
# has_many through
@@ -209,7 +209,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
members = Member.joins(:organization_member_details_2).
where("member_details.id" => 9)
- assert members.empty?
+ assert_empty members
end
# has_many through
@@ -425,9 +425,9 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
# Check the polymorphism of taggings is being observed correctly (in both joins)
authors = Author.joins(:similar_posts).where("taggings.taggable_type" => "FakeModel")
- assert authors.empty?
+ assert_empty authors
authors = Author.joins(:similar_posts).where("taggings_authors_join.taggable_type" => "FakeModel")
- assert authors.empty?
+ assert_empty authors
end
def test_nested_has_many_through_with_scope_on_polymorphic_reflection
@@ -456,9 +456,9 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
# Ensure STI is respected in the join
scope = Post.joins(:special_comments_ratings).where(id: posts(:sti_comments).id)
- assert scope.where("comments.type" => "Comment").empty?
- assert !scope.where("comments.type" => "SpecialComment").empty?
- assert !scope.where("comments.type" => "SubSpecialComment").empty?
+ assert_empty scope.where("comments.type" => "Comment")
+ assert_not_empty scope.where("comments.type" => "SpecialComment")
+ assert_not_empty scope.where("comments.type" => "SubSpecialComment")
end
def test_has_many_through_with_sti_on_nested_through_reflection
@@ -466,8 +466,8 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [taggings(:special_comment_rating)], taggings
scope = Post.joins(:special_comments_ratings_taggings).where(id: posts(:sti_comments).id)
- assert scope.where("comments.type" => "Comment").empty?
- assert !scope.where("comments.type" => "SpecialComment").empty?
+ assert_empty scope.where("comments.type" => "Comment")
+ assert_not_empty scope.where("comments.type" => "SpecialComment")
end
def test_nested_has_many_through_writers_should_raise_error
@@ -517,7 +517,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
end
def test_nested_has_many_through_with_conditions_on_through_associations_preload
- assert Author.where("tags.id" => 100).joins(:misc_post_first_blue_tags).empty?
+ assert_empty Author.where("tags.id" => 100).joins(:misc_post_first_blue_tags)
authors = assert_queries(3) { Author.includes(:misc_post_first_blue_tags).to_a.sort_by(&:id) }
blue = tags(:blue)
@@ -574,9 +574,9 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
c = Categorization.new
c.author = authors(:david)
c.post_taggings.to_a
- assert !c.post_taggings.empty?
+ assert_not_empty c.post_taggings
c.save
- assert !c.post_taggings.empty?
+ assert_not_empty c.post_taggings
end
def test_polymorphic_has_many_through_when_through_association_has_not_loaded
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index de04221ea6..fce2a7eed1 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -47,7 +47,7 @@ class AssociationsTest < ActiveRecord::TestCase
ship = Ship.create!(name: "The good ship Dollypop")
part = ship.parts.create!(name: "Mast")
part.mark_for_destruction
- assert ship.parts[0].marked_for_destruction?
+ assert_predicate ship.parts[0], :marked_for_destruction?
end
def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction
@@ -119,7 +119,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = authors(:david)
david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!"))
- assert !david.posts.loaded?
+ assert_not_predicate david.posts, :loaded?
assert_includes david.posts, post
end
@@ -127,7 +127,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = authors(:david)
david.categories << categories(:technology)
- assert !david.categories.loaded?
+ assert_not_predicate david.categories, :loaded?
assert_includes david.categories, categories(:technology)
end
@@ -135,23 +135,23 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = authors(:david)
david.posts << (post = Post.new(title: "New on Edge", body: "More cool stuff!"))
- assert !david.posts.loaded?
+ assert_not_predicate david.posts, :loaded?
david.save
- assert !david.posts.loaded?
+ assert_not_predicate david.posts, :loaded?
assert_includes david.posts, post
end
def test_push_does_not_lose_additions_to_new_record
josh = Author.new(name: "Josh")
josh.posts << Post.new(title: "New on Edge", body: "More cool stuff!")
- assert josh.posts.loaded?
+ assert_predicate josh.posts, :loaded?
assert_equal 1, josh.posts.size
end
def test_append_behaves_like_push
josh = Author.new(name: "Josh")
josh.posts.append Post.new(title: "New on Edge", body: "More cool stuff!")
- assert josh.posts.loaded?
+ assert_predicate josh.posts, :loaded?
assert_equal 1, josh.posts.size
end
@@ -163,22 +163,22 @@ class AssociationProxyTest < ActiveRecord::TestCase
def test_save_on_parent_does_not_load_target
david = developers(:david)
- assert !david.projects.loaded?
+ assert_not_predicate david.projects, :loaded?
david.update_columns(created_at: Time.now)
- assert !david.projects.loaded?
+ assert_not_predicate david.projects, :loaded?
end
def test_load_does_load_target
david = developers(:david)
- assert !david.projects.loaded?
+ assert_not_predicate david.projects, :loaded?
david.projects.load
- assert david.projects.loaded?
+ assert_predicate david.projects, :loaded?
end
def test_inspect_does_not_reload_a_not_yet_loaded_target
andreas = Developer.new name: "Andreas", log: "new developer added"
- assert !andreas.audit_logs.loaded?
+ assert_not_predicate andreas.audit_logs, :loaded?
assert_match(/message: "new developer added"/, andreas.audit_logs.inspect)
end
@@ -248,14 +248,14 @@ class AssociationProxyTest < ActiveRecord::TestCase
test "first! works on loaded associations" do
david = authors(:david)
assert_equal david.first_posts.first, david.first_posts.reload.first!
- assert david.first_posts.loaded?
+ assert_predicate david.first_posts, :loaded?
assert_no_queries { david.first_posts.first! }
end
def test_pluck_uses_loaded_target
david = authors(:david)
assert_equal david.first_posts.pluck(:title), david.first_posts.load.pluck(:title)
- assert david.first_posts.loaded?
+ assert_predicate david.first_posts, :loaded?
assert_no_queries { david.first_posts.pluck(:title) }
end
@@ -263,9 +263,9 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = authors(:david)
david.posts.reload
- assert david.posts.loaded?
+ assert_predicate david.posts, :loaded?
david.posts.reset
- assert !david.posts.loaded?
+ assert_not_predicate david.posts, :loaded?
end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 2f42684212..dc6638d45d 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -99,8 +99,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
test "boolean attributes" do
- assert !Topic.find(1).approved?
- assert Topic.find(2).approved?
+ assert_not_predicate Topic.find(1), :approved?
+ assert_predicate Topic.find(2), :approved?
end
test "set attributes" do
@@ -142,16 +142,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert_respond_to topic, :title=
assert_respond_to topic, "author_name"
assert_respond_to topic, "attribute_names"
- assert !topic.respond_to?("nothingness")
- assert !topic.respond_to?(:nothingness)
+ assert_not_respond_to topic, "nothingness"
+ assert_not_respond_to topic, :nothingness
end
test "respond_to? with a custom primary key" do
keyboard = Keyboard.create
assert_not_nil keyboard.key_number
assert_equal keyboard.key_number, keyboard.id
- assert keyboard.respond_to?("key_number")
- assert keyboard.respond_to?("id")
+ assert_respond_to keyboard, "key_number"
+ assert_respond_to keyboard, "id"
end
test "id_before_type_cast with a custom primary key" do
@@ -170,8 +170,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
topic = klass.allocate
- assert !topic.respond_to?("nothingness")
- assert !topic.respond_to?(:nothingness)
+ assert_not_respond_to topic, "nothingness"
+ assert_not_respond_to topic, :nothingness
assert_respond_to topic, "title"
assert_respond_to topic, :title
end
@@ -200,12 +200,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
if current_adapter?(:Mysql2Adapter)
test "read attributes_before_type_cast on a boolean" do
bool = Boolean.create!("value" => false)
- if RUBY_PLATFORM.include?("java")
- # JRuby will return the value before typecast as string.
- assert_equal "0", bool.reload.attributes_before_type_cast["value"]
- else
- assert_equal 0, bool.reload.attributes_before_type_cast["value"]
- end
+ assert_equal 0, bool.reload.attributes_before_type_cast["value"]
end
end
@@ -462,30 +457,30 @@ class AttributeMethodsTest < ActiveRecord::TestCase
SQL
assert_equal "Firm", object.string_value
- assert object.string_value?
+ assert_predicate object, :string_value?
object.string_value = " "
- assert !object.string_value?
+ assert_not_predicate object, :string_value?
assert_equal 1, object.int_value.to_i
- assert object.int_value?
+ assert_predicate object, :int_value?
object.int_value = "0"
- assert !object.int_value?
+ assert_not_predicate object, :int_value?
end
test "non-attribute read and write" do
topic = Topic.new
- assert !topic.respond_to?("mumbo")
+ assert_not_respond_to topic, "mumbo"
assert_raise(NoMethodError) { topic.mumbo }
assert_raise(NoMethodError) { topic.mumbo = 5 }
end
test "undeclared attribute method does not affect respond_to? and method_missing" do
topic = @target.new(title: "Budget")
- assert topic.respond_to?("title")
+ assert_respond_to topic, "title"
assert_equal "Budget", topic.title
- assert !topic.respond_to?("title_hello_world")
+ assert_not_respond_to topic, "title_hello_world"
assert_raise(NoMethodError) { topic.title_hello_world }
end
@@ -496,7 +491,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
@target.attribute_method_prefix prefix
meth = "#{prefix}title"
- assert topic.respond_to?(meth)
+ assert_respond_to topic, meth
assert_equal ["title"], topic.send(meth)
assert_equal ["title", "a"], topic.send(meth, "a")
assert_equal ["title", 1, 2, 3], topic.send(meth, 1, 2, 3)
@@ -510,7 +505,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic = @target.new(title: "Budget")
meth = "title#{suffix}"
- assert topic.respond_to?(meth)
+ assert_respond_to topic, meth
assert_equal ["title"], topic.send(meth)
assert_equal ["title", "a"], topic.send(meth, "a")
assert_equal ["title", 1, 2, 3], topic.send(meth, 1, 2, 3)
@@ -524,7 +519,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic = @target.new(title: "Budget")
meth = "#{prefix}title#{suffix}"
- assert topic.respond_to?(meth)
+ assert_respond_to topic, meth
assert_equal ["title"], topic.send(meth)
assert_equal ["title", "a"], topic.send(meth, "a")
assert_equal ["title", 1, 2, 3], topic.send(meth, 1, 2, 3)
@@ -546,7 +541,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
else
topic = Topic.all.merge!(select: "topics.*, 1=2 as is_test").first
end
- assert !topic.is_test?
+ assert_not_predicate topic, :is_test?
end
test "typecast attribute from select to true" do
@@ -557,7 +552,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
else
topic = Topic.all.merge!(select: "topics.*, 2=2 as is_test").first
end
- assert topic.is_test?
+ assert_predicate topic, :is_test?
end
test "raises ActiveRecord::DangerousAttributeError when defining an AR method in a model" do
@@ -748,7 +743,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
expected_time = Time.utc(2000, 01, 01, 10)
assert_equal expected_time, record.bonus_time
- assert record.bonus_time.utc?
+ assert_predicate record.bonus_time, :utc?
end
end
end
@@ -772,7 +767,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
privatize("title")
topic = @target.new(title: "The pros and cons of programming naked.")
- assert !topic.respond_to?(:title)
+ assert_not_respond_to topic, :title
exception = assert_raise(NoMethodError) { topic.title }
assert_includes exception.message, "private method"
assert_equal "I'm private", topic.send(:title)
@@ -782,7 +777,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
privatize("title=(value)")
topic = @target.new
- assert !topic.respond_to?(:title=)
+ assert_not_respond_to topic, :title=
exception = assert_raise(NoMethodError) { topic.title = "Pants" }
assert_includes exception.message, "private method"
topic.send(:title=, "Very large pants")
@@ -792,7 +787,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
privatize("title?")
topic = @target.new(title: "Isaac Newton's pants")
- assert !topic.respond_to?(:title?)
+ assert_not_respond_to topic, :title?
exception = assert_raise(NoMethodError) { topic.title? }
assert_includes exception.message, "private method"
assert topic.send(:title?)
@@ -984,9 +979,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase
test "came_from_user?" do
model = @target.first
- assert_not model.id_came_from_user?
+ assert_not_predicate model, :id_came_from_user?
model.id = "omg"
- assert model.id_came_from_user?
+ assert_predicate model, :id_came_from_user?
end
test "accessed_fields" do
diff --git a/activerecord/test/cases/attributes_test.rb b/activerecord/test/cases/attributes_test.rb
index 2caf2a63d4..3bc56694be 100644
--- a/activerecord/test/cases/attributes_test.rb
+++ b/activerecord/test/cases/attributes_test.rb
@@ -59,7 +59,7 @@ module ActiveRecord
test "nonexistent attribute" do
data = OverloadedType.new(non_existent_decimal: 1)
- assert_equal BigDecimal.new(1), data.non_existent_decimal
+ assert_equal BigDecimal(1), data.non_existent_decimal
assert_raise ActiveRecord::UnknownAttributeError do
UnoverloadedType.new(non_existent_decimal: 1)
end
@@ -211,7 +211,7 @@ module ActiveRecord
end
test "attributes not backed by database columns are not dirty when unchanged" do
- refute OverloadedType.new.non_existent_decimal_changed?
+ assert_not_predicate OverloadedType.new, :non_existent_decimal_changed?
end
test "attributes not backed by database columns are always initialized" do
@@ -245,13 +245,13 @@ module ActiveRecord
model.foo << "asdf"
assert_equal "lolasdf", model.foo
- assert model.foo_changed?
+ assert_predicate model, :foo_changed?
model.reload
assert_equal "lol", model.foo
model.foo = "lol"
- refute model.changed?
+ assert_not_predicate model, :changed?
end
test "attributes not backed by database columns appear in inspect" do
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 4d8368fd8a..09684520d1 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -51,7 +51,7 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
u = person.create!(first_name: "cool")
u.update_attributes!(first_name: "nah") # still valid because validation only applies on 'create'
- assert reference.create!(person: u).persisted?
+ assert_predicate reference.create!(person: u), :persisted?
end
def test_should_not_add_the_same_callbacks_multiple_times_for_has_one
@@ -74,7 +74,7 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
ship = ShipWithoutNestedAttributes.new
ship.prisoners.build
- assert_not ship.valid?
+ assert_not_predicate ship, :valid?
assert_equal 1, ship.errors[:name].length
end
@@ -99,35 +99,35 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
def test_should_save_parent_but_not_invalid_child
firm = Firm.new(name: "GlobalMegaCorp")
- assert firm.valid?
+ assert_predicate firm, :valid?
firm.build_account_using_primary_key
- assert !firm.build_account_using_primary_key.valid?
+ assert_not_predicate firm.build_account_using_primary_key, :valid?
assert firm.save
- assert !firm.account_using_primary_key.persisted?
+ assert_not_predicate firm.account_using_primary_key, :persisted?
end
def test_save_fails_for_invalid_has_one
firm = Firm.first
- assert firm.valid?
+ assert_predicate firm, :valid?
firm.build_account
- assert !firm.account.valid?
- assert !firm.valid?
+ assert_not_predicate firm.account, :valid?
+ assert_not_predicate firm, :valid?
assert !firm.save
assert_equal ["is invalid"], firm.errors["account"]
end
def test_save_succeeds_for_invalid_has_one_with_validate_false
firm = Firm.first
- assert firm.valid?
+ assert_predicate firm, :valid?
firm.build_unvalidated_account
- assert !firm.unvalidated_account.valid?
- assert firm.valid?
+ assert_not_predicate firm.unvalidated_account, :valid?
+ assert_predicate firm, :valid?
assert firm.save
end
@@ -136,10 +136,10 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
account = firm.build_account("credit_limit" => 1000)
assert_equal account, firm.account
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
assert firm.save
assert_equal account, firm.account
- assert account.persisted?
+ assert_predicate account, :persisted?
end
def test_build_before_either_saved
@@ -147,16 +147,16 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
firm.account = account = Account.new("credit_limit" => 1000)
assert_equal account, firm.account
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
assert firm.save
assert_equal account, firm.account
- assert account.persisted?
+ assert_predicate account, :persisted?
end
def test_assignment_before_parent_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = a = Account.find(1)
- assert !firm.persisted?
+ assert_not_predicate firm, :persisted?
assert_equal a, firm.account
assert firm.save
assert_equal a, firm.account
@@ -167,12 +167,12 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
def test_assignment_before_either_saved
firm = Firm.new("name" => "GlobalMegaCorp")
firm.account = a = Account.new("credit_limit" => 1000)
- assert !firm.persisted?
- assert !a.persisted?
+ assert_not_predicate firm, :persisted?
+ assert_not_predicate a, :persisted?
assert_equal a, firm.account
assert firm.save
- assert firm.persisted?
- assert a.persisted?
+ assert_predicate firm, :persisted?
+ assert_predicate a, :persisted?
assert_equal a, firm.account
firm.association(:account).reload
assert_equal a, firm.account
@@ -221,13 +221,13 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
def test_should_save_parent_but_not_invalid_child
client = Client.new(name: "Joe (the Plumber)")
- assert client.valid?
+ assert_predicate client, :valid?
client.build_firm
- assert !client.firm.valid?
+ assert_not_predicate client.firm, :valid?
assert client.save
- assert !client.firm.persisted?
+ assert_not_predicate client.firm, :persisted?
end
def test_save_fails_for_invalid_belongs_to
@@ -235,8 +235,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert log = AuditLog.create(developer_id: 0, message: " ")
log.developer = Developer.new
- assert !log.developer.valid?
- assert !log.valid?
+ assert_not_predicate log.developer, :valid?
+ assert_not_predicate log, :valid?
assert !log.save
assert_equal ["is invalid"], log.errors["developer"]
end
@@ -246,8 +246,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert log = AuditLog.create(developer_id: 0, message: " ")
log.unvalidated_developer = Developer.new
- assert !log.unvalidated_developer.valid?
- assert log.valid?
+ assert_not_predicate log.unvalidated_developer, :valid?
+ assert_predicate log, :valid?
assert log.save
end
@@ -256,10 +256,10 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
apple = Firm.new("name" => "Apple")
client.firm = apple
assert_equal apple, client.firm
- assert !apple.persisted?
+ assert_not_predicate apple, :persisted?
assert client.save
assert apple.save
- assert apple.persisted?
+ assert_predicate apple, :persisted?
assert_equal apple, client.firm
client.association(:firm).reload
assert_equal apple, client.firm
@@ -269,11 +269,11 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
final_cut = Client.new("name" => "Final Cut")
apple = Firm.new("name" => "Apple")
final_cut.firm = apple
- assert !final_cut.persisted?
- assert !apple.persisted?
+ assert_not_predicate final_cut, :persisted?
+ assert_not_predicate apple, :persisted?
assert final_cut.save
- assert final_cut.persisted?
- assert apple.persisted?
+ assert_predicate final_cut, :persisted?
+ assert_predicate apple, :persisted?
assert_equal apple, final_cut.firm
final_cut.association(:firm).reload
assert_equal apple, final_cut.firm
@@ -382,7 +382,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
auditlog.developer = invalid_developer
auditlog.developer_id = valid_developer.id
- assert auditlog.valid?
+ assert_predicate auditlog, :valid?
end
end
@@ -395,8 +395,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
molecule.electrons = [valid_electron, invalid_electron]
molecule.save
- assert_not invalid_electron.valid?
- assert valid_electron.valid?
+ assert_not_predicate invalid_electron, :valid?
+ assert_predicate valid_electron, :valid?
assert_not molecule.persisted?, "Molecule should not be persisted when its electrons are invalid"
end
@@ -408,9 +408,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid]
- assert_not tuning_peg_invalid.valid?
- assert tuning_peg_valid.valid?
- assert_not guitar.valid?
+ assert_not_predicate tuning_peg_invalid, :valid?
+ assert_predicate tuning_peg_valid, :valid?
+ assert_not_predicate guitar, :valid?
assert_equal ["is not a number"], guitar.errors["tuning_pegs[1].pitch"]
assert_not_equal ["is not a number"], guitar.errors["tuning_pegs.pitch"]
end
@@ -425,9 +425,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
molecule.electrons = [valid_electron, invalid_electron]
- assert_not invalid_electron.valid?
- assert valid_electron.valid?
- assert_not molecule.valid?
+ assert_not_predicate invalid_electron, :valid?
+ assert_predicate valid_electron, :valid?
+ assert_not_predicate molecule, :valid?
assert_equal ["can't be blank"], molecule.errors["electrons[1].name"]
assert_not_equal ["can't be blank"], molecule.errors["electrons.name"]
ensure
@@ -441,9 +441,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
molecule.electrons = [valid_electron, invalid_electron]
- assert_not invalid_electron.valid?
- assert valid_electron.valid?
- assert_not molecule.valid?
+ assert_not_predicate invalid_electron, :valid?
+ assert_predicate valid_electron, :valid?
+ assert_not_predicate molecule, :valid?
assert_equal [{ error: :blank }], molecule.errors.details[:"electrons.name"]
end
@@ -455,9 +455,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
guitar.tuning_pegs = [tuning_peg_valid, tuning_peg_invalid]
- assert_not tuning_peg_invalid.valid?
- assert tuning_peg_valid.valid?
- assert_not guitar.valid?
+ assert_not_predicate tuning_peg_invalid, :valid?
+ assert_predicate tuning_peg_valid, :valid?
+ assert_not_predicate guitar, :valid?
assert_equal [{ error: :not_a_number, value: nil }], guitar.errors.details[:"tuning_pegs[1].pitch"]
assert_equal [], guitar.errors.details[:"tuning_pegs.pitch"]
end
@@ -472,9 +472,9 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
molecule.electrons = [valid_electron, invalid_electron]
- assert_not invalid_electron.valid?
- assert valid_electron.valid?
- assert_not molecule.valid?
+ assert_not_predicate invalid_electron, :valid?
+ assert_predicate valid_electron, :valid?
+ assert_not_predicate molecule, :valid?
assert_equal [{ error: :blank }], molecule.errors.details[:"electrons[1].name"]
assert_equal [], molecule.errors.details[:"electrons.name"]
ensure
@@ -488,8 +488,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttrib
molecule.electrons = [valid_electron]
molecule.save
- assert valid_electron.valid?
- assert molecule.persisted?
+ assert_predicate valid_electron, :valid?
+ assert_predicate molecule, :persisted?
assert_equal 1, molecule.electrons.count
end
end
@@ -500,21 +500,21 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_invalid_adding
firm = Firm.find(1)
assert !(firm.clients_of_firm << c = Client.new)
- assert !c.persisted?
- assert !firm.valid?
+ assert_not_predicate c, :persisted?
+ assert_not_predicate firm, :valid?
assert !firm.save
- assert !c.persisted?
+ assert_not_predicate c, :persisted?
end
def test_invalid_adding_before_save
new_firm = Firm.new("name" => "A New Firm, Inc")
new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
- assert !c.persisted?
- assert !c.valid?
- assert !new_firm.valid?
+ assert_not_predicate c, :persisted?
+ assert_not_predicate c, :valid?
+ assert_not_predicate new_firm, :valid?
assert !new_firm.save
- assert !c.persisted?
- assert !new_firm.persisted?
+ assert_not_predicate c, :persisted?
+ assert_not_predicate new_firm, :persisted?
end
def test_invalid_adding_with_validate_false
@@ -522,10 +522,10 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
client = Client.new
firm.unvalidated_clients_of_firm << client
- assert firm.valid?
- assert !client.valid?
+ assert_predicate firm, :valid?
+ assert_not_predicate client, :valid?
assert firm.save
- assert !client.persisted?
+ assert_not_predicate client, :persisted?
end
def test_valid_adding_with_validate_false
@@ -534,24 +534,24 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
firm = Firm.first
client = Client.new("name" => "Apple")
- assert firm.valid?
- assert client.valid?
- assert !client.persisted?
+ assert_predicate firm, :valid?
+ assert_predicate client, :valid?
+ assert_not_predicate client, :persisted?
firm.unvalidated_clients_of_firm << client
assert firm.save
- assert client.persisted?
+ assert_predicate client, :persisted?
assert_equal no_of_clients + 1, Client.count
end
def test_invalid_build
new_client = companies(:first_firm).clients_of_firm.build
- assert !new_client.persisted?
- assert !new_client.valid?
+ assert_not_predicate new_client, :persisted?
+ assert_not_predicate new_client, :valid?
assert_equal new_client, companies(:first_firm).clients_of_firm.last
assert !companies(:first_firm).save
- assert !new_client.persisted?
+ assert_not_predicate new_client, :persisted?
assert_equal 2, companies(:first_firm).clients_of_firm.reload.size
end
@@ -570,8 +570,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
assert_equal no_of_firms, Firm.count # Firm was not saved to database.
assert_equal no_of_clients, Client.count # Clients were not saved to database.
assert new_firm.save
- assert new_firm.persisted?
- assert c.persisted?
+ assert_predicate new_firm, :persisted?
+ assert_predicate c, :persisted?
assert_equal new_firm, c.firm
assert_equal no_of_firms + 1, Firm.count # Firm was saved to database.
assert_equal no_of_clients + 2, Client.count # Clients were saved to database.
@@ -601,11 +601,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_before_save
company = companies(:first_firm)
new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build("name" => "Another Client") }
- assert !company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
company.name += "-changed"
assert_queries(2) { assert company.save }
- assert new_client.persisted?
+ assert_predicate new_client, :persisted?
assert_equal 3, company.clients_of_firm.reload.size
end
@@ -621,11 +621,11 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_build_via_block_before_save
company = companies(:first_firm)
new_client = assert_no_queries(ignore_none: false) { company.clients_of_firm.build { |client| client.name = "Another Client" } }
- assert !company.clients_of_firm.loaded?
+ assert_not_predicate company.clients_of_firm, :loaded?
company.name += "-changed"
assert_queries(2) { assert company.save }
- assert new_client.persisted?
+ assert_predicate new_client, :persisted?
assert_equal 3, company.clients_of_firm.reload.size
end
@@ -657,62 +657,62 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase
new_account = Account.new("credit_limit" => 1000)
new_firm = Firm.new("name" => "some firm")
- assert !new_firm.persisted?
+ assert_not_predicate new_firm, :persisted?
new_account.firm = new_firm
new_account.save!
- assert new_firm.persisted?
+ assert_predicate new_firm, :persisted?
new_account = Account.new("credit_limit" => 1000)
new_autosaved_firm = Firm.new("name" => "some firm")
- assert !new_autosaved_firm.persisted?
+ assert_not_predicate new_autosaved_firm, :persisted?
new_account.unautosaved_firm = new_autosaved_firm
new_account.save!
- assert !new_autosaved_firm.persisted?
+ assert_not_predicate new_autosaved_firm, :persisted?
end
def test_autosave_new_record_on_has_one_can_be_disabled_per_relationship
firm = Firm.new("name" => "some firm")
account = Account.new("credit_limit" => 1000)
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
firm.account = account
firm.save!
- assert account.persisted?
+ assert_predicate account, :persisted?
firm = Firm.new("name" => "some firm")
account = Account.new("credit_limit" => 1000)
firm.unautosaved_account = account
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
firm.unautosaved_account = account
firm.save!
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
end
def test_autosave_new_record_on_has_many_can_be_disabled_per_relationship
firm = Firm.new("name" => "some firm")
account = Account.new("credit_limit" => 1000)
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
firm.accounts << account
firm.save!
- assert account.persisted?
+ assert_predicate account, :persisted?
firm = Firm.new("name" => "some firm")
account = Account.new("credit_limit" => 1000)
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
firm.unautosaved_accounts << account
firm.save!
- assert !account.persisted?
+ assert_not_predicate account, :persisted?
end
def test_autosave_new_record_with_after_create_callback
@@ -745,18 +745,18 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.mark_for_destruction
@pirate.ship.mark_for_destruction
- assert !@pirate.reload.marked_for_destruction?
- assert !@pirate.ship.reload.marked_for_destruction?
+ assert_not_predicate @pirate.reload, :marked_for_destruction?
+ assert_not_predicate @pirate.ship.reload, :marked_for_destruction?
end
# has_one
def test_should_destroy_a_child_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction
- assert !@pirate.ship.marked_for_destruction?
+ assert_not_predicate @pirate.ship, :marked_for_destruction?
@pirate.ship.mark_for_destruction
id = @pirate.ship.id
- assert @pirate.ship.marked_for_destruction?
+ assert_predicate @pirate.ship, :marked_for_destruction?
assert Ship.find_by_id(id)
@pirate.save
@@ -766,7 +766,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_should_skip_validation_on_a_child_association_if_marked_for_destruction
@pirate.ship.name = ""
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
@pirate.ship.mark_for_destruction
@pirate.ship.expects(:valid?).never
@@ -812,12 +812,12 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
# belongs_to
def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destruction
- assert !@ship.pirate.marked_for_destruction?
+ assert_not_predicate @ship.pirate, :marked_for_destruction?
@ship.pirate.mark_for_destruction
id = @ship.pirate.id
- assert @ship.pirate.marked_for_destruction?
+ assert_predicate @ship.pirate, :marked_for_destruction?
assert Pirate.find_by_id(id)
@ship.save
@@ -827,7 +827,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_should_skip_validation_on_a_parent_association_if_marked_for_destruction
@ship.pirate.catchphrase = ""
- assert !@ship.valid?
+ assert_not_predicate @ship, :valid?
@ship.pirate.mark_for_destruction
@ship.pirate.expects(:valid?).never
@@ -881,7 +881,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
ids.each { |id| assert klass.find_by_id(id) }
@pirate.save
- assert @pirate.reload.birds.empty?
+ assert_empty @pirate.reload.birds
ids.each { |id| assert_nil klass.find_by_id(id) }
end
@@ -889,14 +889,14 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.birds.create!(name: :parrot)
@pirate.birds.first.destroy
@pirate.save!
- assert @pirate.reload.birds.empty?
+ assert_empty @pirate.reload.birds
end
def test_should_skip_validation_on_has_many_if_marked_for_destruction
2.times { |i| @pirate.birds.create!(name: "birds_#{i}") }
@pirate.birds.each { |bird| bird.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
@pirate.birds.each do |bird|
bird.mark_for_destruction
@@ -909,10 +909,10 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.birds.create!(name: "birds_1")
@pirate.birds.each { |bird| bird.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
@pirate.birds.each(&:destroy)
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
end
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_has_many
@@ -1010,17 +1010,17 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
@pirate.save
end
- assert @pirate.reload.parrots.empty?
+ assert_empty @pirate.reload.parrots
join_records = Pirate.connection.select_all("SELECT * FROM parrots_pirates WHERE pirate_id = #{@pirate.id}")
- assert join_records.empty?
+ assert_empty join_records
end
def test_should_skip_validation_on_habtm_if_marked_for_destruction
2.times { |i| @pirate.parrots.create!(name: "parrots_#{i}") }
@pirate.parrots.each { |parrot| parrot.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
@pirate.parrots.each do |parrot|
parrot.mark_for_destruction
@@ -1028,17 +1028,17 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
@pirate.save!
- assert @pirate.reload.parrots.empty?
+ assert_empty @pirate.reload.parrots
end
def test_should_skip_validation_on_habtm_if_destroyed
@pirate.parrots.create!(name: "parrots_1")
@pirate.parrots.each { |parrot| parrot.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
@pirate.parrots.each(&:destroy)
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
end
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice_while_saving_habtm
@@ -1145,16 +1145,16 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@pirate.ship.name = ""
- assert @pirate.invalid?
- assert @pirate.errors[:"ship.name"].any?
+ assert_predicate @pirate, :invalid?
+ assert_predicate @pirate.errors[:"ship.name"], :any?
end
def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid
@pirate.ship.name = nil
@pirate.catchphrase = nil
- assert @pirate.invalid?
- assert @pirate.errors[:"ship.name"].any?
- assert @pirate.errors[:catchphrase].any?
+ assert_predicate @pirate, :invalid?
+ assert_predicate @pirate.errors[:"ship.name"], :any?
+ assert_predicate @pirate.errors[:catchphrase], :any?
end
def test_should_not_ignore_different_error_messages_on_the_same_attribute
@@ -1163,7 +1163,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
Ship.validates_format_of :name, with: /\w/
@pirate.ship.name = ""
@pirate.catchphrase = nil
- assert @pirate.invalid?
+ assert_predicate @pirate, :invalid?
assert_equal ["can't be blank", "is invalid"], @pirate.errors[:"ship.name"]
ensure
Ship._validators = old_validators if old_validators
@@ -1244,7 +1244,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
ship = ShipWithoutNestedAttributes.new(name: "The Black Flag")
ship.parts.build.mark_for_destruction
- assert_not ship.valid?
+ assert_not_predicate ship, :valid?
end
end
@@ -1299,16 +1299,16 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_automatically_validate_the_associated_model
@ship.pirate.catchphrase = ""
- assert @ship.invalid?
- assert @ship.errors[:"pirate.catchphrase"].any?
+ assert_predicate @ship, :invalid?
+ assert_predicate @ship.errors[:"pirate.catchphrase"], :any?
end
def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid
@ship.name = nil
@ship.pirate.catchphrase = nil
- assert @ship.invalid?
- assert @ship.errors[:name].any?
- assert @ship.errors[:"pirate.catchphrase"].any?
+ assert_predicate @ship, :invalid?
+ assert_predicate @ship.errors[:name], :any?
+ assert_predicate @ship.errors[:"pirate.catchphrase"], :any?
end
def test_should_still_allow_to_bypass_validations_on_the_associated_model
@@ -1403,17 +1403,17 @@ module AutosaveAssociationOnACollectionAssociationTests
def test_should_automatically_validate_the_associated_models
@pirate.send(@association_name).each { |child| child.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
- assert @pirate.errors[@association_name].empty?
+ assert_empty @pirate.errors[@association_name]
end
def test_should_not_use_default_invalid_error_on_associated_models
@pirate.send(@association_name).build(name: "")
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
- assert @pirate.errors[@association_name].empty?
+ assert_empty @pirate.errors[@association_name]
end
def test_should_default_invalid_error_from_i18n
@@ -1423,10 +1423,10 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).build(name: "")
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"]
assert_equal ["#{@association_name.to_s.humanize} name cannot be blank"], @pirate.errors.full_messages
- assert @pirate.errors[@association_name].empty?
+ assert_empty @pirate.errors[@association_name]
ensure
I18n.backend = I18n::Backend::Simple.new
end
@@ -1435,9 +1435,9 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).each { |child| child.name = "" }
@pirate.catchphrase = nil
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"]
- assert @pirate.errors[:catchphrase].any?
+ assert_predicate @pirate.errors[:catchphrase], :any?
end
def test_should_allow_to_bypass_validations_on_the_associated_models_on_update
@@ -1595,10 +1595,10 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te
end
test "should automatically validate associations" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.birds.each { |bird| bird.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
end
end
@@ -1613,15 +1613,15 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes
end
test "should automatically validate associations with :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.ship.name = ""
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
end
test "should not automatically add validate associations without :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.non_validated_ship.name = ""
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
end
end
@@ -1634,15 +1634,15 @@ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::
end
test "should automatically validate associations with :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.parrot = Parrot.new(name: "")
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
end
test "should not automatically validate associations without :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.non_validated_parrot = Parrot.new(name: "")
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
end
end
@@ -1655,17 +1655,17 @@ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::Test
end
test "should automatically validate associations with :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.parrots = [ Parrot.new(name: "popuga") ]
@pirate.parrots.each { |parrot| parrot.name = "" }
- assert !@pirate.valid?
+ assert_not_predicate @pirate, :valid?
end
test "should not automatically validate associations without :validate => true" do
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
@pirate.non_validated_parrots = [ Parrot.new(name: "popuga") ]
@pirate.non_validated_parrots.each { |parrot| parrot.name = "" }
- assert @pirate.valid?
+ assert_predicate @pirate, :valid?
end
end
@@ -1686,7 +1686,7 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas
end
test "should not generate validation methods for has_one associations without :validate => true" do
- assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_ship)
+ assert_not_respond_to @pirate, :validate_associated_records_for_non_validated_ship
end
test "should generate validation methods for belongs_to associations with :validate => true" do
@@ -1694,7 +1694,7 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas
end
test "should not generate validation methods for belongs_to associations without :validate => true" do
- assert !@pirate.respond_to?(:validate_associated_records_for_non_validated_parrot)
+ assert_not_respond_to @pirate, :validate_associated_records_for_non_validated_parrot
end
test "should generate validation methods for HABTM associations with :validate => true" do
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index f0ef522515..983a3d366a 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -104,7 +104,7 @@ class BasicsTest < ActiveRecord::TestCase
pk = Author.columns_hash["id"]
ref = Post.columns_hash["author_id"]
- assert_equal pk.bigint?, ref.bigint?
+ assert_equal pk.sql_type, ref.sql_type
end
def test_many_mutations
@@ -147,8 +147,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_table_exists
- assert !NonExistentTable.table_exists?
- assert Topic.table_exists?
+ assert_not_predicate NonExistentTable, :table_exists?
+ assert_predicate Topic, :table_exists?
end
def test_preserving_date_objects
@@ -450,7 +450,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_default_values
topic = Topic.new
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_nil topic.written_on
assert_nil topic.bonus_time
assert_nil topic.last_read
@@ -458,7 +458,7 @@ class BasicsTest < ActiveRecord::TestCase
topic.save
topic = Topic.find(topic.id)
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_nil topic.last_read
# Oracle has some funky default handling, so it requires a bit of
@@ -629,7 +629,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_readonly_attributes
- assert_equal Set.new([ "title" , "comments_count" ]), ReadonlyTitlePost.readonly_attributes
+ assert_equal Set.new([ "title", "comments_count" ]), ReadonlyTitlePost.readonly_attributes
post = ReadonlyTitlePost.create(title: "cannot change this", body: "changeable")
post.reload
@@ -727,9 +727,9 @@ class BasicsTest < ActiveRecord::TestCase
b_nil = Boolean.find(nil_id)
assert_nil b_nil.value
b_false = Boolean.find(false_id)
- assert !b_false.value?
+ assert_not_predicate b_false, :value?
b_true = Boolean.find(true_id)
- assert b_true.value?
+ assert_predicate b_true, :value?
end
def test_boolean_without_questionmark
@@ -753,9 +753,9 @@ class BasicsTest < ActiveRecord::TestCase
b_blank = Boolean.find(blank_id)
assert_nil b_blank.value
b_false = Boolean.find(false_id)
- assert !b_false.value?
+ assert_not_predicate b_false, :value?
b_true = Boolean.find(true_id)
- assert b_true.value?
+ assert_predicate b_true, :value?
end
def test_new_record_returns_boolean
@@ -768,7 +768,7 @@ class BasicsTest < ActiveRecord::TestCase
duped_topic = nil
assert_nothing_raised { duped_topic = topic.dup }
assert_equal topic.title, duped_topic.title
- assert !duped_topic.persisted?
+ assert_not_predicate duped_topic, :persisted?
# test if the attributes have been duped
topic.title = "a"
@@ -786,7 +786,7 @@ class BasicsTest < ActiveRecord::TestCase
# test if saved clone object differs from original
duped_topic.save
- assert duped_topic.persisted?
+ assert_predicate duped_topic, :persisted?
assert_not_equal duped_topic.id, topic.id
duped_topic.reload
@@ -807,7 +807,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_nothing_raised { dup = dev.dup }
assert_kind_of DeveloperSalary, dup.salary
assert_equal dev.salary.amount, dup.salary.amount
- assert !dup.persisted?
+ assert_not_predicate dup, :persisted?
# test if the attributes have been duped
original_amount = dup.salary.amount
@@ -815,7 +815,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal original_amount, dup.salary.amount
assert dup.save
- assert dup.persisted?
+ assert_predicate dup, :persisted?
assert_not_equal dup.id, dev.id
end
@@ -835,22 +835,22 @@ class BasicsTest < ActiveRecord::TestCase
def test_clone_of_new_object_with_defaults
developer = Developer.new
- assert !developer.name_changed?
- assert !developer.salary_changed?
+ assert_not_predicate developer, :name_changed?
+ assert_not_predicate developer, :salary_changed?
cloned_developer = developer.clone
- assert !cloned_developer.name_changed?
- assert !cloned_developer.salary_changed?
+ assert_not_predicate cloned_developer, :name_changed?
+ assert_not_predicate cloned_developer, :salary_changed?
end
def test_clone_of_new_object_marks_attributes_as_dirty
developer = Developer.new name: "Bjorn", salary: 100000
- assert developer.name_changed?
- assert developer.salary_changed?
+ assert_predicate developer, :name_changed?
+ assert_predicate developer, :salary_changed?
cloned_developer = developer.clone
- assert cloned_developer.name_changed?
- assert cloned_developer.salary_changed?
+ assert_predicate cloned_developer, :name_changed?
+ assert_predicate cloned_developer, :salary_changed?
end
def test_clone_of_new_object_marks_as_dirty_only_changed_attributes
@@ -859,24 +859,24 @@ class BasicsTest < ActiveRecord::TestCase
assert !developer.salary_changed? # attribute has non-nil default value, so treated as not changed
cloned_developer = developer.clone
- assert cloned_developer.name_changed?
+ assert_predicate cloned_developer, :name_changed?
assert !cloned_developer.salary_changed? # ... and cloned instance should behave same
end
def test_dup_of_saved_object_marks_attributes_as_dirty
developer = Developer.create! name: "Bjorn", salary: 100000
- assert !developer.name_changed?
- assert !developer.salary_changed?
+ assert_not_predicate developer, :name_changed?
+ assert_not_predicate developer, :salary_changed?
cloned_developer = developer.dup
assert cloned_developer.name_changed? # both attributes differ from defaults
- assert cloned_developer.salary_changed?
+ assert_predicate cloned_developer, :salary_changed?
end
def test_dup_of_saved_object_marks_as_dirty_only_changed_attributes
developer = Developer.create! name: "Bjorn"
assert !developer.name_changed? # both attributes of saved object should be treated as not changed
- assert !developer.salary_changed?
+ assert_not_predicate developer, :salary_changed?
cloned_developer = developer.dup
assert cloned_developer.name_changed? # ... but on cloned object should be
@@ -891,11 +891,9 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal 2147483648, company.rating
end
- unless current_adapter?(:SQLite3Adapter)
- def test_bignum_pk
- company = Company.create!(id: 2147483648, name: "foo")
- assert_equal company, Company.find(company.id)
- end
+ def test_bignum_pk
+ company = Company.create!(id: 2147483648, name: "foo")
+ assert_equal company, Company.find(company.id)
end
# TODO: extend defaults tests to other databases!
@@ -953,14 +951,14 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_toggle_attribute
- assert !topics(:first).approved?
+ assert_not_predicate topics(:first), :approved?
topics(:first).toggle!(:approved)
- assert topics(:first).approved?
+ assert_predicate topics(:first), :approved?
topic = topics(:first)
topic.toggle(:approved)
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
topic.reload
- assert topic.approved?
+ assert_predicate topic, :approved?
end
def test_reload
@@ -1433,11 +1431,11 @@ class BasicsTest < ActiveRecord::TestCase
test "resetting column information doesn't remove attribute methods" do
topic = topics(:first)
- assert_not topic.id_changed?
+ assert_not_predicate topic, :id_changed?
Topic.reset_column_information
- assert_not topic.id_changed?
+ assert_not_predicate topic, :id_changed?
end
test "ignored columns are not present in columns_hash" do
@@ -1449,31 +1447,76 @@ class BasicsTest < ActiveRecord::TestCase
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?)
+ assert_not_respond_to Developer.new, :first_name
+ assert_not_respond_to Developer.new, :first_name=
+ assert_not_respond_to Developer.new, :first_name?
+ assert_not_respond_to SubDeveloper.new, :first_name
+ assert_not_respond_to SubDeveloper.new, :first_name=
+ assert_not_respond_to SubDeveloper.new, :first_name?
+ assert_not_respond_to SymbolIgnoredDeveloper.new, :first_name
+ assert_not_respond_to SymbolIgnoredDeveloper.new, :first_name=
+ assert_not_respond_to SymbolIgnoredDeveloper.new, :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?)
+ assert_respond_to Developer.new, :last_name
+ assert_respond_to Developer.new, :last_name=
+ assert_respond_to Developer.new, :last_name?
+ assert_respond_to SubDeveloper.new, :last_name
+ assert_respond_to SubDeveloper.new, :last_name=
+ assert_respond_to SubDeveloper.new, :last_name?
+ assert_respond_to SymbolIgnoredDeveloper.new, :last_name
+ assert_respond_to SymbolIgnoredDeveloper.new, :last_name=
+ assert_respond_to SymbolIgnoredDeveloper.new, :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
+
+ test "when #reload called, ignored columns' attribute methods are not defined" do
+ developer = Developer.create!(name: "Developer")
+ assert_not_respond_to developer, :first_name
+ assert_not_respond_to developer, :first_name=
+
+ developer.reload
+
+ assert_not_respond_to developer, :first_name
+ assert_not_respond_to developer, :first_name=
+ end
+
+ test "ignored columns not included in SELECT" do
+ query = Developer.all.to_sql.downcase
+
+ # ignored column
+ assert_not query.include?("first_name")
+
+ # regular column
+ assert query.include?("name")
+ end
+
+ test "column names are quoted when using #from clause and model has ignored columns" do
+ refute_empty Developer.ignored_columns
+ query = Developer.from("developers").to_sql
+ quoted_id = "#{Developer.quoted_table_name}.#{Developer.quoted_primary_key}"
+
+ assert_match(/SELECT #{quoted_id}.* FROM developers/, query)
+ end
+
+ test "using table name qualified column names unless having SELECT list explicitly" do
+ assert_equal developers(:david), Developer.from("developers").joins(:shared_computers).take
+ end
+
+ test "protected environments by default is an array with production" do
+ assert_equal ["production"], ActiveRecord::Base.protected_environments
+ end
+
+ def test_protected_environments_are_stored_as_an_array_of_string
+ previous_protected_environments = ActiveRecord::Base.protected_environments
+ ActiveRecord::Base.protected_environments = [:staging, "production"]
+ assert_equal ["staging", "production"], ActiveRecord::Base.protected_environments
+ ensure
+ ActiveRecord::Base.protected_environments = previous_protected_environments
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index be8aeed5ac..ad701fb200 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -313,7 +313,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_each_record_should_yield_record_if_block_is_given
assert_queries(6) do
Post.in_batches(of: 2).each_record do |post|
- assert post.title.present?
+ assert_predicate post.title, :present?
assert_kind_of Post, post
end
end
@@ -322,7 +322,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_each_record_should_return_enumerator_if_no_block_given
assert_queries(6) do
Post.in_batches(of: 2).each_record.with_index do |post, i|
- assert post.title.present?
+ assert_predicate post.title, :present?
assert_kind_of Post, post
end
end
@@ -353,24 +353,24 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_not_be_loaded
Post.in_batches(of: 1) do |relation|
- assert_not relation.loaded?
+ assert_not_predicate relation, :loaded?
end
Post.in_batches(of: 1, load: false) do |relation|
- assert_not relation.loaded?
+ assert_not_predicate relation, :loaded?
end
end
def test_in_batches_should_be_loaded
Post.in_batches(of: 1, load: true) do |relation|
- assert relation.loaded?
+ assert_predicate relation, :loaded?
end
end
def test_in_batches_if_not_loaded_executes_more_queries
assert_queries(@total + 1) do
Post.in_batches(of: 1, load: false) do |relation|
- assert_not relation.loaded?
+ assert_not_predicate relation, :loaded?
end
end
end
@@ -592,7 +592,11 @@ class EachTest < ActiveRecord::TestCase
table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias)
predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
- posts = ActiveRecord::Relation.create(Post, table_alias, predicate_builder)
+ posts = ActiveRecord::Relation.create(
+ Post,
+ table: table_alias,
+ predicate_builder: predicate_builder
+ )
posts.find_each {}
end
end
diff --git a/activerecord/test/cases/cache_key_test.rb b/activerecord/test/cases/cache_key_test.rb
index 8f2f2c6186..3a569f226e 100644
--- a/activerecord/test/cases/cache_key_test.rb
+++ b/activerecord/test/cases/cache_key_test.rb
@@ -38,8 +38,8 @@ module ActiveRecord
end
test "cache_version is only there when versioning is on" do
- assert CacheMeWithVersion.create.cache_version.present?
- assert_not CacheMe.create.cache_version.present?
+ assert_predicate CacheMeWithVersion.create.cache_version, :present?
+ assert_not_predicate CacheMe.create.cache_version, :present?
end
test "cache_key_with_version always has both key and version" do
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 66bc14b5ab..ec8bc913a1 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -21,7 +21,7 @@ require "models/comment"
require "models/rating"
class CalculationsTest < ActiveRecord::TestCase
- fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books
+ fixtures :companies, :accounts, :topics, :speedometers, :minivans, :books, :posts, :comments
def test_should_sum_field
assert_equal 318, Account.sum(:credit_limit)
@@ -236,6 +236,18 @@ class CalculationsTest < ActiveRecord::TestCase
end
end
+ def test_count_with_eager_loading_and_custom_order
+ posts = Post.includes(:comments).order("comments.id")
+ assert_queries(1) { assert_equal 11, posts.count }
+ assert_queries(1) { assert_equal 11, posts.count(:all) }
+ end
+
+ def test_distinct_count_all_with_custom_select_and_order
+ accounts = Account.distinct.select("credit_limit % 10").order(Arel.sql("credit_limit % 10"))
+ assert_queries(1) { assert_equal 3, accounts.count(:all) }
+ assert_queries(1) { assert_equal 3, accounts.load.size }
+ end
+
def test_distinct_count_with_order_and_limit
assert_equal 4, Account.distinct.order(:firm_id).limit(4).count
end
@@ -663,14 +675,14 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_pluck_with_selection_clause
- assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT credit_limit").sort
- assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT accounts.credit_limit").sort
- assert_equal [50, 53, 55, 60], Account.pluck("DISTINCT(credit_limit)").sort
+ assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT credit_limit")).sort
+ assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT accounts.credit_limit")).sort
+ assert_equal [50, 53, 55, 60], Account.pluck(Arel.sql("DISTINCT(credit_limit)")).sort
# MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless
# an alias is provided. Without the alias, the column cannot be found
# and properly typecast.
- assert_equal [50 + 53 + 55 + 60], Account.pluck("SUM(DISTINCT(credit_limit)) as credit_limit")
+ assert_equal [50 + 53 + 55 + 60], Account.pluck(Arel.sql("SUM(DISTINCT(credit_limit)) as credit_limit"))
end
def test_plucks_with_ids
@@ -682,6 +694,11 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [], Topic.includes(:replies).limit(1).where("0 = 1").pluck(:id)
end
+ def test_pluck_with_includes_offset
+ assert_equal [5], Topic.includes(:replies).order(:id).offset(4).pluck(:id)
+ assert_equal [], Topic.includes(:replies).order(:id).offset(5).pluck(:id)
+ end
+
def test_pluck_not_auto_table_name_prefix_if_column_included
Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])
ids = Company.includes(:contracts).pluck(:developer_id)
@@ -772,10 +789,20 @@ class CalculationsTest < ActiveRecord::TestCase
companies = Company.order(:name).limit(3).load
assert_queries 1 do
- assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck("DISTINCT name")
+ assert_equal ["37signals", "Apex", "Ex Nihilo"], companies.pluck(Arel.sql("DISTINCT name"))
end
end
+ def test_pick_one
+ assert_equal "The First Topic", Topic.order(:id).pick(:heading)
+ assert_nil Topic.where("1=0").pick(:heading)
+ end
+
+ def test_pick_two
+ assert_equal ["David", "david@loudthinking.com"], Topic.order(:id).pick(:author_name, :author_email_address)
+ assert_nil Topic.where("1=0").pick(:author_name, :author_email_address)
+ end
+
def test_grouped_calculation_with_polymorphic_relation
part = ShipPart.create!(name: "has trinket")
part.trinkets.create!
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 55c7475f46..3b283a3aa6 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -394,27 +394,27 @@ class CallbacksTest < ActiveRecord::TestCase
def test_before_create_throwing_abort
someone = CallbackHaltedDeveloper.new
someone.cancel_before_create = true
- assert someone.valid?
+ assert_predicate someone, :valid?
assert !someone.save
assert_save_callbacks_not_called(someone)
end
def test_before_save_throwing_abort
david = DeveloperWithCanceledCallbacks.find(1)
- assert david.valid?
+ assert_predicate david, :valid?
assert !david.save
exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
assert_equal david, exc.record
david = DeveloperWithCanceledCallbacks.find(1)
david.salary = 10_000_000
- assert !david.valid?
+ assert_not_predicate david, :valid?
assert !david.save
assert_raise(ActiveRecord::RecordInvalid) { david.save! }
someone = CallbackHaltedDeveloper.find(1)
someone.cancel_before_save = true
- assert someone.valid?
+ assert_predicate someone, :valid?
assert !someone.save
assert_save_callbacks_not_called(someone)
end
@@ -422,7 +422,7 @@ class CallbacksTest < ActiveRecord::TestCase
def test_before_update_throwing_abort
someone = CallbackHaltedDeveloper.find(1)
someone.cancel_before_update = true
- assert someone.valid?
+ assert_predicate someone, :valid?
assert !someone.save
assert_save_callbacks_not_called(someone)
end
diff --git a/activerecord/test/cases/clone_test.rb b/activerecord/test/cases/clone_test.rb
index 3187e6aed5..65e5016040 100644
--- a/activerecord/test/cases/clone_test.rb
+++ b/activerecord/test/cases/clone_test.rb
@@ -36,7 +36,7 @@ module ActiveRecord
cloned = Topic.new
clone = cloned.clone
cloned.freeze
- assert_not clone.frozen?
+ assert_not_predicate clone, :frozen?
end
end
end
diff --git a/activerecord/test/cases/collection_cache_key_test.rb b/activerecord/test/cases/collection_cache_key_test.rb
index 19d6464a22..a5d908344a 100644
--- a/activerecord/test/cases/collection_cache_key_test.rb
+++ b/activerecord/test/cases/collection_cache_key_test.rb
@@ -24,7 +24,7 @@ module ActiveRecord
/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
- assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
+ assert_equal ActiveSupport::Digest.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
@@ -37,7 +37,7 @@ module ActiveRecord
/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
- assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
+ assert_equal ActiveSupport::Digest.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
@@ -50,7 +50,7 @@ module ActiveRecord
/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
- assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
+ assert_equal ActiveSupport::Digest.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
@@ -60,7 +60,11 @@ module ActiveRecord
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 = ActiveRecord::Relation.create(
+ Developer,
+ table: table_alias,
+ predicate_builder: predicate_builder
+ )
developers = developers.where(salary: 100000).order(updated_at: :desc)
last_developer_timestamp = developers.first.updated_at
@@ -68,7 +72,7 @@ module ActiveRecord
/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/ =~ developers.cache_key
- assert_equal Digest::MD5.hexdigest(developers.to_sql), $1
+ assert_equal ActiveSupport::Digest.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
@@ -141,5 +145,17 @@ module ActiveRecord
assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
end
+
+ test "cache_key with a relation having distinct and order" do
+ developers = Developer.distinct.order(:salary).limit(5)
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
+ end
+
+ test "cache_key with a relation having custom select and order" do
+ developers = Developer.select("name AS dev_name").order("dev_name DESC").limit(5)
+
+ assert_match(/\Adevelopers\/query-(\h+)-(\d+)-(\d+)\z/, developers.cache_key)
+ end
end
end
diff --git a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
index 82c6cf8dea..72838ff56b 100644
--- a/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
+++ b/activerecord/test/cases/connection_adapters/adapter_leasing_test.rb
@@ -45,11 +45,11 @@ module ActiveRecord
# Make sure the pool marks the connection in use
assert_equal @adapter, pool.connection
- assert @adapter.in_use?
+ assert_predicate @adapter, :in_use?
# Close should put the adapter back in the pool
@adapter.close
- assert_not @adapter.in_use?
+ assert_not_predicate @adapter, :in_use?
assert_equal @adapter, pool.connection
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 74d0ed348e..f4cc251fb9 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -1,10 +1,15 @@
# frozen_string_literal: true
require "cases/helper"
+require "models/person"
module ActiveRecord
module ConnectionAdapters
class ConnectionHandlerTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ fixtures :people
+
def setup
@handler = ConnectionHandler.new
@spec_name = "primary"
@@ -98,11 +103,11 @@ module ActiveRecord
end
def test_active_connections?
- assert !@handler.active_connections?
+ assert_not_predicate @handler, :active_connections?
assert @handler.retrieve_connection(@spec_name)
- assert @handler.active_connections?
+ assert_predicate @handler, :active_connections?
@handler.clear_active_connections!
- assert !@handler.active_connections?
+ assert_not_predicate @handler, :active_connections?
end
def test_retrieve_connection_pool
@@ -139,6 +144,75 @@ module ActiveRecord
rd.close
end
+ def test_forked_child_doesnt_mangle_parent_connection
+ object_id = ActiveRecord::Base.connection.object_id
+ assert_predicate ActiveRecord::Base.connection, :active?
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ pid = fork {
+ rd.close
+ if ActiveRecord::Base.connection.active?
+ wr.write Marshal.dump ActiveRecord::Base.connection.object_id
+ end
+ wr.close
+
+ exit # allow finalizers to run
+ }
+
+ wr.close
+
+ Process.waitpid pid
+ assert_not_equal object_id, Marshal.load(rd.read)
+ rd.close
+
+ assert_equal 3, ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people")
+ end
+
+ unless in_memory_db?
+ def test_forked_child_recovers_from_disconnected_parent
+ object_id = ActiveRecord::Base.connection.object_id
+ assert_predicate ActiveRecord::Base.connection, :active?
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ outer_pid = fork {
+ ActiveRecord::Base.connection.disconnect!
+
+ pid = fork {
+ rd.close
+ if ActiveRecord::Base.connection.active?
+ pair = [ActiveRecord::Base.connection.object_id,
+ ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM people")]
+ wr.write Marshal.dump pair
+ end
+ wr.close
+
+ exit # allow finalizers to run
+ }
+
+ Process.waitpid pid
+ }
+
+ wr.close
+
+ Process.waitpid outer_pid
+ child_id, child_count = Marshal.load(rd.read)
+
+ assert_not_equal object_id, child_id
+ rd.close
+
+ assert_equal 3, child_count
+
+ # Outer connection is unaffected
+ assert_equal 6, ActiveRecord::Base.connection.select_value("SELECT 2 * COUNT(*) FROM people")
+ end
+ end
+
def test_retrieve_connection_pool_copies_schema_cache_from_ancestor_pool
@pool.schema_cache = @pool.connection.schema_cache
@pool.schema_cache.add("posts")
@@ -200,15 +274,15 @@ module ActiveRecord
def test_a_class_using_custom_pool_and_switching_back_to_primary
klass2 = Class.new(Base) { def self.name; "klass2"; end }
- assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ assert_same klass2.connection, ActiveRecord::Base.connection
pool = klass2.establish_connection(ActiveRecord::Base.connection_pool.spec.config)
- assert_equal klass2.connection.object_id, pool.connection.object_id
- refute_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ assert_same klass2.connection, pool.connection
+ refute_same klass2.connection, ActiveRecord::Base.connection
klass2.remove_connection
- assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ assert_same klass2.connection, ActiveRecord::Base.connection
end
def test_connection_specification_name_should_fallback_to_parent
@@ -223,8 +297,8 @@ module ActiveRecord
def test_remove_connection_should_not_remove_parent
klass2 = Class.new(Base) { def self.name; "klass2"; end }
klass2.remove_connection
- refute_nil ActiveRecord::Base.connection.object_id
- assert_equal klass2.connection.object_id, ActiveRecord::Base.connection.object_id
+ refute_nil ActiveRecord::Base.connection
+ assert_same klass2.connection, ActiveRecord::Base.connection
end
end
end
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
index 006be9e65d..67496381d1 100644
--- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -22,8 +22,8 @@ module ActiveRecord
new_cache = YAML.load(YAML.dump(@cache))
assert_no_queries do
- assert_equal 11, new_cache.columns("posts").size
- assert_equal 11, new_cache.columns_hash("posts").size
+ assert_equal 12, new_cache.columns("posts").size
+ assert_equal 12, new_cache.columns_hash("posts").size
assert new_cache.data_sources("posts")
assert_equal "id", new_cache.primary_keys("posts")
end
@@ -75,8 +75,8 @@ module ActiveRecord
@cache = Marshal.load(Marshal.dump(@cache))
assert_no_queries do
- assert_equal 11, @cache.columns("posts").size
- assert_equal 11, @cache.columns_hash("posts").size
+ assert_equal 12, @cache.columns("posts").size
+ assert_equal 12, @cache.columns_hash("posts").size
assert @cache.data_sources("posts")
assert_equal "id", @cache.primary_keys("posts")
end
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
index 917a04ebc3..1c79d776f0 100644
--- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -82,11 +82,11 @@ unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strin
end
def test_bigint_limit
- cast_type = @connection.send(:type_map).lookup("bigint")
+ limit = @connection.send(:type_map).lookup("bigint").send(:_limit)
if current_adapter?(:OracleAdapter)
- assert_equal 19, cast_type.limit
+ assert_equal 19, limit
else
- assert_equal 8, cast_type.limit
+ assert_equal 8, limit
end
end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index 9d6ecbde78..0941ee3309 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -27,7 +27,7 @@ module ActiveRecord
# make sure we have an active connection
assert ActiveRecord::Base.connection
- assert ActiveRecord::Base.connection_handler.active_connections?
+ assert_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
def test_app_delegation
@@ -47,14 +47,14 @@ module ActiveRecord
def test_connections_are_cleared_after_body_close
_, _, body = @management.call(@env)
body.close
- assert !ActiveRecord::Base.connection_handler.active_connections?
+ assert_not_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
def test_active_connections_are_not_cleared_on_body_close_during_transaction
ActiveRecord::Base.transaction do
_, _, body = @management.call(@env)
body.close
- assert ActiveRecord::Base.connection_handler.active_connections?
+ assert_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
end
@@ -62,7 +62,7 @@ module ActiveRecord
app = Class.new(App) { def call(env); raise NotImplementedError; end }.new
explosive = middleware(app)
assert_raises(NotImplementedError) { explosive.call(@env) }
- assert !ActiveRecord::Base.connection_handler.active_connections?
+ assert_not_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
def test_connections_not_closed_if_exception_inside_transaction
@@ -70,14 +70,14 @@ module ActiveRecord
app = Class.new(App) { def call(env); raise RuntimeError; end }.new
explosive = middleware(app)
assert_raises(RuntimeError) { explosive.call(@env) }
- assert ActiveRecord::Base.connection_handler.active_connections?
+ assert_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
end
test "doesn't clear active connections when running in a test case" do
executor.wrap do
@management.call(@env)
- assert ActiveRecord::Base.connection_handler.active_connections?
+ assert_predicate ActiveRecord::Base.connection_handler, :active_connections?
end
end
@@ -85,7 +85,7 @@ module ActiveRecord
body = Class.new(String) { def to_path; "/path"; end }.new
app = lambda { |_| [200, {}, body] }
response_body = middleware(app).call(@env)[2]
- assert response_body.respond_to?(:to_path)
+ assert_respond_to response_body, :to_path
assert_equal "/path", response_body.to_path
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 2bfe490602..bfaaa3c54e 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -35,12 +35,12 @@ module ActiveRecord
def test_checkout_after_close
connection = pool.connection
- assert connection.in_use?
+ assert_predicate connection, :in_use?
connection.close
- assert !connection.in_use?
+ assert_not_predicate connection, :in_use?
- assert pool.connection.in_use?
+ assert_predicate pool.connection, :in_use?
end
def test_released_connection_moves_between_threads
@@ -80,18 +80,18 @@ module ActiveRecord
end
def test_active_connection_in_use
- assert !pool.active_connection?
+ assert_not_predicate pool, :active_connection?
main_thread = pool.connection
- assert pool.active_connection?
+ assert_predicate pool, :active_connection?
main_thread.close
- assert !pool.active_connection?
+ assert_not_predicate pool, :active_connection?
end
def test_full_pool_exception
- @pool.size.times { @pool.checkout }
+ @pool.size.times { assert @pool.checkout }
assert_raises(ConnectionTimeoutError) do
@pool.checkout
end
@@ -156,13 +156,60 @@ module ActiveRecord
@pool.connections.each { |conn| conn.close if conn.in_use? }
end
+ def test_flush
+ idle_conn = @pool.checkout
+ recent_conn = @pool.checkout
+ active_conn = @pool.checkout
+
+ @pool.checkin idle_conn
+ @pool.checkin recent_conn
+
+ assert_equal 3, @pool.connections.length
+
+ def idle_conn.seconds_idle
+ 1000
+ end
+
+ @pool.flush(30)
+
+ assert_equal 2, @pool.connections.length
+
+ assert_equal [recent_conn, active_conn].sort_by(&:__id__), @pool.connections.sort_by(&:__id__)
+ ensure
+ @pool.checkin active_conn
+ end
+
+ def test_flush_bang
+ idle_conn = @pool.checkout
+ recent_conn = @pool.checkout
+ active_conn = @pool.checkout
+ _dead_conn = Thread.new { @pool.checkout }.join
+
+ @pool.checkin idle_conn
+ @pool.checkin recent_conn
+
+ assert_equal 4, @pool.connections.length
+
+ def idle_conn.seconds_idle
+ 1000
+ end
+
+ @pool.flush!
+
+ assert_equal 1, @pool.connections.length
+
+ assert_equal [active_conn].sort_by(&:__id__), @pool.connections.sort_by(&:__id__)
+ ensure
+ @pool.checkin active_conn
+ end
+
def test_remove_connection
conn = @pool.checkout
- assert conn.in_use?
+ assert_predicate conn, :in_use?
length = @pool.connections.length
@pool.remove conn
- assert conn.in_use?
+ assert_predicate conn, :in_use?
assert_equal(length - 1, @pool.connections.length)
ensure
conn.close
@@ -177,11 +224,11 @@ module ActiveRecord
end
def test_active_connection?
- assert !@pool.active_connection?
+ assert_not_predicate @pool, :active_connection?
assert @pool.connection
- assert @pool.active_connection?
+ assert_predicate @pool, :active_connection?
@pool.release_connection
- assert !@pool.active_connection?
+ assert_not_predicate @pool, :active_connection?
end
def test_checkout_behaviour
@@ -205,6 +252,14 @@ module ActiveRecord
end.join
end
+ def test_checkout_order_is_lifo
+ conn1 = @pool.checkout
+ conn2 = @pool.checkout
+ @pool.checkin conn1
+ @pool.checkin conn2
+ assert_equal [conn2, conn1], 2.times.map { @pool.checkout }
+ end
+
# The connection pool is "fair" if threads waiting for
# connections receive them in the order in which they began
# waiting. This ensures that we don't timeout one HTTP request
@@ -414,6 +469,7 @@ module ActiveRecord
end
def test_non_bang_disconnect_and_clear_reloadable_connections_throw_exception_if_threads_dont_return_their_conns
+ Thread.report_on_exception, original_report_on_exception = false, Thread.report_on_exception if Thread.respond_to?(:report_on_exception)
@pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout
[:disconnect, :clear_reloadable_connections].each do |group_action_method|
@pool.with_connection do |connection|
@@ -422,6 +478,8 @@ module ActiveRecord
end
end
end
+ ensure
+ Thread.report_on_exception = original_report_on_exception if Thread.respond_to?(:report_on_exception)
end
def test_disconnect_and_clear_reloadable_connections_attempt_to_wait_for_threads_to_return_their_conns
@@ -438,7 +496,7 @@ module ActiveRecord
assert_nil timed_join_result
# assert that since this is within default timeout our connection hasn't been forcefully taken away from us
- assert @pool.active_connection?
+ assert_predicate @pool, :active_connection?
end
ensure
thread.join if thread && !timed_join_result # clean up the other thread
@@ -452,7 +510,7 @@ module ActiveRecord
@pool.with_connection do |connection|
Thread.new { @pool.send(group_action_method) }.join
# assert connection has been forcefully taken away from us
- assert_not @pool.active_connection?
+ assert_not_predicate @pool, :active_connection?
# make a new connection for with_connection to clean up
@pool.connection
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 4690682cd8..3d11b573f1 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -53,7 +53,7 @@ class DefaultNumbersTest < ActiveRecord::TestCase
def test_default_decimal_number
record = DefaultNumber.new
- assert_equal BigDecimal.new("2.78"), record.decimal_number
+ assert_equal BigDecimal("2.78"), record.decimal_number
assert_equal "2.78", record.decimal_number_before_type_cast
end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index a602f83d8c..14c4e3cbd4 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -24,18 +24,18 @@ class DirtyTest < ActiveRecord::TestCase
# Change catchphrase.
pirate.catchphrase = "arrr"
- assert pirate.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
assert_nil pirate.catchphrase_was
assert_equal [nil, "arrr"], pirate.catchphrase_change
# Saved - no changes.
pirate.save!
- assert !pirate.catchphrase_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
assert_nil pirate.catchphrase_change
# Same value - no changes.
pirate.catchphrase = "arrr"
- assert !pirate.catchphrase_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
assert_nil pirate.catchphrase_change
end
@@ -46,23 +46,23 @@ class DirtyTest < ActiveRecord::TestCase
# New record - no changes.
pirate = target.new
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Saved - no changes.
pirate.catchphrase = "arrrr, time zone!!"
pirate.save!
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Change created_on.
old_created_on = pirate.created_on
pirate.created_on = Time.now - 1.day
- assert pirate.created_on_changed?
+ assert_predicate pirate, :created_on_changed?
assert_kind_of ActiveSupport::TimeWithZone, pirate.created_on_was
assert_equal old_created_on, pirate.created_on_was
pirate.created_on = old_created_on
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
end
end
@@ -73,7 +73,7 @@ class DirtyTest < ActiveRecord::TestCase
pirate = target.create!
pirate.created_on = pirate.created_on
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
end
end
@@ -86,19 +86,19 @@ class DirtyTest < ActiveRecord::TestCase
# New record - no changes.
pirate = target.new
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Saved - no changes.
pirate.catchphrase = "arrrr, time zone!!"
pirate.save!
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Change created_on.
old_created_on = pirate.created_on
pirate.created_on = Time.now + 1.day
- assert pirate.created_on_changed?
+ assert_predicate pirate, :created_on_changed?
# kind_of does not work because
# ActiveSupport::TimeWithZone.name == 'Time'
assert_instance_of Time, pirate.created_on_was
@@ -113,19 +113,19 @@ class DirtyTest < ActiveRecord::TestCase
# New record - no changes.
pirate = target.new
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Saved - no changes.
pirate.catchphrase = "arrrr, time zone!!"
pirate.save!
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
assert_nil pirate.created_on_change
# Change created_on.
old_created_on = pirate.created_on
pirate.created_on = Time.now + 1.day
- assert pirate.created_on_changed?
+ assert_predicate pirate, :created_on_changed?
# kind_of does not work because
# ActiveSupport::TimeWithZone.name == 'Time'
assert_instance_of Time, pirate.created_on_was
@@ -137,11 +137,11 @@ class DirtyTest < ActiveRecord::TestCase
# the actual attribute here is name, title is an
# alias setup via alias_attribute
parrot = Parrot.new
- assert !parrot.title_changed?
+ assert_not_predicate parrot, :title_changed?
assert_nil parrot.title_change
parrot.name = "Sam"
- assert parrot.title_changed?
+ assert_predicate parrot, :title_changed?
assert_nil parrot.title_was
assert_equal parrot.name_change, parrot.title_change
end
@@ -153,7 +153,7 @@ class DirtyTest < ActiveRecord::TestCase
pirate.restore_catchphrase!
assert_equal "Yar!", pirate.catchphrase
assert_equal Hash.new, pirate.changes
- assert !pirate.catchphrase_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
end
def test_nullable_number_not_marked_as_changed_if_new_value_is_blank
@@ -161,7 +161,7 @@ class DirtyTest < ActiveRecord::TestCase
["", nil].each do |value|
pirate.parrot_id = value
- assert !pirate.parrot_id_changed?
+ assert_not_predicate pirate, :parrot_id_changed?
assert_nil pirate.parrot_id_change
end
end
@@ -171,7 +171,7 @@ class DirtyTest < ActiveRecord::TestCase
["", nil].each do |value|
numeric_data.bank_balance = value
- assert !numeric_data.bank_balance_changed?
+ assert_not_predicate numeric_data, :bank_balance_changed?
assert_nil numeric_data.bank_balance_change
end
end
@@ -181,7 +181,7 @@ class DirtyTest < ActiveRecord::TestCase
["", nil].each do |value|
numeric_data.temperature = value
- assert !numeric_data.temperature_changed?
+ assert_not_predicate numeric_data, :temperature_changed?
assert_nil numeric_data.temperature_change
end
end
@@ -197,7 +197,7 @@ class DirtyTest < ActiveRecord::TestCase
["", nil].each do |value|
topic.written_on = value
assert_nil topic.written_on
- assert !topic.written_on_changed?
+ assert_not_predicate topic, :written_on_changed?
end
end
end
@@ -208,10 +208,10 @@ class DirtyTest < ActiveRecord::TestCase
pirate.catchphrase = "arrr"
assert pirate.save!
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
pirate.parrot_id = "0"
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
end
def test_integer_zero_to_integer_zero_not_marked_as_changed
@@ -220,17 +220,17 @@ class DirtyTest < ActiveRecord::TestCase
pirate.catchphrase = "arrr"
assert pirate.save!
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
pirate.parrot_id = 0
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
end
def test_float_zero_to_string_zero_not_marked_as_changed
data = NumericData.new temperature: 0.0
data.save!
- assert_not data.changed?
+ assert_not_predicate data, :changed?
data.temperature = "0"
assert_empty data.changes
@@ -251,38 +251,38 @@ class DirtyTest < ActiveRecord::TestCase
# check the change from 1 to ''
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
pirate.parrot_id = ""
- assert pirate.parrot_id_changed?
+ assert_predicate pirate, :parrot_id_changed?
assert_equal([1, nil], pirate.parrot_id_change)
pirate.save
# check the change from nil to 0
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
pirate.parrot_id = 0
- assert pirate.parrot_id_changed?
+ assert_predicate pirate, :parrot_id_changed?
assert_equal([nil, 0], pirate.parrot_id_change)
pirate.save
# check the change from 0 to ''
pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
pirate.parrot_id = ""
- assert pirate.parrot_id_changed?
+ assert_predicate pirate, :parrot_id_changed?
assert_equal([0, nil], pirate.parrot_id_change)
end
def test_object_should_be_changed_if_any_attribute_is_changed
pirate = Pirate.new
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
assert_equal [], pirate.changed
assert_equal Hash.new, pirate.changes
pirate.catchphrase = "arrr"
- assert pirate.changed?
+ assert_predicate pirate, :changed?
assert_nil pirate.catchphrase_was
assert_equal %w(catchphrase), pirate.changed
assert_equal({ "catchphrase" => [nil, "arrr"] }, pirate.changes)
pirate.save
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
assert_equal [], pirate.changed
assert_equal Hash.new, pirate.changes
end
@@ -290,40 +290,40 @@ class DirtyTest < ActiveRecord::TestCase
def test_attribute_will_change!
pirate = Pirate.create!(catchphrase: "arr")
- assert !pirate.catchphrase_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
assert pirate.catchphrase_will_change!
- assert pirate.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
assert_equal ["arr", "arr"], pirate.catchphrase_change
pirate.catchphrase << " matey!"
- assert pirate.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
assert_equal ["arr", "arr matey!"], pirate.catchphrase_change
end
def test_virtual_attribute_will_change
parrot = Parrot.create!(name: "Ruby")
parrot.send(:attribute_will_change!, :cancel_save_from_callback)
- assert parrot.has_changes_to_save?
+ assert_predicate parrot, :has_changes_to_save?
end
def test_association_assignment_changes_foreign_key
pirate = Pirate.create!(catchphrase: "jarl")
pirate.parrot = Parrot.create!(name: "Lorre")
- assert pirate.changed?
+ assert_predicate pirate, :changed?
assert_equal %w(parrot_id), pirate.changed
end
def test_attribute_should_be_compared_with_type_cast
topic = Topic.new
- assert topic.approved?
- assert !topic.approved_changed?
+ assert_predicate topic, :approved?
+ assert_not_predicate topic, :approved_changed?
# Coming from web form.
params = { topic: { approved: 1 } }
# In the controller.
topic.attributes = params[:topic]
- assert topic.approved?
- assert !topic.approved_changed?
+ assert_predicate topic, :approved?
+ assert_not_predicate topic, :approved_changed?
end
def test_partial_update
@@ -378,9 +378,9 @@ class DirtyTest < ActiveRecord::TestCase
def test_reload_should_clear_changed_attributes
pirate = Pirate.create!(catchphrase: "shiver me timbers")
pirate.catchphrase = "*hic*"
- assert pirate.changed?
+ assert_predicate pirate, :changed?
pirate.reload
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
end
def test_dup_objects_should_not_copy_dirty_flag_from_creator
@@ -388,17 +388,17 @@ class DirtyTest < ActiveRecord::TestCase
pirate_dup = pirate.dup
pirate_dup.restore_catchphrase!
pirate.catchphrase = "I love Rum"
- assert pirate.catchphrase_changed?
- assert !pirate_dup.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
+ assert_not_predicate pirate_dup, :catchphrase_changed?
end
def test_reverted_changes_are_not_dirty
phrase = "shiver me timbers"
pirate = Pirate.create!(catchphrase: phrase)
pirate.catchphrase = "*hic*"
- assert pirate.changed?
+ assert_predicate pirate, :changed?
pirate.catchphrase = phrase
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
end
def test_reverted_changes_are_not_dirty_after_multiple_changes
@@ -406,40 +406,40 @@ class DirtyTest < ActiveRecord::TestCase
pirate = Pirate.create!(catchphrase: phrase)
10.times do |i|
pirate.catchphrase = "*hic*" * i
- assert pirate.changed?
+ assert_predicate pirate, :changed?
end
- assert pirate.changed?
+ assert_predicate pirate, :changed?
pirate.catchphrase = phrase
- assert !pirate.changed?
+ assert_not_predicate pirate, :changed?
end
def test_reverted_changes_are_not_dirty_going_from_nil_to_value_and_back
pirate = Pirate.create!(catchphrase: "Yar!")
pirate.parrot_id = 1
- assert pirate.changed?
- assert pirate.parrot_id_changed?
- assert !pirate.catchphrase_changed?
+ assert_predicate pirate, :changed?
+ assert_predicate pirate, :parrot_id_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
pirate.parrot_id = nil
- assert !pirate.changed?
- assert !pirate.parrot_id_changed?
- assert !pirate.catchphrase_changed?
+ assert_not_predicate pirate, :changed?
+ assert_not_predicate pirate, :parrot_id_changed?
+ assert_not_predicate pirate, :catchphrase_changed?
end
def test_save_should_store_serialized_attributes_even_with_partial_writes
with_partial_writes(Topic) do
topic = Topic.create!(content: { a: "a" })
- assert_not topic.changed?
+ assert_not_predicate topic, :changed?
topic.content[:b] = "b"
- assert topic.changed?
+ assert_predicate topic, :changed?
topic.save!
- assert_not topic.changed?
+ assert_not_predicate topic, :changed?
assert_equal "b", topic.content[:b]
topic.reload
@@ -596,7 +596,7 @@ class DirtyTest < ActiveRecord::TestCase
pirate = Pirate.create!(catchphrase: "rrrr", created_on: time_in_paris)
pirate.created_on = pirate.created_on.in_time_zone("Tokyo").to_s
- assert !pirate.created_on_changed?
+ assert_not_predicate pirate, :created_on_changed?
end
test "partial insert" do
@@ -627,7 +627,7 @@ class DirtyTest < ActiveRecord::TestCase
pirate = Pirate.create!(catchphrase: "arrrr")
pirate.catchphrase << " matey!"
- assert pirate.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
expected_changes = {
"catchphrase" => ["arrrr", "arrrr matey!"]
}
@@ -641,7 +641,7 @@ class DirtyTest < ActiveRecord::TestCase
pirate.reload
assert_equal "arrrr matey!", pirate.catchphrase
- assert_not pirate.changed?
+ assert_not_predicate pirate, :changed?
end
test "in place mutation for binary" do
@@ -652,19 +652,19 @@ class DirtyTest < ActiveRecord::TestCase
binary = klass.create!(data: "\\\\foo")
- assert_not binary.changed?
+ assert_not_predicate binary, :changed?
binary.data = binary.data.dup
- assert_not binary.changed?
+ assert_not_predicate binary, :changed?
binary = klass.last
- assert_not binary.changed?
+ assert_not_predicate binary, :changed?
binary.data << "bar"
- assert binary.changed?
+ assert_predicate binary, :changed?
end
test "changes is correct for subclass" do
@@ -679,7 +679,7 @@ class DirtyTest < ActiveRecord::TestCase
new_catchphrase = "arrrr matey!"
pirate.catchphrase = new_catchphrase
- assert pirate.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
expected_changes = {
"catchphrase" => ["arrrr", new_catchphrase]
@@ -698,7 +698,7 @@ class DirtyTest < ActiveRecord::TestCase
new_catchphrase = "arrrr matey!"
pirate.catchphrase = new_catchphrase
- assert pirate.catchphrase_changed?
+ assert_predicate pirate, :catchphrase_changed?
expected_changes = {
"catchphrase" => ["arrrr", new_catchphrase]
@@ -720,7 +720,7 @@ class DirtyTest < ActiveRecord::TestCase
end
model = klass.new(first_name: "Jim")
- assert model.first_name_changed?
+ assert_predicate model, :first_name_changed?
end
test "attribute_will_change! doesn't try to save non-persistable attributes" do
@@ -732,7 +732,7 @@ class DirtyTest < ActiveRecord::TestCase
record = klass.new(first_name: "Sean")
record.non_persisted_attribute_will_change!
- assert record.non_persisted_attribute_changed?
+ assert_predicate record, :non_persisted_attribute_changed?
assert record.save
end
@@ -762,26 +762,33 @@ class DirtyTest < ActiveRecord::TestCase
test "attributes assigned but not selected are dirty" do
person = Person.select(:id).first
- refute person.changed?
+ assert_not_predicate person, :changed?
person.first_name = "Sean"
- assert person.changed?
+ assert_predicate person, :changed?
person.first_name = nil
- assert person.changed?
+ assert_predicate person, :changed?
+ end
+
+ test "attributes not selected are still missing after save" do
+ person = Person.select(:id).first
+ assert_raises(ActiveModel::MissingAttributeError) { person.first_name }
+ assert person.save # calls forget_attribute_assignments
+ assert_raises(ActiveModel::MissingAttributeError) { person.first_name }
end
test "saved_change_to_attribute? returns whether a change occurred in the last save" do
person = Person.create!(first_name: "Sean")
- assert person.saved_change_to_first_name?
- refute person.saved_change_to_gender?
+ assert_predicate person, :saved_change_to_first_name?
+ assert_not_predicate person, :saved_change_to_gender?
assert person.saved_change_to_first_name?(from: nil, to: "Sean")
assert person.saved_change_to_first_name?(from: nil)
assert person.saved_change_to_first_name?(to: "Sean")
- refute person.saved_change_to_first_name?(from: "Jim", to: "Sean")
- refute person.saved_change_to_first_name?(from: "Jim")
- refute person.saved_change_to_first_name?(to: "Jim")
+ assert_not person.saved_change_to_first_name?(from: "Jim", to: "Sean")
+ assert_not person.saved_change_to_first_name?(from: "Jim")
+ assert_not person.saved_change_to_first_name?(to: "Jim")
end
test "saved_change_to_attribute returns the change that occurred in the last save" do
@@ -816,11 +823,11 @@ class DirtyTest < ActiveRecord::TestCase
test "saved_changes? returns whether the last call to save changed anything" do
person = Person.create!(first_name: "Sean")
- assert person.saved_changes?
+ assert_predicate person, :saved_changes?
person.save
- refute person.saved_changes?
+ assert_not_predicate person, :saved_changes?
end
test "saved_changes returns a hash of all the changes that occurred" do
@@ -850,7 +857,7 @@ class DirtyTest < ActiveRecord::TestCase
end
person = klass.create!(first_name: "Sean")
- refute person.changed?
+ assert_not_predicate person, :changed?
end
private
@@ -863,8 +870,8 @@ class DirtyTest < ActiveRecord::TestCase
end
def check_pirate_after_save_failure(pirate)
- assert pirate.changed?
- assert pirate.parrot_id_changed?
+ assert_predicate pirate, :changed?
+ assert_predicate pirate, :parrot_id_changed?
assert_equal %w(parrot_id), pirate.changed
assert_nil pirate.parrot_id_was
end
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index 2fefdbf204..9e33c3110c 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -3,13 +3,14 @@
require "cases/helper"
require "models/reply"
require "models/topic"
+require "models/movie"
module ActiveRecord
class DupTest < ActiveRecord::TestCase
fixtures :topics
def test_dup
- assert !Topic.new.freeze.dup.frozen?
+ assert_not_predicate Topic.new.freeze.dup, :frozen?
end
def test_not_readonly
@@ -40,7 +41,7 @@ module ActiveRecord
topic.destroy
duped = topic.dup
- assert_not duped.destroyed?
+ assert_not_predicate duped, :destroyed?
end
def test_dup_has_no_id
@@ -62,10 +63,10 @@ module ActiveRecord
topic.attributes = dbtopic.attributes.except("id")
- #duped has no timestamp values
+ # duped has no timestamp values
duped = dbtopic.dup
- #clear topic timestamp values
+ # clear topic timestamp values
topic.send(:clear_timestamp_attributes)
assert_equal topic.changes, duped.changes
@@ -100,7 +101,7 @@ module ActiveRecord
# temporary change to the topic object
topic.updated_at -= 3.days
- #dup should not preserve the timestamps if present
+ # dup should not preserve the timestamps if present
new_topic = topic.dup
assert_nil new_topic.updated_at
assert_nil new_topic.created_at
@@ -126,12 +127,12 @@ module ActiveRecord
duped = topic.dup
duped.title = nil
- assert duped.invalid?
+ assert_predicate duped, :invalid?
topic.title = nil
duped.title = "Mathematics"
- assert topic.invalid?
- assert duped.valid?
+ assert_predicate topic, :invalid?
+ assert_predicate duped, :valid?
end
end
@@ -157,5 +158,20 @@ module ActiveRecord
record.dup
end
end
+
+ def test_dup_record_not_persisted_after_rollback_transaction
+ movie = Movie.new(name: "test")
+
+ assert_raises(ActiveRecord::RecordInvalid) do
+ Movie.transaction do
+ movie.save!
+ duped = movie.dup
+ duped.name = nil
+ duped.save!
+ end
+ end
+
+ assert !movie.persisted?
+ end
end
end
diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb
index 78cb89ccc5..d5a1d11e12 100644
--- a/activerecord/test/cases/enum_test.rb
+++ b/activerecord/test/cases/enum_test.rb
@@ -12,16 +12,16 @@ class EnumTest < ActiveRecord::TestCase
end
test "query state by predicate" do
- assert @book.published?
- assert_not @book.written?
- assert_not @book.proposed?
+ assert_predicate @book, :published?
+ assert_not_predicate @book, :written?
+ assert_not_predicate @book, :proposed?
- assert @book.read?
- assert @book.in_english?
- assert @book.author_visibility_visible?
- assert @book.illustrator_visibility_visible?
- assert @book.with_medium_font_size?
- assert @book.medium_to_read?
+ assert_predicate @book, :read?
+ assert_predicate @book, :in_english?
+ assert_predicate @book, :author_visibility_visible?
+ assert_predicate @book, :illustrator_visibility_visible?
+ assert_predicate @book, :with_medium_font_size?
+ assert_predicate @book, :medium_to_read?
end
test "query state with strings" do
@@ -76,46 +76,46 @@ class EnumTest < ActiveRecord::TestCase
end
test "build from scope" do
- assert Book.written.build.written?
- assert_not Book.written.build.proposed?
+ assert_predicate Book.written.build, :written?
+ assert_not_predicate Book.written.build, :proposed?
end
test "build from where" do
- assert Book.where(status: Book.statuses[:written]).build.written?
- assert_not Book.where(status: Book.statuses[:written]).build.proposed?
- assert Book.where(status: :written).build.written?
- assert_not Book.where(status: :written).build.proposed?
- assert Book.where(status: "written").build.written?
- assert_not Book.where(status: "written").build.proposed?
+ assert_predicate Book.where(status: Book.statuses[:written]).build, :written?
+ assert_not_predicate Book.where(status: Book.statuses[:written]).build, :proposed?
+ assert_predicate Book.where(status: :written).build, :written?
+ assert_not_predicate Book.where(status: :written).build, :proposed?
+ assert_predicate Book.where(status: "written").build, :written?
+ assert_not_predicate Book.where(status: "written").build, :proposed?
end
test "update by declaration" do
@book.written!
- assert @book.written?
+ assert_predicate @book, :written?
@book.in_english!
- assert @book.in_english?
+ assert_predicate @book, :in_english?
@book.author_visibility_visible!
- assert @book.author_visibility_visible?
+ assert_predicate @book, :author_visibility_visible?
end
test "update by setter" do
@book.update! status: :written
- assert @book.written?
+ assert_predicate @book, :written?
end
test "enum methods are overwritable" do
assert_equal "do publish work...", @book.published!
- assert @book.published?
+ assert_predicate @book, :published?
end
test "direct assignment" do
@book.status = :written
- assert @book.written?
+ assert_predicate @book, :written?
end
test "assign string value" do
@book.status = "written"
- assert @book.written?
+ assert_predicate @book, :written?
end
test "enum changed attributes" do
@@ -242,17 +242,17 @@ class EnumTest < ActiveRecord::TestCase
end
test "building new objects with enum scopes" do
- assert Book.written.build.written?
- assert Book.read.build.read?
- assert Book.in_spanish.build.in_spanish?
- assert Book.illustrator_visibility_invisible.build.illustrator_visibility_invisible?
+ assert_predicate Book.written.build, :written?
+ assert_predicate Book.read.build, :read?
+ assert_predicate Book.in_spanish.build, :in_spanish?
+ assert_predicate Book.illustrator_visibility_invisible.build, :illustrator_visibility_invisible?
end
test "creating new objects with enum scopes" do
- assert Book.written.create.written?
- assert Book.read.create.read?
- assert Book.in_spanish.create.in_spanish?
- assert Book.illustrator_visibility_invisible.create.illustrator_visibility_invisible?
+ assert_predicate Book.written.create, :written?
+ assert_predicate Book.read.create, :read?
+ assert_predicate Book.in_spanish.create, :in_spanish?
+ assert_predicate Book.illustrator_visibility_invisible.create, :illustrator_visibility_invisible?
end
test "_before_type_cast" do
@@ -308,6 +308,24 @@ class EnumTest < ActiveRecord::TestCase
end
end
+ test "reserved enum values for relation" do
+ relation_method_samples = [
+ :records,
+ :to_ary,
+ :scope_for_create
+ ]
+
+ relation_method_samples.each do |value|
+ e = assert_raises(ArgumentError, "enum value `#{value}` should not be allowed") do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = "books"
+ enum category: [:other, value]
+ end
+ end
+ assert_match(/You tried to define an enum named .* on the model/, e.message)
+ end
+ end
+
test "overriding enum method should not raise" do
assert_nothing_raised do
Class.new(ActiveRecord::Base) do
@@ -337,9 +355,9 @@ class EnumTest < ActiveRecord::TestCase
klass.delete_all
klass.create!(status: "proposed")
book = klass.new(status: "written")
- assert book.valid?
+ assert_predicate book, :valid?
book.status = "proposed"
- assert_not book.valid?
+ assert_not_predicate book, :valid?
end
test "validate inclusion of value in array" do
@@ -350,9 +368,9 @@ class EnumTest < ActiveRecord::TestCase
end
klass.delete_all
invalid_book = klass.new(status: "proposed")
- assert_not invalid_book.valid?
+ assert_not_predicate invalid_book, :valid?
valid_book = klass.new(status: "written")
- assert valid_book.valid?
+ assert_predicate valid_book, :valid?
end
test "enums are distinct per class" do
@@ -399,10 +417,10 @@ class EnumTest < ActiveRecord::TestCase
end
book1 = klass.proposed.create!
- assert book1.proposed?
+ assert_predicate book1, :proposed?
book2 = klass.single.create!
- assert book2.single?
+ assert_predicate book2, :single?
end
test "enum with alias_attribute" do
@@ -413,62 +431,62 @@ class EnumTest < ActiveRecord::TestCase
end
book = klass.proposed.create!
- assert book.proposed?
+ assert_predicate book, :proposed?
assert_equal "proposed", book.aliased_status
book = klass.find(book.id)
- assert book.proposed?
+ assert_predicate book, :proposed?
assert_equal "proposed", book.aliased_status
end
test "query state by predicate with prefix" do
- assert @book.author_visibility_visible?
- assert_not @book.author_visibility_invisible?
- assert @book.illustrator_visibility_visible?
- assert_not @book.illustrator_visibility_invisible?
+ assert_predicate @book, :author_visibility_visible?
+ assert_not_predicate @book, :author_visibility_invisible?
+ assert_predicate @book, :illustrator_visibility_visible?
+ assert_not_predicate @book, :illustrator_visibility_invisible?
end
test "query state by predicate with custom prefix" do
- assert @book.in_english?
- assert_not @book.in_spanish?
- assert_not @book.in_french?
+ assert_predicate @book, :in_english?
+ assert_not_predicate @book, :in_spanish?
+ assert_not_predicate @book, :in_french?
end
test "query state by predicate with custom suffix" do
- assert @book.medium_to_read?
- assert_not @book.easy_to_read?
- assert_not @book.hard_to_read?
+ assert_predicate @book, :medium_to_read?
+ assert_not_predicate @book, :easy_to_read?
+ assert_not_predicate @book, :hard_to_read?
end
test "enum methods with custom suffix defined" do
- assert @book.class.respond_to?(:easy_to_read)
- assert @book.class.respond_to?(:medium_to_read)
- assert @book.class.respond_to?(:hard_to_read)
+ assert_respond_to @book.class, :easy_to_read
+ assert_respond_to @book.class, :medium_to_read
+ assert_respond_to @book.class, :hard_to_read
- assert @book.respond_to?(:easy_to_read?)
- assert @book.respond_to?(:medium_to_read?)
- assert @book.respond_to?(:hard_to_read?)
+ assert_respond_to @book, :easy_to_read?
+ assert_respond_to @book, :medium_to_read?
+ assert_respond_to @book, :hard_to_read?
- assert @book.respond_to?(:easy_to_read!)
- assert @book.respond_to?(:medium_to_read!)
- assert @book.respond_to?(:hard_to_read!)
+ assert_respond_to @book, :easy_to_read!
+ assert_respond_to @book, :medium_to_read!
+ assert_respond_to @book, :hard_to_read!
end
test "update enum attributes with custom suffix" do
@book.medium_to_read!
- assert_not @book.easy_to_read?
- assert @book.medium_to_read?
- assert_not @book.hard_to_read?
+ assert_not_predicate @book, :easy_to_read?
+ assert_predicate @book, :medium_to_read?
+ assert_not_predicate @book, :hard_to_read?
@book.easy_to_read!
- assert @book.easy_to_read?
- assert_not @book.medium_to_read?
- assert_not @book.hard_to_read?
+ assert_predicate @book, :easy_to_read?
+ assert_not_predicate @book, :medium_to_read?
+ assert_not_predicate @book, :hard_to_read?
@book.hard_to_read!
- assert_not @book.easy_to_read?
- assert_not @book.medium_to_read?
- assert @book.hard_to_read?
+ assert_not_predicate @book, :easy_to_read?
+ assert_not_predicate @book, :medium_to_read?
+ assert_predicate @book, :hard_to_read?
end
test "uses default status when no status is provided in fixtures" do
@@ -479,12 +497,12 @@ class EnumTest < ActiveRecord::TestCase
test "uses default value from database on initialization" do
book = Book.new
- assert book.proposed?
+ assert_predicate book, :proposed?
end
test "uses default value from database on initialization when using custom mapping" do
book = Book.new
- assert book.hard?
+ assert_predicate book, :hard?
end
test "data type of Enum type" do
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
index fb698c47cd..82cc891970 100644
--- a/activerecord/test/cases/explain_subscriber_test.rb
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -15,20 +15,20 @@ if ActiveRecord::Base.connection.supports_explain?
def test_collects_nothing_if_the_payload_has_an_exception
SUBSCRIBER.finish(nil, nil, exception: Exception.new)
- assert queries.empty?
+ assert_empty queries
end
def test_collects_nothing_for_ignored_payloads
ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
SUBSCRIBER.finish(nil, nil, name: ip)
end
- assert queries.empty?
+ assert_empty queries
end
def test_collects_nothing_if_collect_is_false
ActiveRecord::ExplainRegistry.collect = false
SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "select 1 from users", binds: [1, 2])
- assert queries.empty?
+ assert_empty queries
end
def test_collects_pairs_of_queries_and_binds
@@ -42,12 +42,12 @@ if ActiveRecord::Base.connection.supports_explain?
def test_collects_nothing_if_the_statement_is_not_whitelisted
SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "SHOW max_identifier_length")
- assert queries.empty?
+ assert_empty queries
end
def test_collects_nothing_if_the_statement_is_only_partially_matched
SUBSCRIBER.finish(nil, nil, name: "SQL", sql: "select_db yo_mama")
- assert queries.empty?
+ assert_empty queries
end
def test_collects_cte_queries
diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb
index 4039af66d0..59af4e6961 100644
--- a/activerecord/test/cases/finder_respond_to_test.rb
+++ b/activerecord/test/cases/finder_respond_to_test.rb
@@ -8,7 +8,7 @@ class FinderRespondToTest < ActiveRecord::TestCase
def test_should_preserve_normal_respond_to_behaviour_on_base
assert_respond_to ActiveRecord::Base, :new
- assert !ActiveRecord::Base.respond_to?(:find_by_something)
+ assert_not_respond_to ActiveRecord::Base, :find_by_something
end
def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method
@@ -43,14 +43,14 @@ class FinderRespondToTest < ActiveRecord::TestCase
end
def test_should_not_respond_to_find_by_one_missing_attribute
- assert !Topic.respond_to?(:find_by_undertitle)
+ assert_not_respond_to Topic, :find_by_undertitle
end
def test_should_not_respond_to_find_by_invalid_method_syntax
- assert !Topic.respond_to?(:fail_to_find_by_title)
- assert !Topic.respond_to?(:find_by_title?)
- assert !Topic.respond_to?(:fail_to_find_or_create_by_title)
- assert !Topic.respond_to?(:find_or_create_by_title?)
+ assert_not_respond_to Topic, :fail_to_find_by_title
+ assert_not_respond_to Topic, :find_by_title?
+ assert_not_respond_to Topic, :fail_to_find_or_create_by_title
+ assert_not_respond_to Topic, :find_or_create_by_title?
end
private
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index d8bc917e7f..b413212e26 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -120,6 +120,21 @@ class FinderTest < ActiveRecord::TestCase
assert_equal "The Fourth Topic of the day", records[2].title
end
+ def test_find_with_ids_with_no_id_passed
+ exception = assert_raises(ActiveRecord::RecordNotFound) { Topic.find }
+ assert_equal exception.model, "Topic"
+ assert_equal exception.primary_key, "id"
+ end
+
+ def test_find_with_ids_with_id_out_of_range
+ exception = assert_raises(ActiveRecord::RecordNotFound) do
+ Topic.find("9999999999999999999999999999999")
+ end
+
+ assert_equal exception.model, "Topic"
+ assert_equal exception.primary_key, "id"
+ end
+
def test_find_passing_active_record_object_is_not_permitted
assert_raises(ArgumentError) do
Topic.find(Topic.last)
@@ -239,7 +254,7 @@ class FinderTest < ActiveRecord::TestCase
# Ensure +exists?+ runs without an error by excluding order value.
def test_exists_with_order
- assert_equal true, Topic.order("invalid sql here").exists?
+ assert_equal true, Topic.order(Arel.sql("invalid sql here")).exists?
end
def test_exists_with_joins
@@ -255,27 +270,27 @@ class FinderTest < ActiveRecord::TestCase
end
def test_exists_with_includes_limit_and_empty_result
- assert_equal false, Topic.includes(:replies).limit(0).exists?
- assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists?
+ assert_no_queries { assert_equal false, Topic.includes(:replies).limit(0).exists? }
+ assert_queries(1) { assert_equal false, Topic.includes(:replies).limit(1).where("0 = 1").exists? }
end
def test_exists_with_distinct_association_includes_and_limit
author = Author.first
- assert_equal false, author.unique_categorized_posts.includes(:special_comments).limit(0).exists?
- assert_equal true, author.unique_categorized_posts.includes(:special_comments).limit(1).exists?
+ unique_categorized_posts = author.unique_categorized_posts.includes(:special_comments)
+ assert_no_queries { assert_equal false, unique_categorized_posts.limit(0).exists? }
+ assert_queries(1) { assert_equal true, unique_categorized_posts.limit(1).exists? }
end
def test_exists_with_distinct_association_includes_limit_and_order
author = Author.first
- assert_equal false, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(0).exists?
- assert_equal true, author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC").limit(1).exists?
+ unique_categorized_posts = author.unique_categorized_posts.includes(:special_comments).order("comments.tags_count DESC")
+ assert_no_queries { assert_equal false, unique_categorized_posts.limit(0).exists? }
+ assert_queries(1) { assert_equal true, unique_categorized_posts.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
+ ratings = developers(:david).ratings.includes(comment: :post).where(posts: { id: 1 })
+ assert_queries(1) { assert_not_predicate ratings.limit(1), :exists? }
end
def test_exists_with_empty_table_and_no_args_given
@@ -550,7 +565,7 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.offset(4).second_to_last
assert_nil Topic.offset(5).second_to_last
- #test with limit
+ # test with limit
assert_nil Topic.limit(1).second
assert_nil Topic.limit(1).second_to_last
end
@@ -633,13 +648,13 @@ class FinderTest < ActiveRecord::TestCase
def test_last_with_integer_and_order_should_use_sql_limit
relation = Topic.order("title")
assert_queries(1) { relation.last(5) }
- assert !relation.loaded?
+ assert_not_predicate relation, :loaded?
end
def test_last_with_integer_and_reorder_should_use_sql_limit
relation = Topic.reorder("title")
assert_queries(1) { relation.last(5) }
- assert !relation.loaded?
+ assert_not_predicate relation, :loaded?
end
def test_last_on_loaded_relation_should_not_use_sql
@@ -652,7 +667,7 @@ class FinderTest < ActiveRecord::TestCase
def test_last_with_irreversible_order
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("coalesce(author_name, title)").last
+ Topic.order(Arel.sql("coalesce(author_name, title)")).last
end
end
@@ -664,12 +679,34 @@ class FinderTest < ActiveRecord::TestCase
assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2)
assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3)
+ assert_equal comments.offset(2).to_a.last, comments.offset(2).last
+ assert_equal comments.offset(2).to_a.last(2), comments.offset(2).last(2)
+ assert_equal comments.offset(2).to_a.last(3), comments.offset(2).last(3)
+
comments = comments.offset(1)
assert_equal comments.limit(2).to_a.last, comments.limit(2).last
assert_equal comments.limit(2).to_a.last(2), comments.limit(2).last(2)
assert_equal comments.limit(2).to_a.last(3), comments.limit(2).last(3)
end
+ def test_first_on_relation_with_limit_and_offset
+ post = posts("sti_comments")
+
+ comments = post.comments.order(id: :asc)
+ assert_equal comments.limit(2).to_a.first, comments.limit(2).first
+ assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2)
+ assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)
+
+ assert_equal comments.offset(2).to_a.first, comments.offset(2).first
+ assert_equal comments.offset(2).to_a.first(2), comments.offset(2).first(2)
+ assert_equal comments.offset(2).to_a.first(3), comments.offset(2).first(3)
+
+ comments = comments.offset(1)
+ assert_equal comments.limit(2).to_a.first, comments.limit(2).first
+ assert_equal comments.limit(2).to_a.first(2), comments.limit(2).first(2)
+ assert_equal comments.limit(2).to_a.first(3), comments.limit(2).first(3)
+ end
+
def test_take_and_first_and_last_with_integer_should_return_an_array
assert_kind_of Array, Topic.take(5)
assert_kind_of Array, Topic.first(5)
@@ -831,6 +868,25 @@ class FinderTest < ActiveRecord::TestCase
assert_equal customers(:david), found_customer
end
+ def test_hash_condition_find_with_aggregate_having_three_mappings_array
+ david_address = customers(:david).address
+ zaphod_address = customers(:zaphod).address
+ barney_address = customers(:barney).address
+ assert_kind_of Address, david_address
+ assert_kind_of Address, zaphod_address
+ found_customers = Customer.where(address: [david_address, zaphod_address, barney_address])
+ assert_equal [customers(:david), customers(:zaphod), customers(:barney)], found_customers.sort_by(&:id)
+ end
+
+ def test_hash_condition_find_with_aggregate_having_one_mapping_array
+ david_balance = customers(:david).balance
+ zaphod_balance = customers(:zaphod).balance
+ assert_kind_of Money, david_balance
+ assert_kind_of Money, zaphod_balance
+ found_customers = Customer.where(balance: [david_balance, zaphod_balance])
+ assert_equal [customers(:david), customers(:zaphod)], found_customers.sort_by(&:id)
+ end
+
def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate
gps_location = customers(:david).gps_location
assert_kind_of GpsLocation, gps_location
@@ -1036,14 +1092,6 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
end
- def test_find_last_with_offset
- devs = Developer.order("id")
-
- assert_equal devs[2], Developer.offset(2).first
- assert_equal devs[-3], Developer.offset(2).last
- assert_equal devs[-3], Developer.offset(2).order("id DESC").first
- end
-
def test_find_by_nil_attribute
topic = Topic.find_by_last_read nil
assert_not_nil topic
@@ -1223,7 +1271,7 @@ class FinderTest < ActiveRecord::TestCase
test "find_by with associations" do
assert_equal authors(:david), Post.find_by(author: authors(:david)).author
- assert_equal authors(:mary) , Post.find_by(author: authors(:mary)).author
+ assert_equal authors(:mary), Post.find_by(author: authors(:mary)).author
end
test "find_by doesn't have implicit ordering" do
@@ -1288,12 +1336,12 @@ class FinderTest < ActiveRecord::TestCase
test "#skip_query_cache! for #exists? with a limited eager load" do
Topic.cache do
- assert_queries(2) do
+ assert_queries(1) do
Topic.eager_load(:replies).limit(1).exists?
Topic.eager_load(:replies).limit(1).exists?
end
- assert_queries(4) do
+ assert_queries(2) do
Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
Topic.eager_load(:replies).limit(1).skip_query_cache!.exists?
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index b0b63f5203..c88b90c8eb 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -79,6 +79,151 @@ class FixturesTest < ActiveRecord::TestCase
ActiveSupport::Notifications.unsubscribe(subscription)
end
end
+
+ def test_bulk_insert_multiple_table_with_a_multi_statement_query
+ subscriber = InsertQuerySubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
+
+ create_fixtures("bulbs", "authors", "computers")
+
+ expected_sql = <<-EOS.strip_heredoc.chop
+ INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("bulbs")} .*
+ INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("authors")} .*
+ INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("computers")} .*
+ EOS
+ assert_equal 1, subscriber.events.size
+ assert_match(/#{expected_sql}/, subscriber.events.first)
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
+ end
+
+ def test_bulk_insert_with_a_multi_statement_query_raises_an_exception_when_any_insert_fails
+ require "models/aircraft"
+
+ assert_equal false, Aircraft.columns_hash["wheels_count"].null
+ fixtures = {
+ "aircraft" => [
+ { "name" => "working_aircrafts", "wheels_count" => 2 },
+ { "name" => "broken_aircrafts", "wheels_count" => nil },
+ ]
+ }
+
+ assert_no_difference "Aircraft.count" do
+ assert_raises(ActiveRecord::NotNullViolation) do
+ ActiveRecord::Base.connection.insert_fixtures_set(fixtures)
+ end
+ end
+ end
+ end
+
+ if current_adapter?(:Mysql2Adapter)
+ def test_insert_fixtures_set_raises_an_error_when_max_allowed_packet_is_smaller_than_fixtures_set_size
+ conn = ActiveRecord::Base.connection
+ mysql_margin = 2
+ packet_size = 1024
+ bytes_needed_to_have_a_1024_bytes_fixture = 858
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a" * bytes_needed_to_have_a_1024_bytes_fixture] },
+ ]
+ }
+
+ conn.stubs(:max_allowed_packet).returns(packet_size - mysql_margin)
+
+ error = assert_raises(ActiveRecord::ActiveRecordError) { conn.insert_fixtures_set(fixtures) }
+ assert_match(/Fixtures set is too large #{packet_size}\./, error.message)
+ end
+
+ def test_insert_fixture_set_when_max_allowed_packet_is_bigger_than_fixtures_set_size
+ conn = ActiveRecord::Base.connection
+ packet_size = 1024
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a" * 51] },
+ ]
+ }
+
+ conn.stubs(:max_allowed_packet).returns(packet_size)
+
+ assert_difference "TrafficLight.count" do
+ conn.insert_fixtures_set(fixtures)
+ end
+ end
+
+ def test_insert_fixtures_set_split_the_total_sql_into_two_chunks_smaller_than_max_allowed_packet
+ subscriber = InsertQuerySubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
+ conn = ActiveRecord::Base.connection
+ packet_size = 1024
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a" * 450] },
+ ],
+ "comments" => [
+ { "post_id" => 1, "body" => "a" * 450 },
+ ]
+ }
+
+ conn.stubs(:max_allowed_packet).returns(packet_size)
+
+ conn.insert_fixtures_set(fixtures)
+ assert_equal 2, subscriber.events.size
+ assert_operator subscriber.events.first.bytesize, :<, packet_size
+ assert_operator subscriber.events.second.bytesize, :<, packet_size
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
+ end
+
+ def test_insert_fixtures_set_concat_total_sql_into_a_single_packet_smaller_than_max_allowed_packet
+ subscriber = InsertQuerySubscriber.new
+ subscription = ActiveSupport::Notifications.subscribe("sql.active_record", subscriber)
+ conn = ActiveRecord::Base.connection
+ packet_size = 1024
+ fixtures = {
+ "traffic_lights" => [
+ { "location" => "US", "state" => ["NY"], "long_state" => ["a" * 200] },
+ ],
+ "comments" => [
+ { "post_id" => 1, "body" => "a" * 200 },
+ ]
+ }
+
+ conn.stubs(:max_allowed_packet).returns(packet_size)
+
+ assert_difference ["TrafficLight.count", "Comment.count"], +1 do
+ conn.insert_fixtures_set(fixtures)
+ end
+ assert_equal 1, subscriber.events.size
+ ensure
+ ActiveSupport::Notifications.unsubscribe(subscription)
+ end
+ end
+
+ def test_auto_value_on_primary_key
+ fixtures = [
+ { "name" => "first", "wheels_count" => 2 },
+ { "name" => "second", "wheels_count" => 3 }
+ ]
+ conn = ActiveRecord::Base.connection
+ assert_nothing_raised do
+ conn.insert_fixtures_set({ "aircraft" => fixtures }, ["aircraft"])
+ end
+ result = conn.select_all("SELECT name, wheels_count FROM aircraft ORDER BY id")
+ assert_equal fixtures, result.to_a
+ end
+
+ def test_deprecated_insert_fixtures
+ fixtures = [
+ { "name" => "first", "wheels_count" => 2 },
+ { "name" => "second", "wheels_count" => 3 }
+ ]
+ conn = ActiveRecord::Base.connection
+ conn.delete("DELETE FROM aircraft")
+ assert_deprecated do
+ conn.insert_fixtures(fixtures, "aircraft")
+ end
+ result = conn.select_all("SELECT name, wheels_count FROM aircraft ORDER BY id")
+ assert_equal fixtures, result.to_a
end
def test_broken_yaml_exception
@@ -247,8 +392,8 @@ class FixturesTest < ActiveRecord::TestCase
def test_nonexistent_fixture_file
nonexistent_fixture_path = FIXTURES_ROOT + "/imnothere"
- #sanity check to make sure that this file never exists
- assert Dir[nonexistent_fixture_path + "*"].empty?
+ # sanity check to make sure that this file never exists
+ assert_empty Dir[nonexistent_fixture_path + "*"]
assert_raise(Errno::ENOENT) do
ActiveRecord::FixtureSet.new(Account.connection, "companies", Company, nonexistent_fixture_path)
@@ -998,10 +1143,10 @@ class FoxyFixturesTest < ActiveRecord::TestCase
end
def test_resolves_enums
- assert books(:awdr).published?
- assert books(:awdr).read?
- assert books(:rfr).proposed?
- assert books(:ddd).published?
+ assert_predicate books(:awdr), :published?
+ assert_predicate books(:awdr), :read?
+ assert_predicate books(:rfr), :proposed?
+ assert_predicate books(:ddd), :published?
end
end
diff --git a/activerecord/test/cases/habtm_destroy_order_test.rb b/activerecord/test/cases/habtm_destroy_order_test.rb
index 5e503272e1..b15e1b48c4 100644
--- a/activerecord/test/cases/habtm_destroy_order_test.rb
+++ b/activerecord/test/cases/habtm_destroy_order_test.rb
@@ -15,7 +15,7 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase
sicp.destroy
end
end
- assert !sicp.destroyed?
+ assert_not_predicate sicp, :destroyed?
end
test "should not raise error if have foreign key in the join table" do
@@ -42,7 +42,7 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase
ben.lessons << sicp
ben.save!
ben.destroy
- assert !ben.reload.lessons.empty?
+ assert_not_empty ben.reload.lessons
ensure
# get rid of it so Student is still like it was
Student.reset_callbacks(:destroy)
@@ -58,6 +58,6 @@ class HabtmDestroyOrderTest < ActiveRecord::TestCase
assert_raises LessonError do
sicp.destroy
end
- assert !sicp.reload.students.empty?
+ assert_not_empty sicp.reload.students
end
end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index c931f7d21c..e3ca79af99 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -130,46 +130,46 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_descends_from_active_record
- assert !ActiveRecord::Base.descends_from_active_record?
+ assert_not_predicate ActiveRecord::Base, :descends_from_active_record?
# Abstract subclass of AR::Base.
- assert LoosePerson.descends_from_active_record?
+ assert_predicate LoosePerson, :descends_from_active_record?
# Concrete subclass of an abstract class.
- assert LooseDescendant.descends_from_active_record?
+ assert_predicate LooseDescendant, :descends_from_active_record?
# Concrete subclass of AR::Base.
- assert TightPerson.descends_from_active_record?
+ assert_predicate TightPerson, :descends_from_active_record?
# Concrete subclass of a concrete class but has no type column.
- assert TightDescendant.descends_from_active_record?
+ assert_predicate TightDescendant, :descends_from_active_record?
# Concrete subclass of AR::Base.
- assert Post.descends_from_active_record?
+ assert_predicate Post, :descends_from_active_record?
# Concrete subclasses of a concrete class which has a type column.
- assert !StiPost.descends_from_active_record?
- assert !SubStiPost.descends_from_active_record?
+ assert_not_predicate StiPost, :descends_from_active_record?
+ assert_not_predicate SubStiPost, :descends_from_active_record?
# Abstract subclass of a concrete class which has a type column.
# This is pathological, as you'll never have Sub < Abstract < Concrete.
- assert !AbstractStiPost.descends_from_active_record?
+ assert_not_predicate AbstractStiPost, :descends_from_active_record?
# Concrete subclass of an abstract class which has a type column.
- assert !SubAbstractStiPost.descends_from_active_record?
+ assert_not_predicate SubAbstractStiPost, :descends_from_active_record?
end
def test_company_descends_from_active_record
- assert !ActiveRecord::Base.descends_from_active_record?
+ assert_not_predicate ActiveRecord::Base, :descends_from_active_record?
assert AbstractCompany.descends_from_active_record?, "AbstractCompany should descend from ActiveRecord::Base"
assert Company.descends_from_active_record?, "Company should descend from ActiveRecord::Base"
assert !Class.new(Company).descends_from_active_record?, "Company subclass should not descend from ActiveRecord::Base"
end
def test_abstract_class
- assert !ActiveRecord::Base.abstract_class?
- assert LoosePerson.abstract_class?
- assert !LooseDescendant.abstract_class?
+ assert_not_predicate ActiveRecord::Base, :abstract_class?
+ assert_predicate LoosePerson, :abstract_class?
+ assert_not_predicate LooseDescendant, :abstract_class?
end
def test_inheritance_base_class
@@ -280,6 +280,21 @@ class InheritanceTest < ActiveRecord::TestCase
assert_equal Firm, firm.class
end
+ def test_where_new_with_subclass
+ firm = Company.where(type: "Firm").new
+ assert_equal Firm, firm.class
+ end
+
+ def test_where_create_with_subclass
+ firm = Company.where(type: "Firm").create(name: "Basecamp")
+ assert_equal Firm, firm.class
+ end
+
+ def test_where_create_bang_with_subclass
+ firm = Company.where(type: "Firm").create!(name: "Basecamp")
+ assert_equal Firm, firm.class
+ end
+
def test_new_with_abstract_class
e = assert_raises(NotImplementedError) do
AbstractCompany.new
@@ -302,6 +317,30 @@ class InheritanceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::SubclassNotFound) { Company.new(type: "Account") }
end
+ def test_where_new_with_invalid_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "InvalidType").new }
+ end
+
+ def test_where_new_with_unrelated_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "Account").new }
+ end
+
+ def test_where_create_with_invalid_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "InvalidType").create }
+ end
+
+ def test_where_create_with_unrelated_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "Account").create }
+ end
+
+ def test_where_create_bang_with_invalid_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "InvalidType").create! }
+ end
+
+ def test_where_create_bang_with_unrelated_type
+ assert_raise(ActiveRecord::SubclassNotFound) { Company.where(type: "Account").create! }
+ end
+
def test_new_with_unrelated_namespaced_type
without_store_full_sti_class do
e = assert_raises ActiveRecord::SubclassNotFound do
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 60c628511f..ebe0b0aa87 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -161,6 +161,15 @@ module ActiveRecord
end
end
+ class UpOnlyMigration < SilentMigration
+ def change
+ add_column :horses, :oldie, :integer, default: 0
+ up_only { execute "update horses set oldie = 1" }
+ end
+ end
+
+ self.use_transactional_tests = false
+
setup do
@verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false
end
@@ -378,5 +387,23 @@ module ActiveRecord
"horses_index_named index should not exist"
end
end
+
+ def test_up_only
+ InvertibleMigration.new.migrate(:up)
+ horse1 = Horse.create
+ # populates existing horses with oldie = 1 but new ones have default 0
+ UpOnlyMigration.new.migrate(:up)
+ Horse.reset_column_information
+ horse1.reload
+ horse2 = Horse.create
+
+ assert 1, horse1.oldie # created before migration
+ assert 0, horse2.oldie # created after migration
+
+ UpOnlyMigration.new.migrate(:down) # should be no error
+ connection = ActiveRecord::Base.connection
+ assert !connection.column_exists?(:horses, :oldie)
+ Horse.reset_column_information
+ end
end
end
diff --git a/activerecord/test/cases/json_attribute_test.rb b/activerecord/test/cases/json_attribute_test.rb
index 63f3c77fc3..afc39d0420 100644
--- a/activerecord/test/cases/json_attribute_test.rb
+++ b/activerecord/test/cases/json_attribute_test.rb
@@ -19,14 +19,14 @@ class JsonAttributeTest < ActiveRecord::TestCase
def setup
super
@connection.create_table("json_data_type") do |t|
- t.text "payload"
- t.text "settings"
+ t.string "payload"
+ t.string "settings"
end
end
private
def column_type
- :text
+ :string
end
def klass
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index 52fe488cd5..82cf281cff 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -252,7 +252,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
def @david.favorite_quote; "Constraints are liberating"; end
json = @david.to_json(include: :posts, methods: :favorite_quote)
- assert !@david.posts.first.respond_to?(:favorite_quote)
+ assert_not_respond_to @david.posts.first, :favorite_quote
assert_match %r{"favorite_quote":"Constraints are liberating"}, json
assert_equal 1, %r{"favorite_quote":}.match(json).size
end
diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb
index a71485982c..c60a276850 100644
--- a/activerecord/test/cases/json_shared_test_cases.rb
+++ b/activerecord/test/cases/json_shared_test_cases.rb
@@ -23,25 +23,23 @@ module JSONSharedTestCases
def test_column
column = klass.columns_hash["payload"]
assert_equal column_type, column.type
- assert_equal column_type.to_s, column.sql_type
+ assert_type_match column_type, column.sql_type
type = klass.type_for_attribute("payload")
- assert_not type.binary?
+ assert_not_predicate type, :binary?
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
klass.reset_column_information
column = klass.columns_hash["users"]
assert_equal column_type, column.type
- assert_equal column_type.to_s, column.sql_type
+ assert_type_match column_type, column.sql_type
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
@@ -68,26 +66,26 @@ module JSONSharedTestCases
end
def test_rewrite
- @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|)
+ @connection.execute(insert_statement_per_database('{"k":"v"}'))
x = klass.first
x.payload = { '"a\'' => "b" }
assert x.save!
end
def test_select
- @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k":"v"}')|)
+ @connection.execute(insert_statement_per_database('{"k":"v"}'))
x = klass.first
assert_equal({ "k" => "v" }, x.payload)
end
def test_select_multikey
- @connection.execute(%q|insert into json_data_type (payload) VALUES ('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}')|)
+ @connection.execute(insert_statement_per_database('{"k1":"v1", "k2":"v2", "k3":[1,2,3]}'))
x = klass.first
assert_equal({ "k1" => "v1", "k2" => "v2", "k3" => [1, 2, 3] }, x.payload)
end
def test_null_json
- @connection.execute("insert into json_data_type (payload) VALUES(null)")
+ @connection.execute(insert_statement_per_database("null"))
x = klass.first
assert_nil(x.payload)
end
@@ -109,13 +107,13 @@ module JSONSharedTestCases
end
def test_select_array_json_value
- @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|)
+ @connection.execute(insert_statement_per_database('["v0",{"k1":"v1"}]'))
x = klass.first
assert_equal(["v0", { "k1" => "v1" }], x.payload)
end
def test_rewrite_array_json_value
- @connection.execute(%q|insert into json_data_type (payload) VALUES ('["v0",{"k1":"v1"}]')|)
+ @connection.execute(insert_statement_per_database('["v0",{"k1":"v1"}]'))
x = klass.first
x.payload = ["v1", { "k2" => "v2" }, "v3"]
assert x.save!
@@ -154,42 +152,42 @@ module JSONSharedTestCases
def test_changes_in_place
json = klass.new
- assert_not json.changed?
+ assert_not_predicate json, :changed?
json.payload = { "one" => "two" }
- assert json.changed?
- assert json.payload_changed?
+ assert_predicate json, :changed?
+ assert_predicate json, :payload_changed?
json.save!
- assert_not json.changed?
+ assert_not_predicate json, :changed?
json.payload["three"] = "four"
- assert json.payload_changed?
+ assert_predicate json, :payload_changed?
json.save!
json.reload
assert_equal({ "one" => "two", "three" => "four" }, json.payload)
- assert_not json.changed?
+ assert_not_predicate json, :changed?
end
def test_changes_in_place_ignores_key_order
json = klass.new
- assert_not json.changed?
+ assert_not_predicate json, :changed?
json.payload = { "three" => "four", "one" => "two" }
json.save!
json.reload
json.payload = { "three" => "four", "one" => "two" }
- assert_not json.changed?
+ assert_not_predicate json, :changed?
json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }]
json.save!
json.reload
json.payload = [{ "three" => "four", "one" => "two" }, { "seven" => "eight", "five" => "six" }]
- assert_not json.changed?
+ assert_not_predicate json, :changed?
end
def test_changes_in_place_with_ruby_object
@@ -197,10 +195,10 @@ module JSONSharedTestCases
json = klass.create!(payload: time)
json.reload
- assert_not json.changed?
+ assert_not_predicate json, :changed?
json.payload = time
- assert_not json.changed?
+ assert_not_predicate json, :changed?
end
def test_assigning_string_literal
@@ -255,4 +253,17 @@ module JSONSharedTestCases
def klass
JsonDataType
end
+
+ def assert_type_match(type, sql_type)
+ native_type = ActiveRecord::Base.connection.native_database_types[type][:name]
+ assert_match %r(\A#{native_type}\b), sql_type
+ end
+
+ def insert_statement_per_database(values)
+ if current_adapter?(:OracleAdapter)
+ "insert into json_data_type (id, payload) VALUES (json_data_type_seq.nextval, '#{values}')"
+ else
+ "insert into json_data_type (payload) VALUES ('#{values}')"
+ end
+ end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index e857180bd1..02be0aa9e3 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -69,8 +69,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::StaleObjectError) { s2.destroy }
assert s1.destroy
- assert s1.frozen?
- assert s1.destroyed?
+ assert_predicate s1, :frozen?
+ assert_predicate s1, :destroyed?
assert_raises(ActiveRecord::RecordNotFound) { StringKeyObject.find("record1") }
end
@@ -104,8 +104,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::StaleObjectError) { p2.destroy }
assert p1.destroy
- assert p1.frozen?
- assert p1.destroyed?
+ assert_predicate p1, :frozen?
+ assert_predicate p1, :destroyed?
assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) }
end
@@ -201,7 +201,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
person.first_name = "Douglas Adams"
person.lock_version = 42
- assert person.lock_version_changed?
+ assert_predicate person, :lock_version_changed?
person.save
end
@@ -399,25 +399,59 @@ class OptimisticLockingTest < ActiveRecord::TestCase
end
end
+ def test_counter_cache_with_touch_and_lock_version
+ car = Car.create!
+
+ assert_equal 0, car.wheels_count
+ assert_equal 0, car.lock_version
+
+ previously_car_updated_at = car.updated_at
+ travel(2.second) do
+ Wheel.create!(wheelable: car)
+ end
+
+ assert_equal 1, car.reload.wheels_count
+ assert_not_equal previously_car_updated_at, car.updated_at
+ assert_equal 1, car.lock_version
+
+ previously_car_updated_at = car.updated_at
+ travel(1.day) do
+ car.wheels.first.update(size: 42)
+ end
+
+ assert_equal 1, car.reload.wheels_count
+ assert_not_equal previously_car_updated_at, car.updated_at
+ assert_equal 2, car.lock_version
+
+ previously_car_updated_at = car.updated_at
+ travel(2.second) do
+ car.wheels.first.destroy!
+ end
+
+ assert_equal 0, car.reload.wheels_count
+ assert_not_equal previously_car_updated_at, car.updated_at
+ assert_equal 3, car.lock_version
+ end
+
def test_polymorphic_destroy_with_dependencies_and_lock_version
car = Car.create!
assert_difference "car.wheels.count" do
- car.wheels << Wheel.create!
+ car.wheels.create
end
assert_difference "car.wheels.count", -1 do
car.reload.destroy
end
- assert car.destroyed?
+ assert_predicate car, :destroyed?
end
def test_removing_has_and_belongs_to_many_associations_upon_destroy
p = RichPerson.create! first_name: "Jon"
p.treasures.create!
- assert !p.treasures.empty?
+ assert_not_empty p.treasures
p.destroy
- assert p.treasures.empty?
- assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty?
+ assert_empty p.treasures
+ assert_empty RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1")
end
def test_yaml_dumping_with_lock_column
@@ -487,7 +521,7 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
t1.destroy
- assert t1.destroyed?
+ assert_predicate t1, :destroyed?
end
def test_destroy_stale_object
@@ -500,7 +534,7 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
stale_object.destroy!
end
- refute stale_object.destroyed?
+ assert_not_predicate stale_object, :destroyed?
end
private
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 208e54ed0b..e2742ed33e 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -33,8 +33,9 @@ class LogSubscriberTest < ActiveRecord::TestCase
super
end
- def debug(message)
- @debugs << message
+ def debug(progname = nil, &block)
+ @debugs << progname
+ super
end
end
@@ -171,6 +172,22 @@ class LogSubscriberTest < ActiveRecord::TestCase
assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last)
end
+ def test_vebose_query_logs
+ ActiveRecord::Base.verbose_query_logs = true
+
+ logger = TestDebugLogSubscriber.new
+ logger.sql(Event.new(0, sql: "hi mom!"))
+ assert_match(/↳/, @logger.logged(:debug).last)
+ ensure
+ ActiveRecord::Base.verbose_query_logs = false
+ end
+
+ def test_verbose_query_logs_disabled_by_default
+ logger = TestDebugLogSubscriber.new
+ logger.sql(Event.new(0, sql: "hi mom!"))
+ assert_no_match(/↳/, @logger.logged(:debug).last)
+ end
+
def test_cached_queries
ActiveRecord::Base.cache do
Developer.all.load
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
index 7b0644e9c0..1494027182 100644
--- a/activerecord/test/cases/migration/change_schema_test.rb
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -84,7 +84,7 @@ module ActiveRecord
columns = connection.columns(:testings)
array_column = columns.detect { |c| c.name == "foo" }
- assert array_column.array?
+ assert_predicate array_column, :array?
end
def test_create_table_with_array_column
@@ -95,7 +95,7 @@ module ActiveRecord
columns = connection.columns(:testings)
array_column = columns.detect { |c| c.name == "foo" }
- assert array_column.array?
+ assert_predicate array_column, :array?
end
end
@@ -264,19 +264,18 @@ module ActiveRecord
t.column :foo, :timestamp
end
- klass = Class.new(ActiveRecord::Base)
- klass.table_name = "testings"
+ column = connection.columns(:testings).find { |c| c.name == "foo" }
- assert_equal :datetime, klass.columns_hash["foo"].type
+ assert_equal :datetime, column.type
if current_adapter?(:PostgreSQLAdapter)
- assert_equal "timestamp without time zone", klass.columns_hash["foo"].sql_type
+ assert_equal "timestamp without time zone", column.sql_type
elsif current_adapter?(:Mysql2Adapter)
- assert_equal "timestamp", klass.columns_hash["foo"].sql_type
+ assert_equal "timestamp", column.sql_type
elsif current_adapter?(:OracleAdapter)
- assert_equal "TIMESTAMP(6)", klass.columns_hash["foo"].sql_type
+ assert_equal "TIMESTAMP(6)", column.sql_type
else
- assert_equal klass.connection.type_to_sql("datetime"), klass.columns_hash["foo"].sql_type
+ assert_equal connection.type_to_sql("datetime"), column.sql_type
end
end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
index be6dc2acb1..3022121f4c 100644
--- a/activerecord/test/cases/migration/column_attributes_test.rb
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -80,7 +80,7 @@ module ActiveRecord
TestModel.delete_all
# Now use the Rails insertion
- TestModel.create wealth: BigDecimal.new("12345678901234567890.0123456789")
+ TestModel.create wealth: BigDecimal("12345678901234567890.0123456789")
# SELECT
row = TestModel.first
@@ -146,7 +146,7 @@ module ActiveRecord
TestModel.create first_name: "bob", last_name: "bobsen",
bio: "I was born ....", age: 18, height: 1.78,
- wealth: BigDecimal.new("12345678901234567890.0123456789"),
+ wealth: BigDecimal("12345678901234567890.0123456789"),
birthday: 18.years.ago, favorite_day: 10.days.ago,
moment_of_truth: "1782-10-10 21:40:18", male: true
@@ -159,7 +159,7 @@ module ActiveRecord
# Test for 30 significant digits (beyond the 16 of float), 10 of them
# after the decimal place.
- assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
+ assert_equal BigDecimal("0012345678901234567890.0123456789"), bob.wealth
assert_equal true, bob.male?
diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb
index 8ca20b6172..cedd9c44e3 100644
--- a/activerecord/test/cases/migration/columns_test.rb
+++ b/activerecord/test/cases/migration/columns_test.rb
@@ -67,7 +67,7 @@ module ActiveRecord
if current_adapter?(:Mysql2Adapter)
def test_mysql_rename_column_preserves_auto_increment
rename_column "test_models", "id", "id_test"
- assert connection.columns("test_models").find { |c| c.name == "id_test" }.auto_increment?
+ assert_predicate connection.columns("test_models").find { |c| c.name == "id_test" }, :auto_increment?
TestModel.reset_column_information
ensure
rename_column "test_models", "id_test", "id"
@@ -223,31 +223,31 @@ module ActiveRecord
def test_change_column_with_nil_default
add_column "test_models", "contributor", :boolean, default: true
- assert TestModel.new.contributor?
+ assert_predicate TestModel.new, :contributor?
change_column "test_models", "contributor", :boolean, default: nil
TestModel.reset_column_information
- assert_not TestModel.new.contributor?
+ assert_not_predicate TestModel.new, :contributor?
assert_nil TestModel.new.contributor
end
def test_change_column_to_drop_default_with_null_false
add_column "test_models", "contributor", :boolean, default: true, null: false
- assert TestModel.new.contributor?
+ assert_predicate TestModel.new, :contributor?
change_column "test_models", "contributor", :boolean, default: nil, null: false
TestModel.reset_column_information
- assert_not TestModel.new.contributor?
+ assert_not_predicate TestModel.new, :contributor?
assert_nil TestModel.new.contributor
end
def test_change_column_with_new_default
add_column "test_models", "administrator", :boolean, default: true
- assert TestModel.new.administrator?
+ assert_predicate TestModel.new, :administrator?
change_column "test_models", "administrator", :boolean, default: false
TestModel.reset_column_information
- assert_not TestModel.new.administrator?
+ assert_not_predicate TestModel.new, :administrator?
end
def test_change_column_with_custom_index_name
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index 58bc558619..3a11bb081b 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -14,7 +14,7 @@ module ActiveRecord
recorder = CommandRecorder.new(Class.new {
def america; end
}.new)
- assert recorder.respond_to?(:america)
+ assert_respond_to recorder, :america
end
def test_send_calls_super
@@ -27,7 +27,7 @@ module ActiveRecord
recorder = CommandRecorder.new(Class.new {
def create_table(name); end
}.new)
- assert recorder.respond_to?(:create_table), "respond_to? create_table"
+ assert_respond_to recorder, :create_table
recorder.send(:create_table, :horses)
assert_equal [[:create_table, [:horses], nil]], recorder.commands
end
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index 2fef2f796e..69a50674af 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -16,7 +16,7 @@ module ActiveRecord
ActiveRecord::Migration.verbose = false
connection.create_table :testings do |t|
- t.column :foo, :string, limit: 100
+ t.column :foo, :string, limit: 5
t.column :bar, :string, limit: 100
end
end
@@ -126,6 +126,25 @@ module ActiveRecord
end
assert_match(/LegacyMigration < ActiveRecord::Migration\[4\.2\]/, e.message)
end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ class Testing < ActiveRecord::Base
+ end
+
+ def test_legacy_change_column_with_null_executes_update
+ migration = Class.new(ActiveRecord::Migration[5.1]) {
+ def migrate(x)
+ change_column :testings, :foo, :string, limit: 10, null: false, default: "foobar"
+ end
+ }.new
+
+ Testing.create!
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+ assert_equal ["foobar"], Testing.all.map(&:foo)
+ ensure
+ ActiveRecord::Base.clear_cache!
+ end
+ end
end
end
end
@@ -160,12 +179,10 @@ module LegacyPrimaryKeyTestCases
@migration.migrate(:up)
- legacy_pk = LegacyPrimaryKey.columns_hash["id"]
- assert_not legacy_pk.bigint?
- assert_not legacy_pk.null
+ assert_legacy_primary_key
legacy_ref = LegacyPrimaryKey.columns_hash["legacy_ref_id"]
- assert_not legacy_ref.bigint?
+ assert_not_predicate legacy_ref, :bigint?
record1 = LegacyPrimaryKey.create!
assert_not_nil record1.id
@@ -197,55 +214,50 @@ module LegacyPrimaryKeyTestCases
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
+ 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
- }.new
+ end
+ }.new
- @migration.migrate(:up)
+ @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
+ assert_legacy_primary_key
+ 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
+ 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
- }.new
+ change_table :legacy_primary_keys do |t|
+ t.primary_key :id
+ end
+ end
+ }.new
- @migration.migrate(:up)
+ @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
+ assert_legacy_primary_key
+ 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
+ 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
- }.new
+ add_column :legacy_primary_keys, :id, :primary_key
+ end
+ }.new
- @migration.migrate(:up)
+ @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
+ assert_legacy_primary_key
end
def test_legacy_join_table_foreign_keys_should_be_integer
@@ -289,8 +301,8 @@ module LegacyPrimaryKeyTestCases
@migration.migrate(:up)
legacy_pk = LegacyPrimaryKey.columns_hash["id"]
- assert legacy_pk.bigint?
- assert legacy_pk.auto_increment?
+ assert_predicate legacy_pk, :bigint?
+ assert_predicate legacy_pk, :auto_increment?
schema = dump_table_schema "legacy_primary_keys"
assert_match %r{create_table "legacy_primary_keys", (?!id: :bigint, default: nil)}, schema
@@ -314,6 +326,22 @@ module LegacyPrimaryKeyTestCases
assert_match %r{create_table "legacy_primary_keys", id: :bigint, default: nil}, schema
end
end
+
+ private
+ def assert_legacy_primary_key
+ assert_equal "id", LegacyPrimaryKey.primary_key
+
+ legacy_pk = LegacyPrimaryKey.columns_hash["id"]
+
+ assert_equal :integer, legacy_pk.type
+ assert_not_predicate legacy_pk, :bigint?
+ assert_not legacy_pk.null
+
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", id: :(?:integer|serial), (?!default: nil)}, schema
+ end
+ end
end
module LegacyPrimaryKeyTest
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
index 77d32a24a5..83fb4f9385 100644
--- a/activerecord/test/cases/migration/create_join_table_test.rb
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -69,7 +69,7 @@ module ActiveRecord
def test_create_join_table_without_indexes
connection.create_join_table :artists, :musics
- assert connection.indexes(:artists_musics).blank?
+ assert_predicate connection.indexes(:artists_musics), :blank?
end
def test_create_join_table_with_index
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 499d072de5..de37215e80 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -227,6 +227,74 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
end
+ if ActiveRecord::Base.connection.supports_validate_constraints?
+ def test_add_invalid_foreign_key
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false
+
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_not_predicate fk, :validated?
+ end
+
+ def test_validate_foreign_key_infers_column
+ @connection.add_foreign_key :astronauts, :rockets, validate: false
+ assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
+
+ @connection.validate_foreign_key :astronauts, :rockets
+ assert_predicate @connection.foreign_keys("astronauts").first, :validated?
+ end
+
+ def test_validate_foreign_key_by_column
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false
+ assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
+
+ @connection.validate_foreign_key :astronauts, column: "rocket_id"
+ assert_predicate @connection.foreign_keys("astronauts").first, :validated?
+ end
+
+ def test_validate_foreign_key_by_symbol_column
+ @connection.add_foreign_key :astronauts, :rockets, column: :rocket_id, validate: false
+ assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
+
+ @connection.validate_foreign_key :astronauts, column: :rocket_id
+ assert_predicate @connection.foreign_keys("astronauts").first, :validated?
+ end
+
+ def test_validate_foreign_key_by_name
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk", validate: false
+ assert_not_predicate @connection.foreign_keys("astronauts").first, :validated?
+
+ @connection.validate_foreign_key :astronauts, name: "fancy_named_fk"
+ assert_predicate @connection.foreign_keys("astronauts").first, :validated?
+ end
+
+ def test_validate_foreign_non_existing_foreign_key_raises
+ assert_raises ArgumentError do
+ @connection.validate_foreign_key :astronauts, :rockets
+ end
+ end
+
+ def test_validate_constraint_by_name
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", name: "fancy_named_fk", validate: false
+
+ @connection.validate_constraint :astronauts, "fancy_named_fk"
+ assert_predicate @connection.foreign_keys("astronauts").first, :validated?
+ end
+ else
+ # Foreign key should still be created, but should not be invalid
+ def test_add_invalid_foreign_key
+ @connection.add_foreign_key :astronauts, :rockets, column: "rocket_id", validate: false
+
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_predicate fk, :validated?
+ end
+ end
+
def test_schema_dumping
@connection.add_foreign_key :astronauts, :rockets
output = dump_table_schema "astronauts"
diff --git a/activerecord/test/cases/migration/pending_migrations_test.rb b/activerecord/test/cases/migration/pending_migrations_test.rb
index d0066f68be..dedb5ea502 100644
--- a/activerecord/test/cases/migration/pending_migrations_test.rb
+++ b/activerecord/test/cases/migration/pending_migrations_test.rb
@@ -4,37 +4,37 @@ require "cases/helper"
module ActiveRecord
class Migration
- class PendingMigrationsTest < ActiveRecord::TestCase
- def setup
- super
- @connection = Minitest::Mock.new
- @app = Minitest::Mock.new
- conn = @connection
- @pending = Class.new(CheckPending) {
- define_method(:connection) { conn }
- }.new(@app)
- @pending.instance_variable_set :@last_check, -1 # Force checking
- end
+ if current_adapter?(:SQLite3Adapter) && !in_memory_db?
+ class PendingMigrationsTest < ActiveRecord::TestCase
+ setup do
+ file = ActiveRecord::Base.connection.raw_connection.filename
+ @conn = ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:", migrations_paths: MIGRATIONS_ROOT + "/valid"
+ source_db = SQLite3::Database.new file
+ dest_db = ActiveRecord::Base.connection.raw_connection
+ backup = SQLite3::Backup.new(dest_db, "main", source_db, "main")
+ backup.step(-1)
+ backup.finish
+ end
- def teardown
- assert @connection.verify
- assert @app.verify
- super
- end
+ teardown do
+ @conn.release_connection if @conn
+ ActiveRecord::Base.establish_connection :arunit
+ end
+
+ def test_errors_if_pending
+ ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
- def test_errors_if_pending
- ActiveRecord::Migrator.stub :needs_migration?, true do
- assert_raise ActiveRecord::PendingMigrationError do
- @pending.call(nil)
+ assert_raises ActiveRecord::PendingMigrationError do
+ CheckPending.new(Proc.new {}).call({})
end
end
- end
- def test_checks_if_supported
- @app.expect :call, nil, [:foo]
+ def test_checks_if_supported
+ ActiveRecord::SchemaMigration.create_table
+ migrator = Base.connection.migration_context
+ capture(:stdout) { migrator.migrate }
- ActiveRecord::Migrator.stub :needs_migration?, false do
- @pending.call(:foo)
+ assert_nil CheckPending.new(Proc.new {}).call({})
end
end
end
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
index dfce266253..8514ccd55b 100644
--- a/activerecord/test/cases/migration/rename_table_test.rb
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -86,7 +86,7 @@ module ActiveRecord
rename_table :cats, :felines
assert connection.table_exists? :felines
- refute connection.table_exists? :cats
+ assert_not connection.table_exists? :cats
primary_key_name = connection.select_values(<<-SQL.strip_heredoc, "SCHEMA")[0]
SELECT c.relname
@@ -107,7 +107,7 @@ module ActiveRecord
connection.create_table :cats, id: :uuid, default: "uuid_generate_v4()"
assert_nothing_raised { rename_table :cats, :felines }
assert connection.table_exists? :felines
- refute connection.table_exists? :cats
+ assert_not connection.table_exists? :cats
ensure
connection.drop_table :cats, if_exists: true
connection.drop_table :felines, if_exists: true
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index b18af2ab55..a4b8664a85 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -71,6 +71,16 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migration.verbose = @verbose_was
end
+ def test_migrator_migrations_path_is_deprecated
+ assert_deprecated do
+ ActiveRecord::Migrator.migrations_path = "/whatever"
+ end
+ ensure
+ assert_deprecated do
+ ActiveRecord::Migrator.migrations_path = "db/migrate"
+ end
+ end
+
def test_migration_version_matches_component_version
assert_equal ActiveRecord::VERSION::STRING.to_f, ActiveRecord::Migration.current_version
end
@@ -78,20 +88,20 @@ class MigrationTest < ActiveRecord::TestCase
def test_migrator_versions
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
+ migrator = ActiveRecord::MigrationContext.new(migrations_path)
- ActiveRecord::Migrator.up(migrations_path)
- assert_equal 3, ActiveRecord::Migrator.current_version
- assert_equal false, ActiveRecord::Migrator.needs_migration?
+ migrator.up
+ assert_equal 3, migrator.current_version
+ assert_equal false, migrator.needs_migration?
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
- assert_equal 0, ActiveRecord::Migrator.current_version
- assert_equal true, ActiveRecord::Migrator.needs_migration?
+ migrator.down
+ assert_equal 0, migrator.current_version
+ assert_equal true, migrator.needs_migration?
ActiveRecord::SchemaMigration.create!(version: 3)
- assert_equal true, ActiveRecord::Migrator.needs_migration?
+ assert_equal true, migrator.needs_migration?
ensure
- ActiveRecord::Migrator.migrations_paths = old_path
+ ActiveRecord::MigrationContext.new(old_path)
end
def test_migration_detection_without_schema_migration_table
@@ -99,28 +109,31 @@ class MigrationTest < ActiveRecord::TestCase
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
+ migrator = ActiveRecord::MigrationContext.new(migrations_path)
- assert_equal true, ActiveRecord::Migrator.needs_migration?
+ assert_equal true, migrator.needs_migration?
ensure
- ActiveRecord::Migrator.migrations_paths = old_path
+ ActiveRecord::MigrationContext.new(old_path)
end
def test_any_migrations
old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = MIGRATIONS_ROOT + "/valid"
+ migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid")
- assert ActiveRecord::Migrator.any_migrations?
+ assert_predicate migrator, :any_migrations?
- ActiveRecord::Migrator.migrations_paths = MIGRATIONS_ROOT + "/empty"
+ migrator_empty = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/empty")
- assert_not ActiveRecord::Migrator.any_migrations?
+ assert_not_predicate migrator_empty, :any_migrations?
ensure
- ActiveRecord::Migrator.migrations_paths = old_path
+ ActiveRecord::MigrationContext.new(old_path)
end
def test_migration_version
- assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/version_check", 20131219224947) }
+ migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/version_check")
+ assert_equal 0, migrator.current_version
+ migrator.up(20131219224947)
+ assert_equal 20131219224947, migrator.current_version
end
def test_create_table_with_force_true_does_not_drop_nonexisting_table
@@ -157,7 +170,7 @@ class MigrationTest < ActiveRecord::TestCase
def test_add_table_with_decimals
Person.connection.drop_table :big_numbers rescue nil
- assert !BigNumber.table_exists?
+ assert_not_predicate BigNumber, :table_exists?
GiveMeBigNumbers.up
BigNumber.reset_column_information
@@ -216,15 +229,16 @@ class MigrationTest < ActiveRecord::TestCase
def test_filtering_migrations
assert_no_column Person, :last_name
- assert !Reminder.table_exists?
+ assert_not_predicate Reminder, :table_exists?
name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" }
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter)
+ migrator = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid")
+ migrator.up(&name_filter)
assert_column Person, :last_name
assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter)
+ migrator.down(&name_filter)
assert_no_column Person, :last_name
assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
@@ -382,9 +396,9 @@ class MigrationTest < ActiveRecord::TestCase
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
+ migrator = ActiveRecord::MigrationContext.new(migrations_path)
- ActiveRecord::Migrator.up(migrations_path)
+ migrator.up
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
original_rails_env = ENV["RAILS_ENV"]
@@ -395,13 +409,13 @@ class MigrationTest < ActiveRecord::TestCase
refute_equal current_env, new_env
sleep 1 # mysql by default does not store fractional seconds in the database
- ActiveRecord::Migrator.up(migrations_path)
+ migrator.up
assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
ensure
- ActiveRecord::Migrator.migrations_paths = old_path
+ migrator = ActiveRecord::MigrationContext.new(old_path)
ENV["RAILS_ENV"] = original_rails_env
ENV["RACK_ENV"] = original_rack_env
- ActiveRecord::Migrator.up(migrations_path)
+ migrator.up
end
def test_internal_metadata_stores_environment_when_other_data_exists
@@ -411,14 +425,15 @@ class MigrationTest < ActiveRecord::TestCase
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
migrations_path = MIGRATIONS_ROOT + "/valid"
old_path = ActiveRecord::Migrator.migrations_paths
- ActiveRecord::Migrator.migrations_paths = migrations_path
current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
- ActiveRecord::Migrator.up(migrations_path)
+ migrator = ActiveRecord::MigrationContext.new(migrations_path)
+ migrator.up
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
assert_equal "bar", ActiveRecord::InternalMetadata[:foo]
ensure
- ActiveRecord::Migrator.migrations_paths = old_path
+ migrator = ActiveRecord::MigrationContext.new(old_path)
+ migrator.up
end
def test_proper_table_name_on_migration
@@ -450,7 +465,7 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_rename_table_with_prefix_and_suffix
- assert !Thing.table_exists?
+ assert_not_predicate Thing, :table_exists?
ActiveRecord::Base.table_name_prefix = "p_"
ActiveRecord::Base.table_name_suffix = "_s"
Thing.reset_table_name
@@ -471,7 +486,7 @@ class MigrationTest < ActiveRecord::TestCase
end
def test_add_drop_table_with_prefix_and_suffix
- assert !Reminder.table_exists?
+ assert_not_predicate Reminder, :table_exists?
ActiveRecord::Base.table_name_prefix = "prefix_"
ActiveRecord::Base.table_name_suffix = "_suffix"
Reminder.reset_table_name
@@ -801,8 +816,15 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
t.integer :age
end
- # Adding an index fires a query every time to check if an index already exists or not
- assert_queries(3) do
+ classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
+ expected_query_count = {
+ "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not
+ "PostgreSQLAdapter" => 2,
+ }.fetch(classname) {
+ raise "need an expected query count for #{classname}"
+ }
+
+ assert_queries(expected_query_count) do
with_bulk_change_table do |t|
t.index :username, unique: true, name: :awesome_username_index
t.index [:name, :age]
@@ -826,7 +848,15 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
assert index(:index_delete_me_on_name)
- assert_queries(3) do
+ classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
+ expected_query_count = {
+ "Mysql2Adapter" => 3, # Adding an index fires a query every time to check if an index already exists or not
+ "PostgreSQLAdapter" => 2,
+ }.fetch(classname) {
+ raise "need an expected query count for #{classname}"
+ }
+
+ assert_queries(expected_query_count) do
with_bulk_change_table do |t|
t.remove_index :name
t.index :name, name: :new_name_index, unique: true
@@ -848,18 +878,24 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
assert ! column(:name).default
assert_equal :date, column(:birthdate).type
- # One query for columns (delete_me table)
- # One query for primary key (delete_me table)
- # One query to do the bulk change
- assert_queries(3, ignore_none: true) do
+ classname = ActiveRecord::Base.connection.class.name[/[^:]*$/]
+ expected_query_count = {
+ "Mysql2Adapter" => 3, # one query for columns, one query for primary key, one query to do the bulk change
+ "PostgreSQLAdapter" => 3, # one query for columns, one for bulk change, one for comment
+ }.fetch(classname) {
+ raise "need an expected query count for #{classname}"
+ }
+
+ assert_queries(expected_query_count, ignore_none: true) do
with_bulk_change_table do |t|
t.change :name, :string, default: "NONAME"
- t.change :birthdate, :datetime
+ t.change :birthdate, :datetime, comment: "This is a comment"
end
end
assert_equal "NONAME", column(:name).default
assert_equal :datetime, column(:birthdate).type
+ assert_equal "This is a comment", column(:birthdate).comment
end
private
@@ -919,7 +955,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
files_count = Dir[@migrations_path + "/*.rb"].length
copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy")
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
+ assert_empty copied
ensure
clear
end
@@ -960,7 +996,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
files_count = Dir[@migrations_path + "/*.rb"].length
copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps")
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
+ assert_empty copied
end
ensure
clear
@@ -1002,7 +1038,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
files_count = Dir[@migrations_path + "/*.rb"].length
copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/to_copy_with_timestamps")
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
+ assert_empty copied
end
ensure
clear
@@ -1023,7 +1059,7 @@ class CopyMigrationsTest < ActiveRecord::TestCase
files_count = Dir[@migrations_path + "/*.rb"].length
copied = ActiveRecord::Migration.copy(@migrations_path, bukkits: MIGRATIONS_ROOT + "/magic")
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
+ assert_empty copied
ensure
clear
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index 1047ba1367..873455cf67 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -89,7 +89,7 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_finds_migrations
- migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid")
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid").migrations
[[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
assert_equal migrations[i].version, pair.first
@@ -98,7 +98,8 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_finds_migrations_in_subdirectories
- migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid_with_subdirectories")
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/valid_with_subdirectories").migrations
+
[[1, "ValidPeopleHaveLastNames"], [2, "WeNeedReminders"], [3, "InnocentJointable"]].each_with_index do |pair, i|
assert_equal migrations[i].version, pair.first
@@ -108,7 +109,7 @@ class MigratorTest < ActiveRecord::TestCase
def test_finds_migrations_from_two_directories
directories = [MIGRATIONS_ROOT + "/valid_with_timestamps", MIGRATIONS_ROOT + "/to_copy_with_timestamps"]
- migrations = ActiveRecord::Migrator.migrations directories
+ migrations = ActiveRecord::MigrationContext.new(directories).migrations
[[20090101010101, "PeopleHaveHobbies"],
[20090101010202, "PeopleHaveDescriptions"],
@@ -121,14 +122,14 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_finds_migrations_in_numbered_directory
- migrations = ActiveRecord::Migrator.migrations [MIGRATIONS_ROOT + "/10_urban"]
+ migrations = ActiveRecord::MigrationContext.new(MIGRATIONS_ROOT + "/10_urban").migrations
assert_equal 9, migrations[0].version
assert_equal "AddExpressions", migrations[0].name
end
def test_relative_migrations
list = Dir.chdir(MIGRATIONS_ROOT) do
- ActiveRecord::Migrator.migrations("valid")
+ ActiveRecord::MigrationContext.new("valid").migrations
end
migration_proxy = list.find { |item|
@@ -157,7 +158,7 @@ class MigratorTest < ActiveRecord::TestCase
["up", "002", "We need reminders"],
["down", "003", "Innocent jointable"],
["up", "010", "********** NO FILE **********"],
- ], ActiveRecord::Migrator.migrations_status(path)
+ ], ActiveRecord::MigrationContext.new(path).migrations_status
end
def test_migrations_status_in_subdirectories
@@ -171,7 +172,7 @@ class MigratorTest < ActiveRecord::TestCase
["up", "002", "We need reminders"],
["down", "003", "Innocent jointable"],
["up", "010", "********** NO FILE **********"],
- ], ActiveRecord::Migrator.migrations_status(path)
+ ], ActiveRecord::MigrationContext.new(path).migrations_status
end
def test_migrations_status_with_schema_define_in_subdirectories
@@ -186,7 +187,7 @@ class MigratorTest < ActiveRecord::TestCase
["up", "001", "Valid people have last names"],
["up", "002", "We need reminders"],
["up", "003", "Innocent jointable"],
- ], ActiveRecord::Migrator.migrations_status(path)
+ ], ActiveRecord::MigrationContext.new(path).migrations_status
ensure
ActiveRecord::Migrator.migrations_paths = prev_paths
end
@@ -204,7 +205,7 @@ class MigratorTest < ActiveRecord::TestCase
["down", "20100201010101", "Valid with timestamps we need reminders"],
["down", "20100301010101", "Valid with timestamps innocent jointable"],
["up", "20160528010101", "********** NO FILE **********"],
- ], ActiveRecord::Migrator.migrations_status(paths)
+ ], ActiveRecord::MigrationContext.new(paths).migrations_status
end
def test_migrator_interleaved_migrations
@@ -232,25 +233,28 @@ class MigratorTest < ActiveRecord::TestCase
def test_up_calls_up
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
- ActiveRecord::Migrator.new(:up, migrations).migrate
+ migrator = ActiveRecord::Migrator.new(:up, migrations)
+ migrator.migrate
assert migrations.all?(&:went_up)
assert migrations.all? { |m| !m.went_down }
- assert_equal 2, ActiveRecord::Migrator.current_version
+ assert_equal 2, migrator.current_version
end
def test_down_calls_down
test_up_calls_up
migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
- ActiveRecord::Migrator.new(:down, migrations).migrate
+ migrator = ActiveRecord::Migrator.new(:down, migrations)
+ migrator.migrate
assert migrations.all? { |m| !m.went_up }
assert migrations.all?(&:went_down)
- assert_equal 0, ActiveRecord::Migrator.current_version
+ assert_equal 0, migrator.current_version
end
def test_current_version
ActiveRecord::SchemaMigration.create!(version: "1000")
- assert_equal 1000, ActiveRecord::Migrator.current_version
+ migrator = ActiveRecord::MigrationContext.new("db/migrate")
+ assert_equal 1000, migrator.current_version
end
def test_migrator_one_up
@@ -289,33 +293,36 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_double_up
calls, migrations = sensors(3)
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ migrator = ActiveRecord::Migrator.new(:up, migrations, 1)
+ assert_equal(0, migrator.current_version)
- ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ migrator.migrate
assert_equal [[:up, 1]], calls
calls.clear
- ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ migrator.migrate
assert_equal [], calls
end
def test_migrator_double_down
calls, migrations = sensors(3)
+ migrator = ActiveRecord::Migrator.new(:up, migrations, 1)
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ assert_equal 0, migrator.current_version
- ActiveRecord::Migrator.new(:up, migrations, 1).run
+ migrator.run
assert_equal [[:up, 1]], calls
calls.clear
- ActiveRecord::Migrator.new(:down, migrations, 1).run
+ migrator = ActiveRecord::Migrator.new(:down, migrations, 1)
+ migrator.run
assert_equal [[:down, 1]], calls
calls.clear
- ActiveRecord::Migrator.new(:down, migrations, 1).run
+ migrator.run
assert_equal [], calls
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ assert_equal 0, migrator.current_version
end
def test_migrator_verbosity
@@ -361,78 +368,85 @@ class MigratorTest < ActiveRecord::TestCase
def test_migrator_going_down_due_to_version_target
calls, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
- migrator.up("valid", 1)
+ migrator.up(1)
assert_equal [[:up, 1]], calls
calls.clear
- migrator.migrate("valid", 0)
+ migrator.migrate(0)
assert_equal [[:down, 1]], calls
calls.clear
- migrator.migrate("valid")
+ migrator.migrate
assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
end
def test_migrator_output_when_running_multiple_migrations
_, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
- result = migrator.migrate("valid")
+ result = migrator.migrate
assert_equal(3, result.count)
# Nothing migrated from duplicate run
- result = migrator.migrate("valid")
+ result = migrator.migrate
assert_equal(0, result.count)
- result = migrator.rollback("valid")
+ result = migrator.rollback
assert_equal(1, result.count)
end
def test_migrator_output_when_running_single_migration
_, migrator = migrator_class(1)
- result = migrator.run(:up, "valid", 1)
+ migrator = migrator.new("valid")
+
+ result = migrator.run(:up, 1)
assert_equal(1, result.version)
end
def test_migrator_rollback
_, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
- migrator.migrate("valid")
- assert_equal(3, ActiveRecord::Migrator.current_version)
+ migrator.migrate
+ assert_equal(3, migrator.current_version)
- migrator.rollback("valid")
- assert_equal(2, ActiveRecord::Migrator.current_version)
+ migrator.rollback
+ assert_equal(2, migrator.current_version)
- migrator.rollback("valid")
- assert_equal(1, ActiveRecord::Migrator.current_version)
+ migrator.rollback
+ assert_equal(1, migrator.current_version)
- migrator.rollback("valid")
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ migrator.rollback
+ assert_equal(0, migrator.current_version)
- migrator.rollback("valid")
- assert_equal(0, ActiveRecord::Migrator.current_version)
+ migrator.rollback
+ assert_equal(0, migrator.current_version)
end
def test_migrator_db_has_no_schema_migrations_table
_, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true
assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations")
- migrator.migrate("valid", 1)
+ migrator.migrate(1)
assert ActiveRecord::Base.connection.table_exists?("schema_migrations")
end
def test_migrator_forward
_, migrator = migrator_class(3)
- migrator.migrate("/valid", 1)
- assert_equal(1, ActiveRecord::Migrator.current_version)
+ migrator = migrator.new("/valid")
+ migrator.migrate(1)
+ assert_equal(1, migrator.current_version)
- migrator.forward("/valid", 2)
- assert_equal(3, ActiveRecord::Migrator.current_version)
+ migrator.forward(2)
+ assert_equal(3, migrator.current_version)
- migrator.forward("/valid")
- assert_equal(3, ActiveRecord::Migrator.current_version)
+ migrator.forward
+ assert_equal(3, migrator.current_version)
end
def test_only_loads_pending_migrations
@@ -440,25 +454,27 @@ class MigratorTest < ActiveRecord::TestCase
ActiveRecord::SchemaMigration.create!(version: "1")
calls, migrator = migrator_class(3)
- migrator.migrate("valid", nil)
+ migrator = migrator.new("valid")
+ migrator.migrate
assert_equal [[:up, 2], [:up, 3]], calls
end
def test_get_all_versions
_, migrator = migrator_class(3)
+ migrator = migrator.new("valid")
- migrator.migrate("valid")
- assert_equal([1, 2, 3], ActiveRecord::Migrator.get_all_versions)
+ migrator.migrate
+ assert_equal([1, 2, 3], migrator.get_all_versions)
- migrator.rollback("valid")
- assert_equal([1, 2], ActiveRecord::Migrator.get_all_versions)
+ migrator.rollback
+ assert_equal([1, 2], migrator.get_all_versions)
- migrator.rollback("valid")
- assert_equal([1], ActiveRecord::Migrator.get_all_versions)
+ migrator.rollback
+ assert_equal([1], migrator.get_all_versions)
- migrator.rollback("valid")
- assert_equal([], ActiveRecord::Migrator.get_all_versions)
+ migrator.rollback
+ assert_equal([], migrator.get_all_versions)
end
private
@@ -483,11 +499,11 @@ class MigratorTest < ActiveRecord::TestCase
def migrator_class(count)
calls, migrations = sensors(count)
- migrator = Class.new(ActiveRecord::Migrator).extend(Module.new {
- define_method(:migrations) { |paths|
+ migrator = Class.new(ActiveRecord::MigrationContext) {
+ define_method(:migrations) { |*|
migrations
}
- })
+ }
[calls, migrator]
end
end
diff --git a/activerecord/test/cases/multiparameter_attributes_test.rb b/activerecord/test/cases/multiparameter_attributes_test.rb
index 59be4dc5a8..a24b173cf5 100644
--- a/activerecord/test/cases/multiparameter_attributes_test.rb
+++ b/activerecord/test/cases/multiparameter_attributes_test.rb
@@ -227,7 +227,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic = Topic.find(1)
topic.attributes = attributes
assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
- assert_equal false, topic.written_on.respond_to?(:time_zone)
+ assert_not_respond_to topic.written_on, :time_zone
end
end
@@ -242,7 +242,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic = Topic.find(1)
topic.attributes = attributes
assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
- assert_equal false, topic.written_on.respond_to?(:time_zone)
+ assert_not_respond_to topic.written_on, :time_zone
end
ensure
Topic.skip_time_zone_conversion_for_attributes = []
@@ -261,7 +261,7 @@ class MultiParameterAttributeTest < ActiveRecord::TestCase
topic = Topic.find(1)
topic.attributes = attributes
assert_equal Time.zone.local(2000, 1, 1, 16, 24, 0), topic.bonus_time
- assert_not topic.bonus_time.utc?
+ assert_not_predicate topic.bonus_time, :utc?
attributes = {
"written_on(1i)" => "2000", "written_on(2i)" => "", "written_on(3i)" => "",
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index a2ccb603a9..1ed3a61bbb 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -36,7 +36,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate.birds_with_reject_all_blank_attributes = [{ name: "", color: "", _destroy: "0" }]
pirate.save!
- assert pirate.birds_with_reject_all_blank.empty?
+ assert_empty pirate.birds_with_reject_all_blank
end
def test_should_not_build_a_new_record_if_reject_all_blank_returns_false
@@ -44,7 +44,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate.birds_with_reject_all_blank_attributes = [{ name: "", color: "" }]
pirate.save!
- assert pirate.birds_with_reject_all_blank.empty?
+ assert_empty pirate.birds_with_reject_all_blank
end
def test_should_build_a_new_record_if_reject_all_blank_does_not_return_false
@@ -152,7 +152,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
man = Man.create(name: "Jon")
interest = man.interests.create(topic: "the ladies")
man.update(interests_attributes: { _destroy: "1", id: interest.id })
- assert man.reload.interests.empty?
+ assert_empty man.reload.interests
end
def test_reject_if_is_not_short_circuited_if_allow_destroy_is_false
@@ -240,7 +240,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
@ship.destroy
@pirate.reload.ship_attributes = { name: "Davy Jones Gold Dagger" }
- assert !@pirate.ship.persisted?
+ assert_not_predicate @pirate.ship, :persisted?
assert_equal "Davy Jones Gold Dagger", @pirate.ship.name
end
@@ -261,7 +261,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_replace_an_existing_record_if_there_is_no_id
@pirate.reload.ship_attributes = { name: "Davy Jones Gold Dagger" }
- assert !@pirate.ship.persisted?
+ assert_not_predicate @pirate.ship, :persisted?
assert_equal "Davy Jones Gold Dagger", @pirate.ship.name
assert_equal "Nights Dirty Lightning", @ship.name
end
@@ -335,7 +335,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_also_work_with_a_HashWithIndifferentAccess
@pirate.ship_attributes = ActiveSupport::HashWithIndifferentAccess.new(id: @ship.id, name: "Davy Jones Gold Dagger")
- assert @pirate.ship.persisted?
+ assert_predicate @pirate.ship, :persisted?
assert_equal "Davy Jones Gold Dagger", @pirate.ship.name
end
@@ -350,12 +350,12 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
def test_should_not_destroy_the_associated_model_until_the_parent_is_saved
@pirate.attributes = { ship_attributes: { id: @ship.id, _destroy: "1" } }
- assert !@pirate.ship.destroyed?
- assert @pirate.ship.marked_for_destruction?
+ assert_not_predicate @pirate.ship, :destroyed?
+ assert_predicate @pirate.ship, :marked_for_destruction?
@pirate.save
- assert @pirate.ship.destroyed?
+ assert_predicate @pirate.ship, :destroyed?
assert_nil @pirate.reload.ship
end
@@ -424,7 +424,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
@pirate.destroy
@ship.reload.pirate_attributes = { catchphrase: "Arr" }
- assert !@ship.pirate.persisted?
+ assert_not_predicate @ship.pirate, :persisted?
assert_equal "Arr", @ship.pirate.catchphrase
end
@@ -445,7 +445,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_replace_an_existing_record_if_there_is_no_id
@ship.reload.pirate_attributes = { catchphrase: "Arr" }
- assert !@ship.pirate.persisted?
+ assert_not_predicate @ship.pirate, :persisted?
assert_equal "Arr", @ship.pirate.catchphrase
assert_equal "Aye", @pirate.catchphrase
end
@@ -550,7 +550,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
@pirate.delete
@ship.reload.attributes = { update_only_pirate_attributes: { catchphrase: "Arr" } }
- assert !@ship.update_only_pirate.persisted?
+ assert_not_predicate @ship.update_only_pirate, :persisted?
end
def test_should_update_existing_when_update_only_is_true_and_no_id_is_given
@@ -632,10 +632,10 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_not_load_association_when_updating_existing_records
@pirate.reload
@pirate.send(association_setter, [{ id: @child_1.id, name: "Grace OMalley" }])
- assert ! @pirate.send(@association_name).loaded?
+ assert_not_predicate @pirate.send(@association_name), :loaded?
@pirate.save
- assert ! @pirate.send(@association_name).loaded?
+ assert_not_predicate @pirate.send(@association_name), :loaded?
assert_equal "Grace OMalley", @child_1.reload.name
end
@@ -663,7 +663,7 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_not_remove_scheduled_destroys_when_loading_association
@pirate.reload
@pirate.send(association_setter, [{ id: @child_1.id, _destroy: "1" }])
- assert @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }.marked_for_destruction?
+ assert_predicate @pirate.send(@association_name).load_target.find { |r| r.id == @child_1.id }, :marked_for_destruction?
end
def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models
@@ -705,10 +705,10 @@ module NestedAttributesOnACollectionAssociationTests
association_getter => { "foo" => { name: "Grace OMalley" }, "bar" => { name: "Privateers Greed" } }
}
- assert !@pirate.send(@association_name).first.persisted?
+ assert_not_predicate @pirate.send(@association_name).first, :persisted?
assert_equal "Grace OMalley", @pirate.send(@association_name).first.name
- assert !@pirate.send(@association_name).last.persisted?
+ assert_not_predicate @pirate.send(@association_name).last, :persisted?
assert_equal "Privateers Greed", @pirate.send(@association_name).last.name
end
@@ -1091,7 +1091,7 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
test "nested singular associations are validated" do
part = ShipPart.new(name: "Stern", ship_attributes: { name: nil })
- assert_not part.valid?
+ assert_not_predicate part, :valid?
assert_equal ["Ship name can't be blank"], part.errors.full_messages
end
end
diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
index f04c68b08f..1d26057fdc 100644
--- a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
+++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
@@ -63,7 +63,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
# Characterizing when :before_add callback is called
test ":before_add called for new bird when not loaded" do
- assert_not @pirate.birds_with_add.loaded?
+ assert_not_predicate @pirate.birds_with_add, :loaded?
@pirate.birds_with_add_attributes = new_bird_attributes
assert_new_bird_with_callback_called
end
@@ -80,7 +80,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
end
test ":before_add not called for identical assignment when not loaded" do
- assert_not @pirate.birds_with_add.loaded?
+ assert_not_predicate @pirate.birds_with_add, :loaded?
@pirate.birds_with_add_attributes = existing_birds_attributes
assert_callbacks_not_called
end
@@ -92,7 +92,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
end
test ":before_add not called for destroy assignment when not loaded" do
- assert_not @pirate.birds_with_add.loaded?
+ assert_not_predicate @pirate.birds_with_add, :loaded?
@pirate.birds_with_add_attributes = destroy_bird_attributes
assert_callbacks_not_called
end
@@ -111,7 +111,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
# Ensuring that the records in the association target are updated,
# whether the association is loaded before or not
test "Assignment updates records in target when not loaded" do
- assert_not @pirate.birds_with_add.loaded?
+ assert_not_predicate @pirate.birds_with_add, :loaded?
@pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes
assert_assignment_affects_records_in_target(:birds_with_add)
end
@@ -124,7 +124,7 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
test("Assignment updates records in target when not loaded" \
" and callback loads target") do
- assert_not @pirate.birds_with_add_load.loaded?
+ assert_not_predicate @pirate.birds_with_add_load, :loaded?
@pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes
assert_assignment_affects_records_in_target(:birds_with_add_load)
end
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index f088c064f5..d4a4cd564a 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -59,18 +59,47 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_all_with_order_and_limit_updates_subset_only
author = authors(:david)
- assert_nothing_raised do
- assert_equal 1, author.posts_sorted_by_id_limited.size
- assert_equal 2, author.posts_sorted_by_id_limited.limit(2).to_a.size
- assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ])
- assert_equal "bulk update!", posts(:welcome).body
- assert_not_equal "bulk update!", posts(:thinking).body
- end
+ limited_posts = author.posts_sorted_by_id_limited
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:welcome).body
+ assert_not_equal "bulk update!", posts(:thinking).body
+ end
+
+ def test_update_all_with_order_and_limit_and_offset_updates_subset_only
+ author = authors(:david)
+ limited_posts = author.posts_sorted_by_id_limited.offset(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:thinking).body
+ assert_not_equal "bulk update!", posts(:welcome).body
+ end
+
+ def test_delete_all_with_order_and_limit_deletes_subset_only
+ author = authors(:david)
+ limited_posts = Post.where(author: author).order(:id).limit(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { posts(:welcome) }
+ assert posts(:thinking)
+ end
+
+ def test_delete_all_with_order_and_limit_and_offset_deletes_subset_only
+ author = authors(:david)
+ limited_posts = Post.where(author: author).order(:id).limit(1).offset(1)
+ assert_equal 1, limited_posts.size
+ assert_equal 2, limited_posts.limit(2).size
+ assert_equal 1, limited_posts.delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { posts(:thinking) }
+ assert posts(:welcome)
end
end
def test_update_many
- topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" }, nil => {} }
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
updated = Topic.update(topic_data.keys, topic_data.values)
assert_equal [1, 2], updated.map(&:id)
@@ -78,10 +107,33 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal "2 updated", Topic.find(2).content
end
+ def test_update_many_with_duplicated_ids
+ updated = Topic.update([1, 1, 2], [
+ { "content" => "1 duplicated" }, { "content" => "1 updated" }, { "content" => "2 updated" }
+ ])
+
+ assert_equal [1, 1, 2], updated.map(&:id)
+ assert_equal "1 updated", Topic.find(1).content
+ assert_equal "2 updated", Topic.find(2).content
+ end
+
+ def test_update_many_with_invalid_id
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" }, 99999 => {} }
+
+ assert_raise(ActiveRecord::RecordNotFound) do
+ Topic.update(topic_data.keys, topic_data.values)
+ end
+
+ assert_not_equal "1 updated", Topic.find(1).content
+ assert_not_equal "2 updated", Topic.find(2).content
+ end
+
def test_class_level_update_is_affected_by_scoping
topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
- assert_equal [], Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) }
+ assert_raise(ActiveRecord::RecordNotFound) do
+ Topic.where("1=0").scoping { Topic.update(topic_data.keys, topic_data.values) }
+ end
assert_not_equal "1 updated", Topic.find(1).content
assert_not_equal "2 updated", Topic.find(2).content
@@ -165,7 +217,7 @@ class PersistenceTest < ActiveRecord::TestCase
def test_destroy_all
conditions = "author_name = 'Mary'"
topics_by_mary = Topic.all.merge!(where: conditions, order: "id").to_a
- assert ! topics_by_mary.empty?
+ assert_not_empty topics_by_mary
assert_difference("Topic.count", -topics_by_mary.size) do
destroyed = Topic.where(conditions).destroy_all.sort_by(&:id)
@@ -175,23 +227,40 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_destroy_many
- clients = Client.all.merge!(order: "id").find([2, 3])
+ clients = Client.find([2, 3])
assert_difference("Client.count", -2) do
- destroyed = Client.destroy([2, 3, nil]).sort_by(&:id)
+ destroyed = Client.destroy([2, 3])
assert_equal clients, destroyed
assert destroyed.all?(&:frozen?), "destroyed clients should be frozen"
end
end
+ def test_destroy_many_with_invalid_id
+ clients = Client.find([2, 3])
+
+ assert_raise(ActiveRecord::RecordNotFound) do
+ Client.destroy([2, 3, 99999])
+ end
+
+ assert_equal clients, Client.find([2, 3])
+ end
+
def test_becomes
assert_kind_of Reply, topics(:first).becomes(Reply)
assert_equal "The First Topic", topics(:first).becomes(Reply).title
end
+ def test_becomes_after_reload_schema_from_cache
+ Reply.define_attribute_methods
+ Reply.serialize(:content) # invoke reload_schema_from_cache
+ assert_kind_of Reply, topics(:first).becomes(Reply)
+ assert_equal "The First Topic", topics(:first).becomes(Reply).title
+ end
+
def test_becomes_includes_errors
company = Company.new(name: nil)
- assert !company.valid?
+ assert_not_predicate company, :valid?
original_errors = company.errors
client = company.becomes(Client)
assert_equal original_errors.keys, client.errors.keys
@@ -308,8 +377,8 @@ class PersistenceTest < ActiveRecord::TestCase
developer.destroy
new_developer = developer.dup
new_developer.save
- assert new_developer.persisted?
- assert_not new_developer.destroyed?
+ assert_predicate new_developer, :persisted?
+ assert_not_predicate new_developer, :destroyed?
end
def test_create_many
@@ -320,12 +389,13 @@ class PersistenceTest < ActiveRecord::TestCase
def test_create_columns_not_equal_attributes
topic = Topic.instantiate(
- "attributes" => {
- "title" => "Another New Topic",
- "does_not_exist" => "test"
- }
+ "title" => "Another New Topic",
+ "does_not_exist" => "test"
)
+ topic = topic.dup # reset @new_record
assert_nothing_raised { topic.save }
+ assert_predicate topic, :persisted?
+ assert_equal "Another New Topic", topic.reload.title
end
def test_create_through_factory_with_block
@@ -372,6 +442,8 @@ class PersistenceTest < ActiveRecord::TestCase
topic_reloaded = Topic.instantiate(topic.attributes.merge("does_not_exist" => "test"))
topic_reloaded.title = "A New Topic"
assert_nothing_raised { topic_reloaded.save }
+ assert_predicate topic_reloaded, :persisted?
+ assert_equal "A New Topic", topic_reloaded.reload.title
end
def test_update_for_record_with_only_primary_key
@@ -408,6 +480,22 @@ class PersistenceTest < ActiveRecord::TestCase
assert_instance_of Reply, Reply.find(reply.id)
end
+ def test_becomes_default_sti_subclass
+ original_type = Topic.columns_hash["type"].default
+ ActiveRecord::Base.connection.change_column_default :topics, :type, "Reply"
+ Topic.reset_column_information
+
+ reply = topics(:second)
+ assert_instance_of Reply, reply
+
+ topic = reply.becomes(Topic)
+ assert_instance_of Topic, topic
+
+ ensure
+ ActiveRecord::Base.connection.change_column_default :topics, :type, original_type
+ Topic.reset_column_information
+ end
+
def test_update_after_create
klass = Class.new(Topic) do
def self.name; "Topic"; end
@@ -473,10 +561,18 @@ class PersistenceTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
end
- def test_record_not_found_exception
+ def test_find_raises_record_not_found_exception
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(99999) }
end
+ def test_update_raises_record_not_found_exception
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.update(99999, approved: true) }
+ end
+
+ def test_destroy_raises_record_not_found_exception
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.destroy(99999) }
+ end
+
def test_update_all
assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
assert_equal "bulk updated!", Topic.find(1).content
@@ -526,40 +622,65 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_delete_new_record
- client = Client.new
+ client = Client.new(name: "37signals")
client.delete
- assert client.frozen?
+ assert_predicate client, :frozen?
+
+ assert_not client.save
+ assert_raise(ActiveRecord::RecordNotSaved) { client.save! }
+
+ assert_predicate client, :frozen?
+ assert_raise(RuntimeError) { client.name = "something else" }
end
def test_delete_record_with_associations
client = Client.find(3)
client.delete
- assert client.frozen?
+ assert_predicate client, :frozen?
assert_kind_of Firm, client.firm
+
+ assert_not client.save
+ assert_raise(ActiveRecord::RecordNotSaved) { client.save! }
+
+ assert_predicate client, :frozen?
assert_raise(RuntimeError) { client.name = "something else" }
end
def test_destroy_new_record
- client = Client.new
+ client = Client.new(name: "37signals")
client.destroy
- assert client.frozen?
+ assert_predicate client, :frozen?
+
+ assert_not client.save
+ assert_raise(ActiveRecord::RecordNotSaved) { client.save! }
+
+ assert_predicate client, :frozen?
+ assert_raise(RuntimeError) { client.name = "something else" }
end
def test_destroy_record_with_associations
client = Client.find(3)
client.destroy
- assert client.frozen?
+ assert_predicate client, :frozen?
assert_kind_of Firm, client.firm
+
+ assert_not client.save
+ assert_raise(ActiveRecord::RecordNotSaved) { client.save! }
+
+ assert_predicate client, :frozen?
assert_raise(RuntimeError) { client.name = "something else" }
end
def test_update_attribute
- assert !Topic.find(1).approved?
+ assert_not_predicate Topic.find(1), :approved?
Topic.find(1).update_attribute("approved", true)
- assert Topic.find(1).approved?
+ assert_predicate Topic.find(1), :approved?
Topic.find(1).update_attribute(:approved, false)
- assert !Topic.find(1).approved?
+ assert_not_predicate Topic.find(1), :approved?
+
+ Topic.find(1).update_attribute(:change_approved_before_save, true)
+ assert_predicate Topic.find(1), :approved?
end
def test_update_attribute_for_readonly_attribute
@@ -596,14 +717,14 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_column
topic = Topic.find(1)
topic.update_column("approved", true)
- assert topic.approved?
+ assert_predicate topic, :approved?
topic.reload
- assert topic.approved?
+ assert_predicate topic, :approved?
topic.update_column(:approved, false)
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
topic.reload
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
end
def test_update_column_should_not_use_setter_method
@@ -690,10 +811,10 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update_columns
topic = Topic.find(1)
topic.update_columns("approved" => true, title: "Sebastian Topic")
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_equal "Sebastian Topic", topic.title
topic.reload
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_equal "Sebastian Topic", topic.title
end
@@ -801,33 +922,33 @@ class PersistenceTest < ActiveRecord::TestCase
def test_update
topic = Topic.find(1)
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
assert_equal "The First Topic", topic.title
topic.update("approved" => true, "title" => "The First Topic Updated")
topic.reload
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_equal "The First Topic Updated", topic.title
topic.update(approved: false, title: "The First Topic")
topic.reload
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
assert_equal "The First Topic", topic.title
end
def test_update_attributes
topic = Topic.find(1)
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
assert_equal "The First Topic", topic.title
topic.update_attributes("approved" => true, "title" => "The First Topic Updated")
topic.reload
- assert topic.approved?
+ assert_predicate topic, :approved?
assert_equal "The First Topic Updated", topic.title
topic.update_attributes(approved: false, title: "The First Topic")
topic.reload
- assert !topic.approved?
+ assert_not_predicate topic, :approved?
assert_equal "The First Topic", topic.title
error = assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do
@@ -928,7 +1049,7 @@ class PersistenceTest < ActiveRecord::TestCase
Topic.find(1).replies << should_be_destroyed_reply
topic = Topic.destroy(1)
- assert topic.destroyed?
+ assert_predicate topic, :destroyed?
assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
@@ -938,7 +1059,9 @@ 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_nil Topic.where("1=0").scoping { Topic.destroy(1) }
+ assert_raise(ActiveRecord::RecordNotFound) do
+ Topic.where("1=0").scoping { Topic.destroy(1) }
+ end
assert_nothing_raised { Topic.find(1) }
assert_nothing_raised { Reply.find(should_not_be_destroyed_reply.id) }
@@ -1006,13 +1129,13 @@ class PersistenceTest < ActiveRecord::TestCase
def test_find_via_reload
post = Post.new
- assert post.new_record?
+ assert_predicate post, :new_record?
post.id = 1
post.reload
assert_equal "Welcome to the weblog", post.title
- assert_not post.new_record?
+ assert_not_predicate post, :new_record?
end
def test_reload_via_querycache
@@ -1060,13 +1183,18 @@ class PersistenceTest < ActiveRecord::TestCase
end
def test_reset_column_information_resets_children
- child = Class.new(Topic)
- child.new # force schema to load
+ child_class = Class.new(Topic)
+ child_class.new # force schema to load
ActiveRecord::Base.connection.add_column(:topics, :foo, :string)
Topic.reset_column_information
- assert_equal "bar", child.new(foo: :bar).foo
+ # this should redefine attribute methods
+ child_class.new
+
+ assert child_class.instance_methods.include?(:foo)
+ assert child_class.instance_methods.include?(:foo_changed?)
+ assert_equal "bar", child_class.new(foo: :bar).foo
ensure
ActiveRecord::Base.connection.remove_column(:topics, :foo)
Topic.reset_column_information
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 7f6c2382ca..60dac91ec9 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -207,13 +207,13 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_serial_with_quoted_sequence_name
column = MixedCaseMonkey.columns_hash[MixedCaseMonkey.primary_key]
assert_equal "nextval('\"mixed_case_monkeys_monkeyID_seq\"'::regclass)", column.default_function
- assert column.serial?
+ assert_predicate column, :serial?
end
def test_serial_with_unquoted_sequence_name
column = Topic.columns_hash[Topic.primary_key]
assert_equal "nextval('topics_id_seq'::regclass)", column.default_function
- assert column.serial?
+ assert_predicate column, :serial?
end
end
end
@@ -298,6 +298,8 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase
assert_not column.null
assert_equal :string, column.type
assert_equal 42, column.limit
+ ensure
+ Barcode.reset_column_information
end
test "schema dump primary key includes type and options" do
@@ -428,7 +430,7 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
@connection.create_table(:widgets, id: @pk_type, force: true)
column = @connection.columns(:widgets).find { |c| c.name == "id" }
assert_equal :integer, column.type
- assert_not column.bigint?
+ assert_not_predicate column, :bigint?
end
test "primary key with serial/integer are automatically numbered" do
@@ -447,10 +449,10 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
test "primary key column type with options" do
@connection.create_table(:widgets, id: :primary_key, limit: 4, unsigned: true, force: true)
column = @connection.columns(:widgets).find { |c| c.name == "id" }
- assert column.auto_increment?
+ assert_predicate column, :auto_increment?
assert_equal :integer, column.type
- assert_not column.bigint?
- assert column.unsigned?
+ assert_not_predicate column, :bigint?
+ assert_predicate column, :unsigned?
schema = dump_table_schema "widgets"
assert_match %r{create_table "widgets", id: :integer, unsigned: true, }, schema
@@ -459,10 +461,10 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
test "bigint primary key with unsigned" do
@connection.create_table(:widgets, id: :bigint, unsigned: true, force: true)
column = @connection.columns(:widgets).find { |c| c.name == "id" }
- assert column.auto_increment?
+ assert_predicate column, :auto_increment?
assert_equal :integer, column.type
- assert column.bigint?
- assert column.unsigned?
+ assert_predicate column, :bigint?
+ assert_predicate column, :unsigned?
schema = dump_table_schema "widgets"
assert_match %r{create_table "widgets", id: :bigint, unsigned: true, }, schema
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 5cb537b623..8d37f5ea95 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -82,7 +82,7 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_cache :off, conn
end
- assert !ActiveRecord::Base.connection.nil?
+ assert_not_predicate ActiveRecord::Base.connection, :nil?
assert_cache :off
middleware {
@@ -283,7 +283,7 @@ class QueryCacheTest < ActiveRecord::TestCase
payload[:sql].downcase!
end
- assert_raises RuntimeError do
+ assert_raises frozen_error_class do
ActiveRecord::Base.cache do
assert_queries(1) { Task.find(1); Task.find(1) }
end
@@ -327,7 +327,7 @@ class QueryCacheTest < ActiveRecord::TestCase
conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2")
ActiveRecord::Base.connection_handler.establish_connection(conf)
Task.connection_specification_name = "test2"
- refute Task.connected?
+ assert_not_predicate Task, :connected?
Task.cache do
begin
@@ -414,20 +414,20 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_query_cache_does_not_establish_connection_if_unconnected
with_temporary_connection_pool do
ActiveRecord::Base.clear_active_connections!
- refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
+ assert_not ActiveRecord::Base.connection_handler.active_connections? # sanity check
middleware {
- refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup"
+ assert_not ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup"
}.call({})
- refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup"
+ assert_not ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup"
end
end
def test_query_cache_is_enabled_on_connections_established_after_middleware_runs
with_temporary_connection_pool do
ActiveRecord::Base.clear_active_connections!
- refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
+ assert_not ActiveRecord::Base.connection_handler.active_connections? # sanity check
middleware {
assert ActiveRecord::Base.connection.query_cache_enabled, "QueryCache did not get lazily enabled"
@@ -444,14 +444,23 @@ class QueryCacheTest < ActiveRecord::TestCase
assert ActiveRecord::Base.connection.query_cache_enabled
Thread.new {
- refute ActiveRecord::Base.connection_pool.query_cache_enabled
- refute ActiveRecord::Base.connection.query_cache_enabled
+ assert_not ActiveRecord::Base.connection_pool.query_cache_enabled
+ assert_not ActiveRecord::Base.connection.query_cache_enabled
}.join
}.call({})
end
end
+ def test_query_cache_is_enabled_on_all_connection_pools
+ middleware {
+ ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool|
+ assert pool.query_cache_enabled
+ assert pool.connection.query_cache_enabled
+ end
+ }.call({})
+ end
+
private
def middleware(&app)
executor = Class.new(ActiveSupport::Executor)
@@ -553,7 +562,7 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
assert_called(ActiveRecord::Base.connection, :clear_query_cache, times: 2) do
ActiveRecord::Base.cache do
p = Post.find(1)
- assert p.categories.any?
+ assert_predicate p.categories, :any?
p.categories.delete_all
end
end
diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb
index 897d252cf8..6534770c57 100644
--- a/activerecord/test/cases/quoting_test.rb
+++ b/activerecord/test/cases/quoting_test.rb
@@ -111,7 +111,7 @@ module ActiveRecord
end
def test_quote_bigdecimal
- bigdec = BigDecimal.new((1 << 100).to_s)
+ bigdec = BigDecimal((1 << 100).to_s)
assert_equal bigdec.to_s("F"), @quoter.quote(bigdec)
end
diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb
index d1b85cb4ef..383e43ed55 100644
--- a/activerecord/test/cases/readonly_test.rb
+++ b/activerecord/test/cases/readonly_test.rb
@@ -16,10 +16,10 @@ class ReadOnlyTest < ActiveRecord::TestCase
def test_cant_save_readonly_record
dev = Developer.find(1)
- assert !dev.readonly?
+ assert_not_predicate dev, :readonly?
dev.readonly!
- assert dev.readonly?
+ assert_predicate dev, :readonly?
assert_nothing_raised do
dev.name = "Luscious forbidden fruit."
@@ -54,7 +54,7 @@ class ReadOnlyTest < ActiveRecord::TestCase
def test_has_many_find_readonly
post = Post.find(1)
- assert !post.comments.empty?
+ assert_not_empty post.comments
assert !post.comments.any?(&:readonly?)
assert !post.comments.to_a.any?(&:readonly?)
assert post.comments.readonly(true).all?(&:readonly?)
@@ -66,44 +66,44 @@ class ReadOnlyTest < ActiveRecord::TestCase
end
def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_by_id
- assert !posts(:welcome).people.find(1).readonly?
+ assert_not_predicate posts(:welcome).people.find(1), :readonly?
end
def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_first
- assert !posts(:welcome).people.first.readonly?
+ assert_not_predicate posts(:welcome).people.first, :readonly?
end
def test_has_many_with_through_is_not_implicitly_marked_readonly_while_finding_last
- assert !posts(:welcome).people.last.readonly?
+ assert_not_predicate posts(:welcome).people.last, :readonly?
end
def test_readonly_scoping
Post.where("1=1").scoping do
- assert !Post.find(1).readonly?
- assert Post.readonly(true).find(1).readonly?
- assert !Post.readonly(false).find(1).readonly?
+ assert_not_predicate Post.find(1), :readonly?
+ assert_predicate Post.readonly(true).find(1), :readonly?
+ assert_not_predicate Post.readonly(false).find(1), :readonly?
end
Post.joins(" ").scoping do
- assert !Post.find(1).readonly?
- assert Post.readonly.find(1).readonly?
- assert !Post.readonly(false).find(1).readonly?
+ assert_not_predicate Post.find(1), :readonly?
+ assert_predicate Post.readonly.find(1), :readonly?
+ assert_not_predicate Post.readonly(false).find(1), :readonly?
end
# Oracle barfs on this because the join includes unqualified and
# conflicting column names
unless current_adapter?(:OracleAdapter)
Post.joins(", developers").scoping do
- assert_not Post.find(1).readonly?
- assert Post.readonly.find(1).readonly?
- assert !Post.readonly(false).find(1).readonly?
+ assert_not_predicate Post.find(1), :readonly?
+ assert_predicate Post.readonly.find(1), :readonly?
+ assert_not_predicate Post.readonly(false).find(1), :readonly?
end
end
Post.readonly(true).scoping do
- assert Post.find(1).readonly?
- assert Post.readonly.find(1).readonly?
- assert !Post.readonly(false).find(1).readonly?
+ assert_predicate Post.find(1), :readonly?
+ assert_predicate Post.readonly.find(1), :readonly?
+ assert_not_predicate Post.readonly(false).find(1), :readonly?
end
end
@@ -111,10 +111,10 @@ class ReadOnlyTest < ActiveRecord::TestCase
developer = Developer.find(1)
project = Post.find(1)
- assert !developer.projects.all_as_method.first.readonly?
- assert !developer.projects.all_as_scope.first.readonly?
+ assert_not_predicate developer.projects.all_as_method.first, :readonly?
+ assert_not_predicate developer.projects.all_as_scope.first, :readonly?
- assert !project.comments.all_as_method.first.readonly?
- assert !project.comments.all_as_scope.first.readonly?
+ assert_not_predicate project.comments.all_as_method.first, :readonly?
+ assert_not_predicate project.comments.all_as_scope.first, :readonly?
end
end
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
index 49170abe6f..61cb0f130d 100644
--- a/activerecord/test/cases/reaper_test.rb
+++ b/activerecord/test/cases/reaper_test.rb
@@ -18,6 +18,7 @@ module ActiveRecord
class FakePool
attr_reader :reaped
+ attr_reader :flushed
def initialize
@reaped = false
@@ -26,6 +27,10 @@ module ActiveRecord
def reap
@reaped = true
end
+
+ def flush
+ @flushed = true
+ end
end
# A reaper with nil time should never reap connections
@@ -47,6 +52,7 @@ module ActiveRecord
Thread.pass
end
assert fp.reaped
+ assert fp.flushed
end
def test_pool_has_reaper
@@ -73,14 +79,14 @@ module ActiveRecord
end
Thread.pass while conn.nil?
- assert conn.in_use?
+ assert_predicate conn, :in_use?
child.terminate
while conn.in_use?
Thread.pass
end
- assert !conn.in_use?
+ assert_not_predicate conn, :in_use?
end
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 37c2235f1a..ed19192ad9 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -66,6 +66,9 @@ class ReflectionTest < ActiveRecord::TestCase
def test_column_string_type_and_limit
assert_equal :string, @first.column_for_attribute("title").type
+ assert_equal :string, @first.column_for_attribute(:title).type
+ assert_equal :string, @first.type_for_attribute("title").type
+ assert_equal :string, @first.type_for_attribute(:title).type
assert_equal 250, @first.column_for_attribute("title").limit
end
@@ -81,6 +84,9 @@ class ReflectionTest < ActiveRecord::TestCase
def test_integer_columns
assert_equal :integer, @first.column_for_attribute("id").type
+ assert_equal :integer, @first.column_for_attribute(:id).type
+ assert_equal :integer, @first.type_for_attribute("id").type
+ assert_equal :integer, @first.type_for_attribute(:id).type
end
def test_non_existent_columns_return_null_object
@@ -89,6 +95,9 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "attribute_that_doesnt_exist", column.name
assert_nil column.sql_type
assert_nil column.type
+
+ column = @first.column_for_attribute(:attribute_that_doesnt_exist)
+ assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column
end
def test_non_existent_types_are_identity_types
@@ -98,6 +107,11 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal object, type.deserialize(object)
assert_equal object, type.cast(object)
assert_equal object, type.serialize(object)
+
+ type = @first.type_for_attribute(:attribute_that_doesnt_exist)
+ assert_equal object, type.deserialize(object)
+ assert_equal object, type.cast(object)
+ assert_equal object, type.serialize(object)
end
def test_reflection_klass_for_nested_class_name
@@ -149,7 +163,7 @@ class ReflectionTest < ActiveRecord::TestCase
expected = Pirate.reflect_on_all_associations.select { |r| r.options[:autosave] }
received = Pirate.reflect_on_all_autosave_associations
- assert !received.empty?
+ assert_not_empty received
assert_not_equal Pirate.reflect_on_all_associations.length, received.length
assert_equal expected, received
end
@@ -254,23 +268,34 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case
- @hotel = Hotel.create!
- @department = @hotel.departments.create!
- @department.chefs.create!(employable: CakeDesigner.create!)
- @department.chefs.create!(employable: DrinkDesigner.create!)
+ hotel = Hotel.create!
+ department = hotel.departments.create!
+ department.chefs.create!(employable: CakeDesigner.create!)
+ department.chefs.create!(employable: DrinkDesigner.create!)
- assert_equal 1, @hotel.cake_designers.size
- assert_equal 1, @hotel.drink_designers.size
- assert_equal 2, @hotel.chefs.size
+ assert_equal 1, hotel.cake_designers.size
+ assert_equal 1, hotel.cake_designers.count
+ assert_equal 1, hotel.drink_designers.size
+ assert_equal 1, hotel.drink_designers.count
+ assert_equal 2, hotel.chefs.size
+ assert_equal 2, hotel.chefs.count
end
def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case_and_sti
- @hotel = Hotel.create!
- @hotel.mocktail_designers << MocktailDesigner.create!
+ hotel = Hotel.create!
+ hotel.mocktail_designers << MocktailDesigner.create!
+
+ assert_equal 1, hotel.mocktail_designers.size
+ assert_equal 1, hotel.mocktail_designers.count
+ assert_equal 1, hotel.chef_lists.size
+ assert_equal 1, hotel.chef_lists.count
+
+ hotel.mocktail_designers = []
- assert_equal 1, @hotel.mocktail_designers.size
- assert_equal 1, @hotel.mocktail_designers.count
- assert_equal 1, @hotel.chef_lists.size
+ assert_equal 0, hotel.mocktail_designers.size
+ assert_equal 0, hotel.mocktail_designers.count
+ assert_equal 0, hotel.chef_lists.size
+ assert_equal 0, hotel.chef_lists.count
end
def test_scope_chain_of_polymorphic_association_does_not_leak_into_other_hmt_associations
@@ -290,12 +315,12 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_nested?
- assert !Author.reflect_on_association(:comments).nested?
- assert Author.reflect_on_association(:tags).nested?
+ assert_not_predicate Author.reflect_on_association(:comments), :nested?
+ assert_predicate Author.reflect_on_association(:tags), :nested?
# Only goes :through once, but the through_reflection is a has_and_belongs_to_many, so this is
# a nested through association
- assert Category.reflect_on_association(:post_comments).nested?
+ assert_predicate Category.reflect_on_association(:post_comments), :nested?
end
def test_association_primary_key
@@ -310,15 +335,6 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "custom_primary_key", Author.reflect_on_association(:tags_with_primary_key).association_primary_key.to_s # nested
end
- def test_association_primary_key_type
- # Normal Association
- assert_equal :integer, Author.reflect_on_association(:posts).association_primary_key_type.type
- assert_equal :string, Author.reflect_on_association(:essay).association_primary_key_type.type
-
- # Through Association
- assert_equal :string, Author.reflect_on_association(:essay_category).association_primary_key_type.type
- end
-
def test_association_primary_key_raises_when_missing_primary_key
reflection = ActiveRecord::Reflection.create(:has_many, :edge, nil, {}, Author)
assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key }
@@ -352,36 +368,36 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_collection_association
- assert Pirate.reflect_on_association(:birds).collection?
- assert Pirate.reflect_on_association(:parrots).collection?
+ assert_predicate Pirate.reflect_on_association(:birds), :collection?
+ assert_predicate Pirate.reflect_on_association(:parrots), :collection?
- assert !Pirate.reflect_on_association(:ship).collection?
- assert !Ship.reflect_on_association(:pirate).collection?
+ assert_not_predicate Pirate.reflect_on_association(:ship), :collection?
+ assert_not_predicate Ship.reflect_on_association(:pirate), :collection?
end
def test_default_association_validation
- assert ActiveRecord::Reflection.create(:has_many, :clients, nil, {}, Firm).validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, {}, Firm), :validate?
- assert !ActiveRecord::Reflection.create(:has_one, :client, nil, {}, Firm).validate?
- assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, {}, Firm).validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, {}, Firm), :validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, {}, Firm), :validate?
end
def test_always_validate_association_if_explicit
- assert ActiveRecord::Reflection.create(:has_one, :client, nil, { validate: true }, Firm).validate?
- assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { validate: true }, Firm).validate?
- assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { validate: true }, Firm).validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { validate: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { validate: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { validate: true }, Firm), :validate?
end
def test_validate_association_if_autosave
- assert ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true }, Firm).validate?
- assert ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true }, Firm).validate?
- assert ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true }, Firm).validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true }, Firm), :validate?
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true }, Firm), :validate?
end
def test_never_validate_association_if_explicit
- assert !ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true, validate: false }, Firm).validate?
- assert !ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true, validate: false }, Firm).validate?
- assert !ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true, validate: false }, Firm).validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:has_one, :client, nil, { autosave: true, validate: false }, Firm), :validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:belongs_to, :client, nil, { autosave: true, validate: false }, Firm), :validate?
+ assert_not_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { autosave: true, validate: false }, Firm), :validate?
end
def test_foreign_key
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index b68b3723f6..074ce9454f 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -58,7 +58,7 @@ class RelationMergingTest < ActiveRecord::TestCase
def test_relation_merging_with_locks
devs = Developer.lock.where("salary >= 80000").order("id DESC").merge(Developer.limit(2))
- assert devs.locked?
+ assert_predicate devs, :locked?
end
def test_relation_merging_with_preload
@@ -72,6 +72,12 @@ class RelationMergingTest < ActiveRecord::TestCase
assert_equal 1, comments.count
end
+ def test_relation_merging_with_left_outer_joins
+ comments = Comment.joins(:post).where(body: "Thank you for the welcome").merge(Post.left_outer_joins(:author).where(body: "Such a lovely day"))
+
+ assert_equal 1, comments.count
+ end
+
def test_relation_merging_with_association
assert_queries(2) do # one for loading post, and another one merged query
post = Post.where(body: "Such a lovely day").first
@@ -107,9 +113,9 @@ class RelationMergingTest < ActiveRecord::TestCase
def test_merging_with_from_clause
relation = Post.all
- assert relation.from_clause.empty?
+ assert_empty relation.from_clause
relation = relation.merge(Post.from("posts"))
- refute relation.from_clause.empty?
+ assert_not_empty relation.from_clause
end
end
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index ad3700b73a..1428b3e132 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -25,7 +25,7 @@ module ActiveRecord
test "#order! with symbol prepends the table name" do
assert relation.order!(:name).equal?(relation)
node = relation.order_values.first
- assert node.ascending?
+ assert_predicate node, :ascending?
assert_equal :name, node.expr.name
assert_equal "posts", node.expr.relation.name
end
@@ -88,7 +88,7 @@ module ActiveRecord
assert relation.reorder!(:name).equal?(relation)
node = relation.order_values.first
- assert node.ascending?
+ assert_predicate node, :ascending?
assert_equal :name, node.expr.name
assert_equal "posts", node.expr.relation.name
end
@@ -139,7 +139,7 @@ module ActiveRecord
private
def relation
- @relation ||= Relation.new(FakeKlass, Post.arel_table, Post.predicate_builder)
+ @relation ||= Relation.new(FakeKlass)
end
end
end
diff --git a/activerecord/test/cases/relation/where_clause_test.rb b/activerecord/test/cases/relation/where_clause_test.rb
index e5eb159d36..8703d238a0 100644
--- a/activerecord/test/cases/relation/where_clause_test.rb
+++ b/activerecord/test/cases/relation/where_clause_test.rb
@@ -75,8 +75,8 @@ class ActiveRecord::Relation
end
test "a clause knows if it is empty" do
- assert WhereClause.empty.empty?
- assert_not WhereClause.new(["anything"]).empty?
+ assert_empty WhereClause.empty
+ assert_not_empty WhereClause.new(["anything"])
end
test "invert cannot handle nil" do
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index d95a54a2fe..99797528b2 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -265,7 +265,7 @@ module ActiveRecord
end
def test_where_with_decimal_for_string_column
- count = Post.where(title: BigDecimal.new(0)).count
+ count = Post.where(title: BigDecimal(0)).count
assert_equal 0, count
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 8362722e12..4e75371147 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -11,30 +11,29 @@ module ActiveRecord
fixtures :posts, :comments, :authors, :author_addresses, :ratings
def test_construction
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass, table: :b)
assert_equal FakeKlass, relation.klass
assert_equal :b, relation.table
assert !relation.loaded, "relation is not loaded"
end
def test_responds_to_model_and_returns_klass
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
assert_equal FakeKlass, relation.model
end
def test_initialize_single_values
- relation = Relation.new(FakeKlass, :b, nil)
- (Relation::SINGLE_VALUE_METHODS - [:create_with, :readonly]).each do |method|
+ relation = Relation.new(FakeKlass)
+ (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
- assert_equal false, relation.readonly_value
value = relation.create_with_value
assert_equal({}, value)
assert_predicate value, :frozen?
end
def test_multi_value_initialize
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
Relation::MULTI_VALUE_METHODS.each do |method|
values = relation.send("#{method}_values")
assert_equal [], values, method.to_s
@@ -43,49 +42,49 @@ module ActiveRecord
end
def test_extensions
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
assert_equal [], relation.extensions
end
def test_empty_where_values_hash
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
assert_equal({}, relation.where_values_hash)
end
def test_has_values
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ relation = Relation.new(Post)
relation.where!(id: 10)
assert_equal({ "id" => 10 }, relation.where_values_hash)
end
def test_values_wrong_table
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ relation = Relation.new(Post)
relation.where! Comment.arel_table[:id].eq(10)
assert_equal({}, relation.where_values_hash)
end
def test_tree_is_not_traversed
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ relation = Relation.new(Post)
left = relation.table[:id].eq(10)
right = relation.table[:id].eq(10)
- combine = left.and right
+ combine = left.or(right)
relation.where! combine
assert_equal({}, relation.where_values_hash)
end
def test_scope_for_create
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
assert_equal({}, relation.scope_for_create)
end
def test_create_with_value
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ relation = Relation.new(Post)
relation.create_with_value = { hello: "world" }
assert_equal({ "hello" => "world" }, relation.scope_for_create)
end
def test_create_with_value_with_wheres
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ relation = Relation.new(Post)
assert_equal({}, relation.scope_for_create)
relation.where!(id: 10)
@@ -96,11 +95,11 @@ module ActiveRecord
end
def test_empty_scope
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
- assert relation.empty_scope?
+ relation = Relation.new(Post)
+ assert_predicate relation, :empty_scope?
relation.merge!(relation)
- assert relation.empty_scope?
+ assert_predicate relation, :empty_scope?
end
def test_bad_constants_raise_errors
@@ -110,31 +109,31 @@ module ActiveRecord
end
def test_empty_eager_loading?
- relation = Relation.new(FakeKlass, :b, nil)
- assert !relation.eager_loading?
+ relation = Relation.new(FakeKlass)
+ assert_not_predicate relation, :eager_loading?
end
def test_eager_load_values
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
relation.eager_load! :b
- assert relation.eager_loading?
+ assert_predicate relation, :eager_loading?
end
def test_references_values
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
assert_equal [], relation.references_values
relation = relation.references(:foo).references(:omg, :lol)
assert_equal ["foo", "omg", "lol"], relation.references_values
end
def test_references_values_dont_duplicate
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
relation = relation.references(:foo).references(:foo)
assert_equal ["foo"], relation.references_values
end
test "merging a hash into a relation" do
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
+ relation = Relation.new(Post)
relation = relation.merge where: { name: :lol }, readonly: true
assert_equal({ "name" => :lol }, relation.where_clause.to_h)
@@ -142,7 +141,7 @@ module ActiveRecord
end
test "merging an empty hash into a relation" do
- assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass, :b, nil).merge({}).where_clause
+ assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass).merge({}).where_clause
end
test "merging a hash with unknown keys raises" do
@@ -150,7 +149,7 @@ module ActiveRecord
end
test "merging nil or false raises" do
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
e = assert_raises(ArgumentError) do
relation = relation.merge nil
@@ -166,7 +165,7 @@ module ActiveRecord
end
test "#values returns a dup of the values" do
- relation = Relation.new(Post, Post.arel_table, Post.predicate_builder).where!(name: :foo)
+ relation = Relation.new(Post).where!(name: :foo)
values = relation.values
values[:where] = nil
@@ -174,7 +173,7 @@ module ActiveRecord
end
test "relations can be created with a values hash" do
- relation = Relation.new(FakeKlass, :b, nil, select: [:foo])
+ relation = Relation.new(FakeKlass, values: { select: [:foo] })
assert_equal [:foo], relation.select_values
end
@@ -186,13 +185,13 @@ module ActiveRecord
end
end
- relation = Relation.new(klass, :b, nil)
+ relation = Relation.new(klass)
relation.merge!(where: ["foo = ?", "bar"])
assert_equal Relation::WhereClause.new(["foo = bar"]), relation.where_clause
end
def test_merging_readonly_false
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
readonly_false_relation = relation.readonly(false)
# test merging in both directions
assert_equal false, relation.merge(readonly_false_relation).readonly_value
@@ -236,17 +235,17 @@ module ActiveRecord
def test_merge_raises_with_invalid_argument
assert_raises ArgumentError do
- relation = Relation.new(FakeKlass, :b, nil)
+ relation = Relation.new(FakeKlass)
relation.merge(true)
end
end
def test_respond_to_for_non_selected_element
post = Post.select(:title).first
- assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception"
+ assert_not_respond_to post, :body, "post should not respond_to?(:body) since invoking it raises exception"
silence_warnings { post = Post.select("'title' as post_title").first }
- assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception"
+ assert_not_respond_to post, :title, "post should not respond_to?(:body) since invoking it raises exception"
end
def test_select_quotes_when_using_from_clause
@@ -288,13 +287,18 @@ module ActiveRecord
:string
end
+ def cast(value)
+ raise value unless value == "value from user"
+ "cast value"
+ end
+
def deserialize(value)
raise value unless value == "type cast for database"
"type cast from database"
end
def serialize(value)
- raise value unless value == "value from user"
+ raise value unless value == "cast value"
"type cast for database"
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index eec43ef79e..bc7c54dbe0 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -56,7 +56,7 @@ class RelationTest < ActiveRecord::TestCase
def test_dynamic_finder
x = Post.where("author_id = ?", 1)
- assert x.klass.respond_to?(:find_by_id), "@klass should handle dynamic finders"
+ assert_respond_to x.klass, :find_by_id
end
def test_multivalue_where
@@ -98,7 +98,7 @@ class RelationTest < ActiveRecord::TestCase
2.times { assert_equal 5, topics.to_a.size }
end
- assert topics.loaded?
+ assert_predicate topics, :loaded?
end
def test_scoped_first
@@ -108,7 +108,7 @@ class RelationTest < ActiveRecord::TestCase
2.times { assert_equal "The First Topic", topics.first.title }
end
- assert ! topics.loaded?
+ assert_not_predicate topics, :loaded?
end
def test_loaded_first
@@ -119,7 +119,7 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "The First Topic", topics.first.title
end
- assert topics.loaded?
+ assert_predicate topics, :loaded?
end
def test_loaded_first_with_limit
@@ -131,7 +131,7 @@ class RelationTest < ActiveRecord::TestCase
"The Second Topic of the day"], topics.first(2).map(&:title)
end
- assert topics.loaded?
+ assert_predicate topics, :loaded?
end
def test_first_get_more_than_available
@@ -152,14 +152,14 @@ class RelationTest < ActiveRecord::TestCase
2.times { topics.to_a }
end
- assert topics.loaded?
+ assert_predicate topics, :loaded?
original_size = topics.to_a.size
Topic.create! title: "fake"
assert_queries(1) { topics.reload }
assert_equal original_size + 1, topics.size
- assert topics.loaded?
+ assert_predicate topics, :loaded?
end
def test_finding_with_subquery
@@ -250,7 +250,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_reverse_order_with_function
- topics = Topic.order("length(title)").reverse_order
+ topics = Topic.order(Arel.sql("length(title)")).reverse_order
assert_equal topics(:second).title, topics.first.title
end
@@ -260,24 +260,24 @@ class RelationTest < ActiveRecord::TestCase
end
def test_reverse_order_with_function_other_predicates
- topics = Topic.order("author_name, length(title), id").reverse_order
+ topics = Topic.order(Arel.sql("author_name, length(title), id")).reverse_order
assert_equal topics(:second).title, topics.first.title
- topics = Topic.order("length(author_name), id, length(title)").reverse_order
+ topics = Topic.order(Arel.sql("length(author_name), id, length(title)")).reverse_order
assert_equal topics(:fifth).title, topics.first.title
end
def test_reverse_order_with_multiargument_function
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(author_name, title)").reverse_order
+ Topic.order(Arel.sql("concat(author_name, title)")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(lower(author_name), title)").reverse_order
+ Topic.order(Arel.sql("concat(lower(author_name), title)")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(author_name, lower(title))").reverse_order
+ Topic.order(Arel.sql("concat(author_name, lower(title))")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("concat(lower(author_name), title, length(title)").reverse_order
+ Topic.order(Arel.sql("concat(lower(author_name), title, length(title)")).reverse_order
end
end
@@ -289,10 +289,10 @@ class RelationTest < ActiveRecord::TestCase
def test_reverse_order_with_nulls_first_or_last
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("title NULLS FIRST").reverse_order
+ Topic.order(Arel.sql("title NULLS FIRST")).reverse_order
end
assert_raises(ActiveRecord::IrreversibleOrderError) do
- Topic.order("title nulls last").reverse_order
+ Topic.order(Arel.sql("title nulls last")).reverse_order
end
end
@@ -385,29 +385,29 @@ class RelationTest < ActiveRecord::TestCase
def test_finding_with_cross_table_order_and_limit
tags = Tag.includes(:taggings).
- order("tags.name asc", "taggings.taggable_id asc", "REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").
+ order("tags.name asc", "taggings.taggable_id asc", Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).
limit(1).to_a
assert_equal 1, tags.length
end
def test_finding_with_complex_order_and_limit
- tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a
+ tags = Tag.includes(:taggings).references(:taggings).order(Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).limit(1).to_a
assert_equal 1, tags.length
end
def test_finding_with_complex_order
- tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a
+ tags = Tag.includes(:taggings).references(:taggings).order(Arel.sql("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)")).to_a
assert_equal 3, tags.length
end
def test_finding_with_sanitized_order
- query = Tag.order(["field(id, ?)", [1, 3, 2]]).to_sql
+ query = Tag.order([Arel.sql("field(id, ?)"), [1, 3, 2]]).to_sql
assert_match(/field\(id, 1,3,2\)/, query)
- query = Tag.order(["field(id, ?)", []]).to_sql
+ query = Tag.order([Arel.sql("field(id, ?)"), []]).to_sql
assert_match(/field\(id, NULL\)/, query)
- query = Tag.order(["field(id, ?)", nil]).to_sql
+ query = Tag.order([Arel.sql("field(id, ?)"), nil]).to_sql
assert_match(/field\(id, NULL\)/, query)
end
@@ -501,12 +501,12 @@ class RelationTest < ActiveRecord::TestCase
relation = Topic.all
["find_by_title", "find_by_title_and_author_name"].each do |method|
- assert_respond_to relation, method, "Topic.all should respond to #{method.inspect}"
+ assert_respond_to relation, method
end
end
def test_respond_to_class_methods_and_scopes
- assert Topic.all.respond_to?(:by_lifo)
+ assert_respond_to Topic.all, :by_lifo
end
def test_find_with_readonly_option
@@ -601,7 +601,7 @@ class RelationTest < ActiveRecord::TestCase
reader = Reader.create! post_id: post.id, person_id: 1
comment = Comment.create! post_id: post.id, body: "body"
- assert !comment.respond_to?(:readers)
+ assert_not_respond_to comment, :readers
post_rel = Post.preload(:readers).joins(:readers).where(title: "Uhuu")
result_comment = Comment.joins(:post).merge(post_rel).to_a.first
@@ -722,7 +722,7 @@ class RelationTest < ActiveRecord::TestCase
def test_find_in_empty_array
authors = Author.all.where(id: [])
- assert authors.to_a.blank?
+ assert_predicate authors.to_a, :blank?
end
def test_where_with_ar_object
@@ -780,8 +780,6 @@ class RelationTest < ActiveRecord::TestCase
def test_find_all_using_where_with_relation
david = authors(:david)
- # switching the lines below would succeed in current rails
- # assert_queries(2) {
assert_queries(1) {
relation = Author.where(id: Author.where(id: david.id))
assert_equal [david], relation.to_a
@@ -820,8 +818,6 @@ class RelationTest < ActiveRecord::TestCase
def test_find_all_using_where_with_relation_and_alternate_primary_key
cool_first = minivans(:cool_first)
- # switching the lines below would succeed in current rails
- # assert_queries(2) {
assert_queries(1) {
relation = Minivan.where(minivan_id: Minivan.where(name: cool_first.name))
assert_equal [cool_first], relation.to_a
@@ -867,19 +863,19 @@ class RelationTest < ActiveRecord::TestCase
# Force load
assert_equal [authors(:david)], davids.to_a
- assert davids.loaded?
+ assert_predicate davids, :loaded?
assert_difference("Author.count", -1) { davids.destroy_all }
assert_equal [], davids.to_a
- assert davids.loaded?
+ assert_predicate davids, :loaded?
end
def test_delete_all
davids = Author.where(name: "David")
assert_difference("Author.count", -1) { davids.delete_all }
- assert ! davids.loaded?
+ assert_not_predicate davids, :loaded?
end
def test_delete_all_loaded
@@ -887,20 +883,18 @@ class RelationTest < ActiveRecord::TestCase
# Force load
assert_equal [authors(:david)], davids.to_a
- assert davids.loaded?
+ assert_predicate davids, :loaded?
assert_difference("Author.count", -1) { davids.delete_all }
assert_equal [], davids.to_a
- assert davids.loaded?
+ assert_predicate davids, :loaded?
end
def test_delete_all_with_unpermitted_relation_raises_error
- assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all }
assert_raises(ActiveRecord::ActiveRecordError) { Author.distinct.delete_all }
assert_raises(ActiveRecord::ActiveRecordError) { Author.group(:name).delete_all }
assert_raises(ActiveRecord::ActiveRecordError) { Author.having("SUM(id) < 3").delete_all }
- assert_raises(ActiveRecord::ActiveRecordError) { Author.offset(10).delete_all }
end
def test_select_with_aggregates
@@ -908,9 +902,9 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 11, posts.count(:all)
assert_equal 11, posts.size
- assert posts.any?
- assert posts.many?
- assert_not posts.empty?
+ assert_predicate posts, :any?
+ assert_predicate posts, :many?
+ assert_not_empty posts
end
def test_select_takes_a_variable_list_of_args
@@ -956,7 +950,7 @@ class RelationTest < ActiveRecord::TestCase
assert_equal author.posts.where(author_id: author.id).size, posts.count
assert_equal 0, author.posts.where(author_id: another_author.id).size
- assert author.posts.where(author_id: another_author.id).empty?
+ assert_empty author.posts.where(author_id: another_author.id)
end
def test_count_with_distinct
@@ -969,6 +963,18 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 11, posts.distinct(false).select(:comments_count).count
end
+ def test_size_with_distinct
+ posts = Post.distinct.select(:author_id, :comments_count)
+ assert_queries(1) { assert_equal 8, posts.size }
+ assert_queries(1) { assert_equal 8, posts.load.size }
+ end
+
+ def test_size_with_eager_loading_and_custom_order
+ posts = Post.includes(:comments).order("comments.id")
+ assert_queries(1) { assert_equal 11, posts.size }
+ assert_queries(1) { assert_equal 11, posts.load.size }
+ end
+
def test_update_all_with_scope
tag = Tag.first
Post.tagged_with(tag.id).update_all title: "rofl"
@@ -1000,7 +1006,7 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.all
assert_queries(1) { assert_equal 11, posts.size }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
best_posts = posts.where(comments_count: 0)
best_posts.load # force load
@@ -1011,7 +1017,7 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.limit(10)
assert_queries(1) { assert_equal 10, posts.size }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
best_posts = posts.where(comments_count: 0)
best_posts.load # force load
@@ -1022,7 +1028,7 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.limit(0)
assert_no_queries { assert_equal 0, posts.size }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
posts.load # force load
assert_no_queries { assert_equal 0, posts.size }
@@ -1032,7 +1038,7 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.limit(0)
assert_no_queries { assert_equal true, posts.empty? }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
end
def test_count_complex_chained_relations
@@ -1046,11 +1052,11 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.all
assert_queries(1) { assert_equal false, posts.empty? }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
no_posts = posts.where(title: "")
assert_queries(1) { assert_equal true, no_posts.empty? }
- assert ! no_posts.loaded?
+ assert_not_predicate no_posts, :loaded?
best_posts = posts.where(comments_count: 0)
best_posts.load # force load
@@ -1061,11 +1067,11 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.select("comments_count").where("id is not null").group("author_id").where("comments_count > 0")
assert_queries(1) { assert_equal false, posts.empty? }
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
no_posts = posts.where(title: "")
assert_queries(1) { assert_equal true, no_posts.empty? }
- assert ! no_posts.loaded?
+ assert_not_predicate no_posts, :loaded?
end
def test_any
@@ -1081,13 +1087,13 @@ class RelationTest < ActiveRecord::TestCase
assert_queries(3) do
assert posts.any? # Uses COUNT()
- assert ! posts.where(id: nil).any?
+ assert_not_predicate posts.where(id: nil), :any?
assert posts.any? { |p| p.id > 0 }
assert ! posts.any? { |p| p.id <= 0 }
end
- assert posts.loaded?
+ assert_predicate posts, :loaded?
end
def test_many
@@ -1099,14 +1105,14 @@ class RelationTest < ActiveRecord::TestCase
assert ! posts.many? { |p| p.id < 2 }
end
- assert posts.loaded?
+ assert_predicate posts, :loaded?
end
def test_many_with_limits
posts = Post.all
- assert posts.many?
- assert ! posts.limit(1).many?
+ assert_predicate posts, :many?
+ assert_not_predicate posts.limit(1), :many?
end
def test_none?
@@ -1115,14 +1121,14 @@ class RelationTest < ActiveRecord::TestCase
assert ! posts.none? # Uses COUNT()
end
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
assert_queries(1) do
assert posts.none? { |p| p.id < 0 }
assert ! posts.none? { |p| p.id == 1 }
end
- assert posts.loaded?
+ assert_predicate posts, :loaded?
end
def test_one
@@ -1131,14 +1137,14 @@ class RelationTest < ActiveRecord::TestCase
assert ! posts.one? # Uses COUNT()
end
- assert ! posts.loaded?
+ assert_not_predicate posts, :loaded?
assert_queries(1) do
assert ! posts.one? { |p| p.id < 3 }
assert posts.one? { |p| p.id == 1 }
end
- assert posts.loaded?
+ assert_predicate posts, :loaded?
end
def test_to_a_should_dup_target
@@ -1171,10 +1177,10 @@ class RelationTest < ActiveRecord::TestCase
sparrow = birds.create
assert_kind_of Bird, sparrow
- assert !sparrow.persisted?
+ assert_not_predicate sparrow, :persisted?
hen = birds.where(name: "hen").create
- assert hen.persisted?
+ assert_predicate hen, :persisted?
assert_equal "hen", hen.name
end
@@ -1185,34 +1191,43 @@ class RelationTest < ActiveRecord::TestCase
hen = birds.where(name: "hen").create!
assert_kind_of Bird, hen
- assert hen.persisted?
+ assert_predicate hen, :persisted?
assert_equal "hen", hen.name
end
+ def test_create_with_polymorphic_association
+ author = authors(:david)
+ post = posts(:welcome)
+ comment = Comment.where(post: post, author: author).create!(body: "hello")
+
+ assert_equal author, comment.author
+ assert_equal post, comment.post
+ end
+
def test_first_or_create
parrot = Bird.where(color: "green").first_or_create(name: "parrot")
assert_kind_of Bird, parrot
- assert parrot.persisted?
+ assert_predicate parrot, :persisted?
assert_equal "parrot", parrot.name
assert_equal "green", parrot.color
same_parrot = Bird.where(color: "green").first_or_create(name: "parakeet")
assert_kind_of Bird, same_parrot
- assert same_parrot.persisted?
+ assert_predicate same_parrot, :persisted?
assert_equal parrot, same_parrot
end
def test_first_or_create_with_no_parameters
parrot = Bird.where(color: "green").first_or_create
assert_kind_of Bird, parrot
- assert !parrot.persisted?
+ assert_not_predicate parrot, :persisted?
assert_equal "green", parrot.color
end
def test_first_or_create_with_block
parrot = Bird.where(color: "green").first_or_create { |bird| bird.name = "parrot" }
assert_kind_of Bird, parrot
- assert parrot.persisted?
+ assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
assert_equal "parrot", parrot.name
@@ -1233,13 +1248,13 @@ class RelationTest < ActiveRecord::TestCase
def test_first_or_create_bang_with_valid_options
parrot = Bird.where(color: "green").first_or_create!(name: "parrot")
assert_kind_of Bird, parrot
- assert parrot.persisted?
+ assert_predicate parrot, :persisted?
assert_equal "parrot", parrot.name
assert_equal "green", parrot.color
same_parrot = Bird.where(color: "green").first_or_create!(name: "parakeet")
assert_kind_of Bird, same_parrot
- assert same_parrot.persisted?
+ assert_predicate same_parrot, :persisted?
assert_equal parrot, same_parrot
end
@@ -1254,7 +1269,7 @@ class RelationTest < ActiveRecord::TestCase
def test_first_or_create_bang_with_valid_block
parrot = Bird.where(color: "green").first_or_create! { |bird| bird.name = "parrot" }
assert_kind_of Bird, parrot
- assert parrot.persisted?
+ assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
assert_equal "parrot", parrot.name
@@ -1285,9 +1300,9 @@ class RelationTest < ActiveRecord::TestCase
def test_first_or_initialize
parrot = Bird.where(color: "green").first_or_initialize(name: "parrot")
assert_kind_of Bird, parrot
- assert !parrot.persisted?
- assert parrot.valid?
- assert parrot.new_record?
+ assert_not_predicate parrot, :persisted?
+ assert_predicate parrot, :valid?
+ assert_predicate parrot, :new_record?
assert_equal "parrot", parrot.name
assert_equal "green", parrot.color
end
@@ -1295,18 +1310,18 @@ class RelationTest < ActiveRecord::TestCase
def test_first_or_initialize_with_no_parameters
parrot = Bird.where(color: "green").first_or_initialize
assert_kind_of Bird, parrot
- assert !parrot.persisted?
- assert !parrot.valid?
- assert parrot.new_record?
+ assert_not_predicate parrot, :persisted?
+ assert_not_predicate parrot, :valid?
+ assert_predicate parrot, :new_record?
assert_equal "green", parrot.color
end
def test_first_or_initialize_with_block
parrot = Bird.where(color: "green").first_or_initialize { |bird| bird.name = "parrot" }
assert_kind_of Bird, parrot
- assert !parrot.persisted?
- assert parrot.valid?
- assert parrot.new_record?
+ assert_not_predicate parrot, :persisted?
+ assert_predicate parrot, :valid?
+ assert_predicate parrot, :new_record?
assert_equal "green", parrot.color
assert_equal "parrot", parrot.name
end
@@ -1315,7 +1330,7 @@ class RelationTest < ActiveRecord::TestCase
assert_nil Bird.find_by(name: "bob")
bird = Bird.find_or_create_by(name: "bob")
- assert bird.persisted?
+ assert_predicate bird, :persisted?
assert_equal bird, Bird.find_or_create_by(name: "bob")
end
@@ -1324,7 +1339,7 @@ class RelationTest < ActiveRecord::TestCase
assert_nil Bird.find_by(name: "bob")
bird = Bird.create_with(color: "green").find_or_create_by(name: "bob")
- assert bird.persisted?
+ assert_predicate bird, :persisted?
assert_equal "green", bird.color
assert_equal bird, Bird.create_with(color: "blue").find_or_create_by(name: "bob")
@@ -1338,7 +1353,7 @@ class RelationTest < ActiveRecord::TestCase
assert_nil Bird.find_by(name: "bob")
bird = Bird.find_or_initialize_by(name: "bob")
- assert bird.new_record?
+ assert_predicate bird, :new_record?
bird.save!
assert_equal bird, Bird.find_or_initialize_by(name: "bob")
@@ -1513,10 +1528,10 @@ class RelationTest < ActiveRecord::TestCase
def test_doesnt_add_having_values_if_options_are_blank
scope = Post.having("")
- assert scope.having_clause.empty?
+ assert_empty scope.having_clause
scope = Post.having([])
- assert scope.having_clause.empty?
+ assert_empty scope.having_clause
end
def test_having_with_binds_for_both_where_and_having
@@ -1542,13 +1557,13 @@ class RelationTest < ActiveRecord::TestCase
def test_references_triggers_eager_loading
scope = Post.includes(:comments)
- assert !scope.eager_loading?
- assert scope.references(:comments).eager_loading?
+ assert_not_predicate scope, :eager_loading?
+ assert_predicate scope.references(:comments), :eager_loading?
end
def test_references_doesnt_trigger_eager_loading_if_reference_not_included
scope = Post.references(:comments)
- assert !scope.eager_loading?
+ assert_not_predicate scope, :eager_loading?
end
def test_automatically_added_where_references
@@ -1579,7 +1594,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body")
assert_equal ["comments"], scope.references_values
- scope = Post.order("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
+ scope = Post.order(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}"))
if current_adapter?(:OracleAdapter)
assert_equal ["COMMENTS"], scope.references_values
else
@@ -1596,7 +1611,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body asc")
assert_equal ["comments"], scope.references_values
- scope = Post.order("foo(comments.body)")
+ scope = Post.order(Arel.sql("foo(comments.body)"))
assert_equal [], scope.references_values
end
@@ -1604,7 +1619,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body")
assert_equal %w(comments), scope.references_values
- scope = Post.reorder("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
+ scope = Post.reorder(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}"))
if current_adapter?(:OracleAdapter)
assert_equal ["COMMENTS"], scope.references_values
else
@@ -1621,7 +1636,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body asc")
assert_equal %w(comments), scope.references_values
- scope = Post.reorder("foo(comments.body)")
+ scope = Post.reorder(Arel.sql("foo(comments.body)"))
assert_equal [], scope.references_values
end
@@ -1656,7 +1671,7 @@ class RelationTest < ActiveRecord::TestCase
# count always trigger the COUNT query.
assert_queries(1) { topics.count }
- assert topics.loaded?
+ assert_predicate topics, :loaded?
end
test "find_by with hash conditions returns the first matching record" do
@@ -1831,7 +1846,7 @@ class RelationTest < ActiveRecord::TestCase
test "delegations do not leak to other classes" do
Topic.all.by_lifo
assert Topic.all.class.method_defined?(:by_lifo)
- assert !Post.all.respond_to?(:by_lifo)
+ assert_not_respond_to Post.all, :by_lifo
end
def test_unscope_with_subquery
@@ -1856,7 +1871,7 @@ class RelationTest < ActiveRecord::TestCase
def test_locked_should_not_build_arel
posts = Post.locked
- assert posts.locked?
+ assert_predicate posts, :locked?
assert_nothing_raised { posts.lock!(false) }
end
@@ -1925,6 +1940,10 @@ class RelationTest < ActiveRecord::TestCase
table_metadata = ActiveRecord::TableMetadata.new(Post, table_alias)
predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
- ActiveRecord::Relation.create(Post, table_alias, predicate_builder)
+ ActiveRecord::Relation.create(
+ Post,
+ table: table_alias,
+ predicate_builder: predicate_builder
+ )
end
end
diff --git a/activerecord/test/cases/reserved_word_test.rb b/activerecord/test/cases/reserved_word_test.rb
index 0214dbec17..e32605fd11 100644
--- a/activerecord/test/cases/reserved_word_test.rb
+++ b/activerecord/test/cases/reserved_word_test.rb
@@ -39,7 +39,7 @@ class ReservedWordTest < ActiveRecord::TestCase
t.string :order
t.belongs_to :select
end
- @connection.create_table :values, force: true do |t|
+ @connection.create_table :values, primary_key: :as, force: true do |t|
t.belongs_to :group
end
end
@@ -88,6 +88,13 @@ class ReservedWordTest < ActiveRecord::TestCase
assert_equal x, Group.find(x.id)
end
+ def test_delete_all_with_subselect
+ create_test_fixtures :values
+ assert_equal 1, Values.order(:as).limit(1).offset(1).delete_all
+ assert_raise(ActiveRecord::RecordNotFound) { Values.find(2) }
+ assert Values.find(1)
+ end
+
def test_has_one_associations
create_test_fixtures :group, :values
v = Group.find(1).values
@@ -109,7 +116,7 @@ class ReservedWordTest < ActiveRecord::TestCase
end
def test_activerecord_introspection
- assert Group.table_exists?
+ assert_predicate Group, :table_exists?
assert_equal ["id", "order", "select_id"], Group.columns.map(&:name).sort
end
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 85a555fe35..778cf86ac3 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -4,6 +4,7 @@ require "cases/helper"
require "models/binary"
require "models/author"
require "models/post"
+require "models/customer"
class SanitizeTest < ActiveRecord::TestCase
def setup
@@ -11,30 +12,30 @@ class SanitizeTest < ActiveRecord::TestCase
def test_sanitize_sql_array_handles_string_interpolation
quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi")
- assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi"])
- assert_equal "name='#{quoted_bambi}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi".mb_chars])
+ assert_equal "name='#{quoted_bambi}'", Binary.sanitize_sql_array(["name='%s'", "Bambi"])
+ assert_equal "name='#{quoted_bambi}'", Binary.sanitize_sql_array(["name='%s'", "Bambi".mb_chars])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper")
- assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper"])
- assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.send(:sanitize_sql_array, ["name='%s'", "Bambi\nand\nThumper".mb_chars])
+ assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.sanitize_sql_array(["name='%s'", "Bambi\nand\nThumper"])
+ assert_equal "name='#{quoted_bambi_and_thumper}'", Binary.sanitize_sql_array(["name='%s'", "Bambi\nand\nThumper".mb_chars])
end
def test_sanitize_sql_array_handles_bind_variables
quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
- assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi"])
- assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi".mb_chars])
+ assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=?", "Bambi"])
+ assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=?", "Bambi".mb_chars])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
- assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper"])
- assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".mb_chars])
+ assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=?", "Bambi\nand\nThumper"])
+ assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=?", "Bambi\nand\nThumper".mb_chars])
end
def test_sanitize_sql_array_handles_named_bind_variables
quoted_bambi = ActiveRecord::Base.connection.quote("Bambi")
- assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=:name", name: "Bambi"])
- assert_equal "name=#{quoted_bambi} AND id=1", Binary.send(:sanitize_sql_array, ["name=:name AND id=:id", name: "Bambi", id: 1])
+ assert_equal "name=#{quoted_bambi}", Binary.sanitize_sql_array(["name=:name", name: "Bambi"])
+ assert_equal "name=#{quoted_bambi} AND id=1", Binary.sanitize_sql_array(["name=:name AND id=:id", name: "Bambi", id: 1])
quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper")
- assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=:name", name: "Bambi\nand\nThumper"])
- assert_equal "name=#{quoted_bambi_and_thumper} AND name2=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=:name AND name2=:name", name: "Bambi\nand\nThumper"])
+ assert_equal "name=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=:name", name: "Bambi\nand\nThumper"])
+ assert_equal "name=#{quoted_bambi_and_thumper} AND name2=#{quoted_bambi_and_thumper}", Binary.sanitize_sql_array(["name=:name AND name2=:name", name: "Bambi\nand\nThumper"])
end
def test_sanitize_sql_array_handles_relations
@@ -43,42 +44,50 @@ class SanitizeTest < ActiveRecord::TestCase
sub_query_pattern = /\(\bselect\b.*?\bwhere\b.*?\)/i
- select_author_sql = Post.send(:sanitize_sql_array, ["id in (?)", david_posts])
+ select_author_sql = Post.sanitize_sql_array(["id in (?)", david_posts])
assert_match(sub_query_pattern, select_author_sql, "should sanitize `Relation` as subquery for bind variables")
- select_author_sql = Post.send(:sanitize_sql_array, ["id in (:post_ids)", post_ids: david_posts])
+ select_author_sql = Post.sanitize_sql_array(["id in (:post_ids)", post_ids: david_posts])
assert_match(sub_query_pattern, select_author_sql, "should sanitize `Relation` as subquery for named bind variables")
end
def test_sanitize_sql_array_handles_empty_statement
- select_author_sql = Post.send(:sanitize_sql_array, [""])
+ select_author_sql = Post.sanitize_sql_array([""])
assert_equal("", select_author_sql)
end
def test_sanitize_sql_like
- assert_equal '100\%', Binary.send(:sanitize_sql_like, "100%")
- assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, "snake_cased_string")
- assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint')
- assert_equal "normal string 42", Binary.send(:sanitize_sql_like, "normal string 42")
+ assert_equal '100\%', Binary.sanitize_sql_like("100%")
+ assert_equal 'snake\_cased\_string', Binary.sanitize_sql_like("snake_cased_string")
+ assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.sanitize_sql_like('C:\\Programs\\MsPaint')
+ assert_equal "normal string 42", Binary.sanitize_sql_like("normal string 42")
end
def test_sanitize_sql_like_with_custom_escape_character
- assert_equal "100!%", Binary.send(:sanitize_sql_like, "100%", "!")
- assert_equal "snake!_cased!_string", Binary.send(:sanitize_sql_like, "snake_cased_string", "!")
- assert_equal "great!!", Binary.send(:sanitize_sql_like, "great!", "!")
- assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', "!")
- assert_equal "normal string 42", Binary.send(:sanitize_sql_like, "normal string 42", "!")
+ assert_equal "100!%", Binary.sanitize_sql_like("100%", "!")
+ assert_equal "snake!_cased!_string", Binary.sanitize_sql_like("snake_cased_string", "!")
+ assert_equal "great!!", Binary.sanitize_sql_like("great!", "!")
+ assert_equal 'C:\\Programs\\MsPaint', Binary.sanitize_sql_like('C:\\Programs\\MsPaint', "!")
+ assert_equal "normal string 42", Binary.sanitize_sql_like("normal string 42", "!")
end
def test_sanitize_sql_like_example_use_case
searchable_post = Class.new(Post) do
- def self.search(term)
+ def self.search_as_method(term)
where("title LIKE ?", sanitize_sql_like(term, "!"))
end
+
+ scope :search_as_scope, -> (term) {
+ where("title LIKE ?", sanitize_sql_like(term, "!"))
+ }
+ end
+
+ assert_sql(/LIKE '20!% !_reduction!_!!'/) do
+ searchable_post.search_as_method("20% _reduction_!").to_a
end
assert_sql(/LIKE '20!% !_reduction!_!!'/) do
- searchable_post.search("20% _reduction_!").to_a
+ searchable_post.search_as_scope("20% _reduction_!").to_a
end
end
@@ -159,6 +168,12 @@ class SanitizeTest < ActiveRecord::TestCase
assert_equal "#{ActiveRecord::Base.connection.quote('10')}::integer '2009-01-01'::date", l.call
end
+ def test_deprecated_expand_hash_conditions_for_aggregates
+ assert_deprecated do
+ assert_equal({ "balance" => 50 }, Customer.send(:expand_hash_conditions_for_aggregates, balance: Money.new(50)))
+ end
+ end
+
private
def bind(statement, *vars)
if vars.first.is_a?(Hash)
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index ac5092f1c1..a612ce9bb2 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -177,14 +177,14 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dumps_index_columns_in_right_order
index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_index/).first.strip
- if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter)
- assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }', index_definition
- elsif current_adapter?(:Mysql2Adapter)
+ if current_adapter?(:Mysql2Adapter)
if ActiveRecord::Base.connection.supports_index_sort_order?
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, order: { rating: :desc }', index_definition
else
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }', index_definition
end
+ elsif ActiveRecord::Base.connection.supports_index_sort_order?
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }', index_definition
else
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
@@ -199,6 +199,24 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
+ def test_schema_dumps_index_sort_order
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*_name_and_rating/).first.strip
+ if ActiveRecord::Base.connection.supports_index_sort_order?
+ assert_equal 't.index ["name", "rating"], name: "index_companies_on_name_and_rating", order: :desc', index_definition
+ else
+ assert_equal 't.index ["name", "rating"], name: "index_companies_on_name_and_rating"', index_definition
+ end
+ end
+
+ def test_schema_dumps_index_length
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*_name_and_description/).first.strip
+ if current_adapter?(:Mysql2Adapter)
+ assert_equal 't.index ["name", "description"], name: "index_companies_on_name_and_description", length: 10', index_definition
+ else
+ assert_equal 't.index ["name", "description"], name: "index_companies_on_name_and_description"', index_definition
+ end
+ end
+
def test_schema_dump_should_honor_nonstandard_primary_keys
output = standard_dump
match = output.match(%r{create_table "movies"(.*)do})
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 716ca29eda..0804de1fb3 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -120,49 +120,49 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_unscope_with_where_attributes
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(name: "David").unscope(where: :name).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
expected_2 = Developer.order("salary DESC").collect(&:name)
received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({ where: :name }, :select).collect(&:name)
- assert_equal expected_2, received_2
+ assert_equal expected_2.sort, received_2.sort
expected_3 = Developer.order("salary DESC").collect(&:name)
received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect(&:name)
- assert_equal expected_3, received_3
+ assert_equal expected_3.sort, received_3.sort
expected_4 = Developer.order("salary DESC").collect(&:name)
received_4 = DeveloperOrderedBySalary.where.not("name" => "Jamis").unscope(where: :name).collect(&:name)
- assert_equal expected_4, received_4
+ assert_equal expected_4.sort, received_4.sort
expected_5 = Developer.order("salary DESC").collect(&:name)
received_5 = DeveloperOrderedBySalary.where.not("name" => ["Jamis", "David"]).unscope(where: :name).collect(&:name)
- assert_equal expected_5, received_5
+ assert_equal expected_5.sort, received_5.sort
expected_6 = Developer.order("salary DESC").collect(&:name)
received_6 = DeveloperOrderedBySalary.where(Developer.arel_table["name"].eq("David")).unscope(where: :name).collect(&:name)
- assert_equal expected_6, received_6
+ assert_equal expected_6.sort, received_6.sort
expected_7 = Developer.order("salary DESC").collect(&:name)
received_7 = DeveloperOrderedBySalary.where(Developer.arel_table[:name].eq("David")).unscope(where: :name).collect(&:name)
- assert_equal expected_7, received_7
+ assert_equal expected_7.sort, received_7.sort
end
def test_unscope_comparison_where_clauses
# unscoped for WHERE (`developers`.`id` <= 2)
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(id: -Float::INFINITY..2).unscope(where: :id).collect { |dev| dev.name }
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
# unscoped for WHERE (`developers`.`id` < 2)
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(id: -Float::INFINITY...2).unscope(where: :id).collect { |dev| dev.name }
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_unscope_multiple_where_clauses
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.where(name: "Jamis").where(id: 1).unscope(where: [:name, :id]).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_unscope_string_where_clauses_involved
@@ -172,23 +172,23 @@ class DefaultScopingTest < ActiveRecord::TestCase
dev_ordered_relation = DeveloperOrderedBySalary.where(name: "Jamis").where("created_at > ?", 1.year.ago)
received = dev_ordered_relation.unscope(where: [:name]).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_unscope_with_grouping_attributes
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
expected_2 = Developer.order("salary DESC").collect(&:name)
received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect(&:name)
- assert_equal expected_2, received_2
+ assert_equal expected_2.sort, received_2.sort
end
def test_unscope_with_limit_in_query
expected = Developer.order("salary DESC").collect(&:name)
received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect(&:name)
- assert_equal expected, received
+ assert_equal expected.sort, received.sort
end
def test_order_to_unscope_reordering
@@ -224,6 +224,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
+ def test_unscope_left_outer_joins
+ expected = Developer.all.collect(&:name)
+ received = Developer.left_outer_joins(:projects).select(:id).unscope(:left_outer_joins, :select).collect(&:name)
+ assert_equal expected, received
+ end
+
+ def test_unscope_left_joins
+ expected = Developer.all.collect(&:name)
+ received = Developer.left_joins(:projects).select(:id).unscope(:left_joins, :select).collect(&:name)
+ assert_equal expected, received
+ end
+
def test_unscope_includes
expected = Developer.all.collect(&:name)
received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect(&:name)
@@ -290,8 +302,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
def test_unscope_merging
merged = Developer.where(name: "Jamis").merge(Developer.unscope(:where))
- assert merged.where_clause.empty?
- assert !merged.where(name: "Jon").where_clause.empty?
+ assert_empty merged.where_clause
+ assert_not_empty merged.where(name: "Jon").where_clause
end
def test_order_in_default_scope_should_not_prevail
@@ -472,7 +484,7 @@ class DefaultScopingTest < ActiveRecord::TestCase
test "a scope can remove the condition from the default scope" do
scope = DeveloperCalledJamis.david2
assert_equal 1, scope.where_clause.ast.children.length
- assert_equal Developer.where(name: "David"), scope
+ assert_equal Developer.where(name: "David").map(&:id), scope.map(&:id)
end
def test_with_abstract_class_where_clause_should_not_be_duplicated
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index b0431a4e34..ea71a5ce28 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -13,7 +13,7 @@ class NamedScopingTest < ActiveRecord::TestCase
fixtures :posts, :authors, :topics, :comments, :author_addresses
def test_implements_enumerable
- assert !Topic.all.empty?
+ assert_not_empty Topic.all
assert_equal Topic.all.to_a, Topic.base
assert_equal Topic.all.to_a, Topic.base.to_a
@@ -40,7 +40,7 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_delegates_finds_and_calculations_to_the_base_class
- assert !Topic.all.empty?
+ assert_not_empty Topic.all
assert_equal Topic.all.to_a, Topic.base.to_a
assert_equal Topic.first, Topic.base.first
@@ -65,13 +65,13 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy
- assert Topic.approved.respond_to?(:limit)
- assert Topic.approved.respond_to?(:count)
- assert Topic.approved.respond_to?(:length)
+ assert_respond_to Topic.approved, :limit
+ assert_respond_to Topic.approved, :count
+ assert_respond_to Topic.approved, :length
end
def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified
- assert !Topic.all.merge!(where: { approved: true }).to_a.empty?
+ assert_not_empty Topic.all.merge!(where: { approved: true }).to_a
assert_equal Topic.all.merge!(where: { approved: true }).to_a, Topic.approved
assert_equal Topic.where(approved: true).count, Topic.approved.count
@@ -87,7 +87,7 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal((approved = Topic.all.merge!(where: { approved: true }).to_a), Topic.approved)
assert_equal((replied = Topic.all.merge!(where: "replies_count > 0").to_a), Topic.replied)
assert !(approved == replied)
- assert !(approved & replied).empty?
+ assert_not_empty (approved & replied)
assert_equal approved & replied, Topic.approved.replied
end
@@ -115,7 +115,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_has_many_associations_have_access_to_scopes
assert_not_equal Post.containing_the_letter_a, authors(:david).posts
- assert !Post.containing_the_letter_a.empty?
+ assert_not_empty Post.containing_the_letter_a
expected = authors(:david).posts & Post.containing_the_letter_a
assert_equal expected.sort_by(&:id), authors(:david).posts.containing_the_letter_a.sort_by(&:id)
@@ -128,15 +128,15 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_has_many_through_associations_have_access_to_scopes
assert_not_equal Comment.containing_the_letter_e, authors(:david).comments
- assert !Comment.containing_the_letter_e.empty?
+ assert_not_empty Comment.containing_the_letter_e
expected = authors(:david).comments & Comment.containing_the_letter_e
assert_equal expected.sort_by(&:id), authors(:david).comments.containing_the_letter_e.sort_by(&:id)
end
def test_scopes_honor_current_scopes_from_when_defined
- assert !Post.ranked_by_comments.limit_by(5).empty?
- assert !authors(:david).posts.ranked_by_comments.limit_by(5).empty?
+ assert_not_empty Post.ranked_by_comments.limit_by(5)
+ assert_not_empty authors(:david).posts.ranked_by_comments.limit_by(5)
assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5)
assert_not_equal Post.top(5), authors(:david).posts.top(5)
# Oracle sometimes sorts differently if WHERE condition is changed
@@ -151,15 +151,31 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal "The scope body needs to be callable.", e.message
end
+ def test_scopes_name_is_relation_method
+ conflicts = [
+ :records,
+ :to_ary,
+ :to_sql,
+ :explain
+ ]
+
+ conflicts.each do |name|
+ e = assert_raises ArgumentError do
+ Class.new(Post).class_eval { scope name, -> { where(approved: true) } }
+ end
+ assert_match(/You tried to define a scope named \"#{name}\" on the model/, e.message)
+ end
+ end
+
def test_active_records_have_scope_named__all__
- assert !Topic.all.empty?
+ assert_not_empty Topic.all
assert_equal Topic.all.to_a, Topic.base
end
def test_active_records_have_scope_named__scoped__
scope = Topic.where("content LIKE '%Have%'")
- assert !scope.empty?
+ assert_not_empty scope
assert_equal scope, Topic.all.merge!(where: "content LIKE '%Have%'")
end
@@ -212,9 +228,9 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_model_class_should_respond_to_any
- assert Topic.any?
+ assert_predicate Topic, :any?
Topic.delete_all
- assert !Topic.any?
+ assert_not_predicate Topic, :any?
end
def test_many_should_not_load_results
@@ -243,22 +259,22 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_many_should_return_false_if_none_or_one
topics = Topic.base.where(id: 0)
- assert !topics.many?
+ assert_not_predicate topics, :many?
topics = Topic.base.where(id: 1)
- assert !topics.many?
+ assert_not_predicate topics, :many?
end
def test_many_should_return_true_if_more_than_one
- assert Topic.base.many?
+ assert_predicate Topic.base, :many?
end
def test_model_class_should_respond_to_many
Topic.delete_all
- assert !Topic.many?
+ assert_not_predicate Topic, :many?
Topic.create!
- assert !Topic.many?
+ assert_not_predicate Topic, :many?
Topic.create!
- assert Topic.many?
+ assert_predicate Topic, :many?
end
def test_should_build_on_top_of_scope
@@ -407,16 +423,16 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_chaining_applies_last_conditions_when_creating
post = Topic.rejected.new
- assert !post.approved?
+ assert_not_predicate post, :approved?
post = Topic.rejected.approved.new
- assert post.approved?
+ assert_predicate post, :approved?
post = Topic.approved.rejected.new
- assert !post.approved?
+ assert_not_predicate post, :approved?
post = Topic.approved.rejected.approved.new
- assert post.approved?
+ assert_predicate post, :approved?
end
def test_chaining_combines_conditions_when_searching
@@ -482,7 +498,7 @@ class NamedScopingTest < ActiveRecord::TestCase
def test_index_on_scope
approved = Topic.approved.order("id ASC")
assert_equal topics(:second), approved[0]
- assert approved.loaded?
+ assert_predicate approved, :loaded?
end
def test_nested_scopes_queries_size
@@ -562,16 +578,16 @@ class NamedScopingTest < ActiveRecord::TestCase
end
def test_model_class_should_respond_to_none
- assert !Topic.none?
+ assert_not_predicate Topic, :none?
Topic.delete_all
- assert Topic.none?
+ assert_predicate Topic, :none?
end
def test_model_class_should_respond_to_one
- assert !Topic.one?
+ assert_not_predicate Topic, :one?
Topic.delete_all
- assert !Topic.one?
+ assert_not_predicate Topic, :one?
Topic.create!
- assert Topic.one?
+ assert_predicate Topic, :one?
end
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 116f8e83aa..5c86bc892d 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -213,21 +213,21 @@ class RelationScopingTest < ActiveRecord::TestCase
def test_current_scope_does_not_pollute_sibling_subclasses
Comment.none.scoping do
- assert_not SpecialComment.all.any?
- assert_not VerySpecialComment.all.any?
- assert_not SubSpecialComment.all.any?
+ assert_not_predicate SpecialComment.all, :any?
+ assert_not_predicate VerySpecialComment.all, :any?
+ assert_not_predicate SubSpecialComment.all, :any?
end
SpecialComment.none.scoping do
- assert Comment.all.any?
- assert VerySpecialComment.all.any?
- assert_not SubSpecialComment.all.any?
+ assert_predicate Comment.all, :any?
+ assert_predicate VerySpecialComment.all, :any?
+ assert_not_predicate SubSpecialComment.all, :any?
end
SubSpecialComment.none.scoping do
- assert Comment.all.any?
- assert VerySpecialComment.all.any?
- assert SpecialComment.all.any?
+ assert_predicate Comment.all, :any?
+ assert_predicate VerySpecialComment.all, :any?
+ assert_predicate SpecialComment.all, :any?
end
end
@@ -334,7 +334,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
def test_nested_exclusive_scope_for_create
comment = Comment.create_with(body: "Hey guys, nested scopes are broken. Please fix!").scoping do
Comment.unscoped.create_with(post_id: 1).scoping do
- assert Comment.new.body.blank?
+ assert_predicate Comment.new.body, :blank?
Comment.create body: "Hey guys"
end
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index 32dafbd458..7de5429cbb 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -279,7 +279,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic = Topic.new(content: nil)
- assert_not topic.content_changed?
+ assert_not_predicate topic, :content_changed?
end
def test_classes_without_no_arg_constructors_are_not_supported
@@ -349,7 +349,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase
topic = model.create!(foo: "bar")
topic.foo
- refute topic.changed?
+ assert_not_predicate topic, :changed?
end
def test_serialized_attribute_works_under_concurrent_initial_access
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index 1f715e41a6..ad6cd198e2 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -12,7 +12,6 @@ module ActiveRecord
@connection = ActiveRecord::Base.connection
end
- #Cache v 1.1 tests
def test_statement_cache
Book.create(name: "my book")
Book.create(name: "my other book")
@@ -51,8 +50,6 @@ module ActiveRecord
assert_equal("my other book", b.name)
end
- #End
-
def test_statement_cache_with_simple_statement
cache = ActiveRecord::StatementCache.create(Book.connection) do |params|
Book.where(name: "my book").where("author_id > 3")
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index ebf4016960..a30d13632a 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -45,7 +45,7 @@ class StoreTest < ActiveRecord::TestCase
test "updating the store will mark it as changed" do
@john.color = "red"
- assert @john.settings_changed?
+ assert_predicate @john, :settings_changed?
end
test "updating the store populates the changed array correctly" do
@@ -56,7 +56,7 @@ class StoreTest < ActiveRecord::TestCase
test "updating the store won't mark it as changed if an attribute isn't changed" do
@john.color = @john.color
- assert !@john.settings_changed?
+ assert_not_predicate @john, :settings_changed?
end
test "object initialization with not nullable column" do
@@ -137,7 +137,7 @@ class StoreTest < ActiveRecord::TestCase
test "updating the store will mark it as changed encoded with JSON" do
@john.height = "short"
- assert @john.json_data_changed?
+ assert_predicate @john, :json_data_changed?
end
test "object initialization with not nullable column encoded with JSON" do
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index 5a094ead42..21226352ff 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -28,15 +28,32 @@ module ActiveRecord
class DatabaseTasksUtilsTask < ActiveRecord::TestCase
def test_raises_an_error_when_called_with_protected_environment
- ActiveRecord::Migrator.stubs(:current_version).returns(1)
+ ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
- protected_environments = ActiveRecord::Base.protected_environments.dup
- current_env = ActiveRecord::Migrator.current_environment
+ protected_environments = ActiveRecord::Base.protected_environments
+ current_env = ActiveRecord::Base.connection.migration_context.current_environment
assert_not_includes protected_environments, current_env
# Assert no error
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
- ActiveRecord::Base.protected_environments << current_env
+ ActiveRecord::Base.protected_environments = [current_env]
+ assert_raise(ActiveRecord::ProtectedEnvironmentError) do
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+ end
+ ensure
+ ActiveRecord::Base.protected_environments = protected_environments
+ end
+
+ def test_raises_an_error_when_called_with_protected_environment_which_name_is_a_symbol
+ ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
+
+ protected_environments = ActiveRecord::Base.protected_environments
+ current_env = ActiveRecord::Base.connection.migration_context.current_environment
+ assert_not_includes protected_environments, current_env
+ # Assert no error
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
+
+ ActiveRecord::Base.protected_environments = [current_env.to_sym]
assert_raise(ActiveRecord::ProtectedEnvironmentError) do
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
end
@@ -46,7 +63,7 @@ module ActiveRecord
def test_raises_an_error_if_no_migrations_have_been_made
ActiveRecord::InternalMetadata.stubs(:table_exists?).returns(false)
- ActiveRecord::Migrator.stubs(:current_version).returns(1)
+ ActiveRecord::MigrationContext.any_instance.stubs(:current_version).returns(1)
assert_raise(ActiveRecord::NoEnvironmentInSchemaError) do
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
@@ -93,6 +110,7 @@ module ActiveRecord
ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, path)
assert File.file?(path)
ensure
+ ActiveRecord::Base.clear_cache!
FileUtils.rm_rf(path)
end
end
@@ -329,50 +347,92 @@ module ActiveRecord
end
end
- class DatabaseTasksMigrateTest < ActiveRecord::TestCase
- self.use_transactional_tests = false
+ if current_adapter?(:SQLite3Adapter) && !in_memory_db?
+ class DatabaseTasksMigrateTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ # Use a memory db here to avoid having to rollback at the end
+ setup do
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ file = ActiveRecord::Base.connection.raw_connection.filename
+ @conn = ActiveRecord::Base.establish_connection adapter: "sqlite3",
+ database: ":memory:", migrations_paths: migrations_path
+ source_db = SQLite3::Database.new file
+ dest_db = ActiveRecord::Base.connection.raw_connection
+ backup = SQLite3::Backup.new(dest_db, "main", source_db, "main")
+ backup.step(-1)
+ backup.finish
+ end
- def setup
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths = "custom/path"
- end
+ teardown do
+ @conn.release_connection if @conn
+ ActiveRecord::Base.establish_connection :arunit
+ end
- def teardown
- ActiveRecord::Tasks::DatabaseTasks.migrations_paths = nil
- end
+ def test_migrate_set_and_unset_verbose_and_version_env_vars
+ verbose, version = ENV["VERBOSE"], ENV["VERSION"]
+ ENV["VERSION"] = "2"
+ ENV["VERBOSE"] = "false"
- def test_migrate_receives_correct_env_vars
- verbose, version = ENV["VERBOSE"], ENV["VERSION"]
+ # run down migration because it was already run on copied db
+ assert_empty capture_migration_output
- ENV["VERBOSE"] = "false"
- ENV["VERSION"] = "4"
- ActiveRecord::Migrator.expects(:migrate).with("custom/path", 4)
- ActiveRecord::Migration.expects(:verbose=).with(false)
- ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
- ActiveRecord::Tasks::DatabaseTasks.migrate
+ ENV.delete("VERSION")
+ ENV.delete("VERBOSE")
- ENV.delete("VERBOSE")
- ENV.delete("VERSION")
- ActiveRecord::Migrator.expects(:migrate).with("custom/path", nil)
- ActiveRecord::Migration.expects(:verbose=).with(true)
- ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
- ActiveRecord::Tasks::DatabaseTasks.migrate
+ # re-run up migration
+ assert_includes capture_migration_output, "migrating"
+ ensure
+ ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+ end
- ENV["VERBOSE"] = ""
- ENV["VERSION"] = ""
- ActiveRecord::Migrator.expects(:migrate).with("custom/path", nil)
- ActiveRecord::Migration.expects(:verbose=).with(true)
- ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
- ActiveRecord::Tasks::DatabaseTasks.migrate
+ def test_migrate_set_and_unset_empty_values_for_verbose_and_version_env_vars
+ verbose, version = ENV["VERBOSE"], ENV["VERSION"]
- ENV["VERBOSE"] = "yes"
- ENV["VERSION"] = "0"
- ActiveRecord::Migrator.expects(:migrate).with("custom/path", 0)
- ActiveRecord::Migration.expects(:verbose=).with(true)
- ActiveRecord::Migration.expects(:verbose=).with(ActiveRecord::Migration.verbose)
- ActiveRecord::Tasks::DatabaseTasks.migrate
- ensure
- ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+ ENV["VERSION"] = "2"
+ ENV["VERBOSE"] = "false"
+
+ # run down migration because it was already run on copied db
+ assert_empty capture_migration_output
+
+ ENV["VERBOSE"] = ""
+ ENV["VERSION"] = ""
+
+ # re-run up migration
+ assert_includes capture_migration_output, "migrating"
+ ensure
+ ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+ end
+
+ def test_migrate_set_and_unset_nonsense_values_for_verbose_and_version_env_vars
+ verbose, version = ENV["VERBOSE"], ENV["VERSION"]
+
+ # run down migration because it was already run on copied db
+ ENV["VERSION"] = "2"
+ ENV["VERBOSE"] = "false"
+
+ assert_empty capture_migration_output
+
+ ENV["VERBOSE"] = "yes"
+ ENV["VERSION"] = "2"
+
+ # run no migration because 2 was already run
+ assert_empty capture_migration_output
+ ensure
+ ENV["VERBOSE"], ENV["VERSION"] = verbose, version
+ end
+
+ private
+ def capture_migration_output
+ capture(:stdout) do
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ end
+ end
end
+ end
+
+ class DatabaseTasksMigrateErrorTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
def test_migrate_raise_error_on_invalid_version_format
version = ENV["VERSION"]
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 06a8693a7d..024b5bd8a1 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -77,6 +77,10 @@ module ActiveRecord
model.reset_column_information
model.column_names.include?(column_name.to_s)
end
+
+ def frozen_error_class
+ Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError
+ end
end
class PostgreSQLTestCase < TestCase
@@ -112,7 +116,7 @@ module ActiveRecord
# instead examining the SQL content.
oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im, /^\s*select .* from all_sequences/im]
mysql_ignored = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im]
- postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
+ postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i, /^\s*SELECT\b.*::regtype::oid\b/im]
sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im]
[oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 5d3e2a175c..e95446c0a7 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -90,7 +90,7 @@ class TimestampTest < ActiveRecord::TestCase
@developer.touch(:created_at)
end
- assert !@developer.created_at_changed? , "created_at should not be changed"
+ assert !@developer.created_at_changed?, "created_at should not be changed"
assert !@developer.changed?, "record should not be changed"
assert_not_equal previously_created_at, @developer.created_at
assert_not_equal @previously_updated_at, @developer.updated_at
@@ -139,13 +139,13 @@ class TimestampTest < ActiveRecord::TestCase
def test_touching_a_no_touching_object
Developer.no_touching do
- assert @developer.no_touching?
- assert !@owner.no_touching?
+ assert_predicate @developer, :no_touching?
+ assert_not_predicate @owner, :no_touching?
@developer.touch
end
- assert !@developer.no_touching?
- assert !@owner.no_touching?
+ assert_not_predicate @developer, :no_touching?
+ assert_not_predicate @owner, :no_touching?
assert_equal @previously_updated_at, @developer.updated_at
end
@@ -162,26 +162,26 @@ class TimestampTest < ActiveRecord::TestCase
def test_global_no_touching
ActiveRecord::Base.no_touching do
- assert @developer.no_touching?
- assert @owner.no_touching?
+ assert_predicate @developer, :no_touching?
+ assert_predicate @owner, :no_touching?
@developer.touch
end
- assert !@developer.no_touching?
- assert !@owner.no_touching?
+ assert_not_predicate @developer, :no_touching?
+ assert_not_predicate @owner, :no_touching?
assert_equal @previously_updated_at, @developer.updated_at
end
def test_no_touching_threadsafe
Thread.new do
Developer.no_touching do
- assert @developer.no_touching?
+ assert_predicate @developer, :no_touching?
sleep(1)
end
end
- assert !@developer.no_touching?
+ assert_not_predicate @developer, :no_touching?
end
def test_no_touching_with_callbacks
@@ -237,7 +237,7 @@ class TimestampTest < ActiveRecord::TestCase
pet = Pet.new(owner: klass.new)
pet.save!
- assert pet.owner.new_record?
+ assert_predicate pet.owner, :new_record?
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index 1757031371..925a4609a2 100644
--- a/activerecord/test/cases/touch_later_test.rb
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -13,7 +13,7 @@ class TouchLaterTest < ActiveRecord::TestCase
def test_touch_laster_raise_if_non_persisted
invoice = Invoice.new
Invoice.transaction do
- assert_not invoice.persisted?
+ assert_not_predicate invoice, :persisted?
assert_raises(ActiveRecord::ActiveRecordError) do
invoice.touch_later
end
@@ -23,7 +23,7 @@ class TouchLaterTest < ActiveRecord::TestCase
def test_touch_later_dont_set_dirty_attributes
invoice = Invoice.create!
invoice.touch_later
- assert_not invoice.changed?
+ assert_not_predicate invoice, :changed?
end
def test_touch_later_respects_no_touching_policy
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 1f370a80ee..1c7cec4dad 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -158,13 +158,13 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
def test_only_call_after_commit_on_top_level_transactions
@first.after_commit_block { |r| r.history << :after_commit }
- assert @first.history.empty?
+ assert_empty @first.history
@first.transaction do
@first.transaction(requires_new: true) do
@first.touch
end
- assert @first.history.empty?
+ assert_empty @first.history
end
assert_equal [:after_commit], @first.history
end
@@ -518,7 +518,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
@topic.content = "foo"
@topic.save!
end
- assert @topic.history.empty?
+ assert_empty @topic.history
end
def test_commit_run_transactions_callbacks_with_explicit_enrollment
@@ -538,7 +538,7 @@ class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase
@topic.save!
raise ActiveRecord::Rollback
end
- assert @topic.history.empty?
+ assert_empty @topic.history
end
def test_rollback_run_transactions_callbacks_with_explicit_enrollment
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 5c8ae4d3cb..c70286d52a 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -20,22 +20,22 @@ class TransactionTest < ActiveRecord::TestCase
def test_persisted_in_a_model_with_custom_primary_key_after_failed_save
movie = Movie.create
- assert !movie.persisted?
+ assert_not_predicate movie, :persisted?
end
def test_raise_after_destroy
- assert_not @first.frozen?
+ assert_not_predicate @first, :frozen?
assert_raises(RuntimeError) {
Topic.transaction do
@first.destroy
- assert @first.frozen?
+ assert_predicate @first, :frozen?
raise
end
}
assert @first.reload
- assert_not @first.frozen?
+ assert_not_predicate @first, :frozen?
end
def test_successful
@@ -152,7 +152,7 @@ class TransactionTest < ActiveRecord::TestCase
@first.approved = true
e = assert_raises(RuntimeError) { @first.save }
assert_equal "Make the transaction rollback", e.message
- assert !Topic.find(1).approved?
+ assert_not_predicate Topic.find(1), :approved?
end
def test_rolling_back_in_a_callback_rollbacks_before_save
@@ -186,7 +186,7 @@ class TransactionTest < ActiveRecord::TestCase
author = Author.create! name: "foo"
author.name = nil
assert_not author.save
- assert_not author.new_record?
+ assert_not_predicate author, :new_record?
end
def test_update_should_rollback_on_failure
@@ -417,8 +417,8 @@ class TransactionTest < ActiveRecord::TestCase
end
end
- assert @first.reload.approved?
- assert !@second.reload.approved?
+ assert_predicate @first.reload, :approved?
+ assert_not_predicate @second.reload, :approved?
end if Topic.connection.supports_savepoints?
def test_force_savepoint_on_instance
@@ -438,8 +438,8 @@ class TransactionTest < ActiveRecord::TestCase
end
end
- assert @first.reload.approved?
- assert !@second.reload.approved?
+ assert_predicate @first.reload, :approved?
+ assert_not_predicate @second.reload, :approved?
end if Topic.connection.supports_savepoints?
def test_no_savepoint_in_nested_transaction_without_force
@@ -459,8 +459,8 @@ class TransactionTest < ActiveRecord::TestCase
end
end
- assert !@first.reload.approved?
- assert !@second.reload.approved?
+ assert_not_predicate @first.reload, :approved?
+ assert_not_predicate @second.reload, :approved?
end if Topic.connection.supports_savepoints?
def test_many_savepoints
@@ -516,12 +516,12 @@ class TransactionTest < ActiveRecord::TestCase
@first.approved = false
@first.save!
Topic.connection.rollback_to_savepoint("first")
- assert @first.reload.approved?
+ assert_predicate @first.reload, :approved?
@first.approved = false
@first.save!
Topic.connection.release_savepoint("first")
- assert_not @first.reload.approved?
+ assert_not_predicate @first.reload, :approved?
end
end if Topic.connection.supports_savepoints?
@@ -576,7 +576,7 @@ class TransactionTest < ActiveRecord::TestCase
def test_rollback_when_saving_a_frozen_record
topic = Topic.new(title: "test")
topic.freeze
- e = assert_raise(RuntimeError) { topic.save }
+ e = assert_raise(frozen_error_class) { topic.save }
# Not good enough, but we can't do much
# about it since there is no specific error
# for frozen objects.
@@ -663,8 +663,8 @@ class TransactionTest < ActiveRecord::TestCase
raise ActiveRecord::Rollback
end
- assert_not reply.frozen?
- assert_not topic.frozen?
+ assert_not_predicate reply, :frozen?
+ assert_not_predicate topic, :frozen?
end
def test_restore_id_after_rollback
@@ -819,28 +819,28 @@ class TransactionTest < ActiveRecord::TestCase
connection = Topic.connection
transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
- assert transaction.open?
- assert !transaction.state.rolledback?
- assert !transaction.state.committed?
+ assert_predicate transaction, :open?
+ assert_not_predicate transaction.state, :rolledback?
+ assert_not_predicate transaction.state, :committed?
transaction.rollback
- assert transaction.state.rolledback?
- assert !transaction.state.committed?
+ assert_predicate transaction.state, :rolledback?
+ assert_not_predicate transaction.state, :committed?
end
def test_transactions_state_from_commit
connection = Topic.connection
transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
- assert transaction.open?
- assert !transaction.state.rolledback?
- assert !transaction.state.committed?
+ assert_predicate transaction, :open?
+ assert_not_predicate transaction.state, :rolledback?
+ assert_not_predicate transaction.state, :committed?
transaction.commit
- assert !transaction.state.rolledback?
- assert transaction.state.committed?
+ assert_not_predicate transaction.state, :rolledback?
+ assert_predicate transaction.state, :committed?
end
def test_set_state_method_is_deprecated
@@ -929,7 +929,7 @@ class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase
raise
end
rescue
- assert !@first.reload.approved?
+ assert_not_predicate @first.reload, :approved?
end
end
@@ -950,7 +950,7 @@ class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase
end
end
- assert !@first.reload.approved?
+ assert_not_predicate @first.reload, :approved?
end
end if Topic.connection.supports_savepoints?
diff --git a/activerecord/test/cases/type/string_test.rb b/activerecord/test/cases/type/string_test.rb
index 8c51b30fdd..9e7810a6a5 100644
--- a/activerecord/test/cases/type/string_test.rb
+++ b/activerecord/test/cases/type/string_test.rb
@@ -9,16 +9,16 @@ module ActiveRecord
klass.table_name = "authors"
author = klass.create!(name: "Sean")
- assert_not author.changed?
+ assert_not_predicate author, :changed?
author.name << " Griffin"
- assert author.name_changed?
+ assert_predicate author, :name_changed?
author.save!
author.reload
assert_equal "Sean Griffin", author.name
- assert_not author.changed?
+ assert_not_predicate author, :changed?
end
end
end
diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb
new file mode 100644
index 0000000000..72d4997d0b
--- /dev/null
+++ b/activerecord/test/cases/unsafe_raw_sql_test.rb
@@ -0,0 +1,299 @@
+# frozen_string_literal: true
+
+require "cases/helper"
+require "models/post"
+require "models/comment"
+
+class UnsafeRawSqlTest < ActiveRecord::TestCase
+ fixtures :posts, :comments
+
+ test "order: allows string column name" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("title").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("title").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows symbol column name" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:title).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:title).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows downcase symbol direction" do
+ ids_expected = Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: :asc).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :asc).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows upcase symbol direction" do
+ ids_expected = Post.order(Arel.sql("title") => Arel.sql("ASC")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: :ASC).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: :ASC).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows string direction" do
+ ids_expected = Post.order(Arel.sql("title") => Arel.sql("asc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(title: "asc").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(title: "asc").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows multiple columns" do
+ ids_expected = Post.order(Arel.sql("author_id"), Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:author_id, :title).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, :title).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows mixed" do
+ ids_expected = Post.order(Arel.sql("author_id"), Arel.sql("title") => Arel.sql("asc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(:author_id, title: :asc).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(:author_id, title: :asc).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows table and column name" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("posts.title").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows column name and direction in string" do
+ ids_expected = Post.order(Arel.sql("title desc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("title desc").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("title desc").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: allows table name, column name and direction in string" do
+ ids_expected = Post.order(Arel.sql("title desc")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title desc").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("posts.title desc").pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: disallows invalid column name" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order("len(title) asc").pluck(:id)
+ end
+ end
+ end
+
+ test "order: disallows invalid direction" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ArgumentError) do
+ Post.order(title: :foo).pluck(:id)
+ end
+ end
+ end
+
+ test "order: disallows invalid column with direction" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order("len(title)" => :asc).pluck(:id)
+ end
+ end
+ end
+
+ test "order: always allows Arel" do
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(Arel.sql("length(title)")).pluck(:title) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(Arel.sql("length(title)")).pluck(:title) }
+
+ assert_equal ids_depr, ids_disabled
+ end
+
+ test "order: allows Arel.sql with binds" do
+ ids_expected = Post.order(Arel.sql("REPLACE(title, 'misc', 'zzzz'), id")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order([Arel.sql("REPLACE(title, ?, ?), id"), "misc", "zzzz"]).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order([Arel.sql("REPLACE(title, ?, ?), id"), "misc", "zzzz"]).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: disallows invalid bind statement" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order(["REPLACE(title, ?, ?), id", "misc", "zzzz"]).pluck(:id)
+ end
+ end
+ end
+
+ test "order: disallows invalid Array arguments" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.order(["author_id", "length(title)"]).pluck(:id)
+ end
+ end
+ end
+
+ test "order: allows valid Array arguments" do
+ ids_expected = Post.order(Arel.sql("author_id, length(title)")).pluck(:id)
+
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(["author_id", Arel.sql("length(title)")]).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(["author_id", Arel.sql("length(title)")]).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
+ test "order: logs deprecation warning for unrecognized column" do
+ with_unsafe_raw_sql_deprecated do
+ assert_deprecated(/Dangerous query method/) do
+ Post.order("length(title)")
+ end
+ end
+ end
+
+ test "pluck: allows string column name" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("title") }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("title") }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
+ test "pluck: allows symbol column name" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:title) }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title) }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
+ test "pluck: allows multiple column names" do
+ values_expected = Post.pluck(Arel.sql("title"), Arel.sql("id"))
+
+ values_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:title, :id) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:title, :id) }
+
+ assert_equal values_expected, values_depr
+ assert_equal values_expected, values_disabled
+ end
+
+ test "pluck: allows column names with includes" do
+ values_expected = Post.includes(:comments).pluck(Arel.sql("title"), Arel.sql("id"))
+
+ values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, :id) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, :id) }
+
+ assert_equal values_expected, values_depr
+ assert_equal values_expected, values_disabled
+ end
+
+ test "pluck: allows auto-generated attributes" do
+ values_expected = Post.pluck(Arel.sql("tags_count"))
+
+ values_depr = with_unsafe_raw_sql_deprecated { Post.pluck(:tags_count) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.pluck(:tags_count) }
+
+ assert_equal values_expected, values_depr
+ assert_equal values_expected, values_disabled
+ end
+
+ test "pluck: allows table and column names" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck("posts.title") }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck("posts.title") }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
+ test "pluck: disallows invalid column name" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.pluck("length(title)")
+ end
+ end
+ end
+
+ test "pluck: disallows invalid column name amongst valid names" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.pluck(:title, "length(title)")
+ end
+ end
+ end
+
+ test "pluck: disallows invalid column names with includes" do
+ with_unsafe_raw_sql_disabled do
+ assert_raises(ActiveRecord::UnknownAttributeReference) do
+ Post.includes(:comments).pluck(:title, "length(title)")
+ end
+ end
+ end
+
+ test "pluck: always allows Arel" do
+ values_depr = with_unsafe_raw_sql_deprecated { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) }
+ values_disabled = with_unsafe_raw_sql_disabled { Post.includes(:comments).pluck(:title, Arel.sql("length(title)")) }
+
+ assert_equal values_depr, values_disabled
+ end
+
+ test "pluck: logs deprecation warning" do
+ with_unsafe_raw_sql_deprecated do
+ assert_deprecated(/Dangerous query method/) do
+ Post.includes(:comments).pluck(:title, "length(title)")
+ end
+ end
+ end
+
+ def with_unsafe_raw_sql_disabled(&blk)
+ with_config(:disabled, &blk)
+ end
+
+ def with_unsafe_raw_sql_deprecated(&blk)
+ with_config(:deprecated, &blk)
+ end
+
+ def with_config(new_value, &blk)
+ old_value = ActiveRecord::Base.allow_unsafe_raw_sql
+ ActiveRecord::Base.allow_unsafe_raw_sql = new_value
+ blk.call
+ ensure
+ ActiveRecord::Base.allow_unsafe_raw_sql = old_value
+ end
+end
diff --git a/activerecord/test/cases/validations/absence_validation_test.rb b/activerecord/test/cases/validations/absence_validation_test.rb
index a997f8be9c..8235a54d8a 100644
--- a/activerecord/test/cases/validations/absence_validation_test.rb
+++ b/activerecord/test/cases/validations/absence_validation_test.rb
@@ -13,8 +13,8 @@ class AbsenceValidationTest < ActiveRecord::TestCase
validates_absence_of :name
end
- assert boy_klass.new.valid?
- assert_not boy_klass.new(name: "Alex").valid?
+ assert_predicate boy_klass.new, :valid?
+ assert_not_predicate boy_klass.new(name: "Alex"), :valid?
end
def test_has_one_marked_for_destruction
@@ -44,7 +44,7 @@ class AbsenceValidationTest < ActiveRecord::TestCase
assert_not boy.valid?, "should not be valid if has_many association is present"
i2.mark_for_destruction
- assert boy.valid?
+ assert_predicate boy, :valid?
end
def test_does_not_call_to_a_on_associations
@@ -65,11 +65,11 @@ class AbsenceValidationTest < ActiveRecord::TestCase
Interest.validates_absence_of(:token)
interest = Interest.create!(topic: "Thought Leadering")
- assert interest.valid?
+ assert_predicate interest, :valid?
interest.token = "tl"
- assert interest.invalid?
+ assert_predicate interest, :invalid?
end
end
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 80fe375ae5..ce6d42b34b 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -16,14 +16,14 @@ class AssociationValidationTest < ActiveRecord::TestCase
Reply.validates_presence_of(:content)
t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")]
- assert !t.valid?
- assert t.errors[:replies].any?
+ assert_not_predicate t, :valid?
+ assert_predicate t.errors[:replies], :any?
assert_equal 1, r.errors.count # make sure all associated objects have been validated
assert_equal 0, r2.errors.count
assert_equal 1, r3.errors.count
assert_equal 0, r4.errors.count
r.content = r3.content = "non-empty"
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_associated_one
@@ -31,10 +31,10 @@ class AssociationValidationTest < ActiveRecord::TestCase
Topic.validates_presence_of(:content)
r = Reply.new("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
- assert !r.valid?
- assert r.errors[:topic].any?
+ assert_not_predicate r, :valid?
+ assert_predicate r.errors[:topic], :any?
r.topic.content = "non-empty"
- assert r.valid?
+ assert_predicate r, :valid?
end
def test_validates_associated_marked_for_destruction
@@ -42,9 +42,9 @@ class AssociationValidationTest < ActiveRecord::TestCase
Reply.validates_presence_of(:content)
t = Topic.new
t.replies << Reply.new
- assert t.invalid?
+ assert_predicate t, :invalid?
t.replies.first.mark_for_destruction
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_associated_without_marked_for_destruction
@@ -56,7 +56,7 @@ class AssociationValidationTest < ActiveRecord::TestCase
Topic.validates_associated(:replies)
t = Topic.new
t.define_singleton_method(:replies) { [reply.new] }
- assert t.valid?
+ assert_predicate t, :valid?
end
def test_validates_associated_with_custom_message_using_quotes
@@ -71,11 +71,11 @@ class AssociationValidationTest < ActiveRecord::TestCase
def test_validates_associated_missing
Reply.validates_presence_of(:topic)
r = Reply.create("title" => "A reply", "content" => "with content!")
- assert !r.valid?
- assert r.errors[:topic].any?
+ assert_not_predicate r, :valid?
+ assert_predicate r.errors[:topic], :any?
r.topic = Topic.first
- assert r.valid?
+ assert_predicate r, :valid?
end
def test_validates_presence_of_belongs_to_association__parent_is_new_record
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index 87ce4c6f37..73422a31cd 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -18,47 +18,47 @@ class LengthValidationTest < ActiveRecord::TestCase
assert_nothing_raised { @owner.validates_size_of :pets, minimum: 1 }
o = @owner.new("name" => "nopets")
assert !o.save
- assert o.errors[:pets].any?
+ assert_predicate o.errors[:pets], :any?
o.pets.build("name" => "apet")
- assert o.valid?
+ assert_predicate o, :valid?
end
def test_validates_size_of_association_using_within
assert_nothing_raised { @owner.validates_size_of :pets, within: 1..2 }
o = @owner.new("name" => "nopets")
assert !o.save
- assert o.errors[:pets].any?
+ assert_predicate o.errors[:pets], :any?
o.pets.build("name" => "apet")
- assert o.valid?
+ assert_predicate o, :valid?
2.times { o.pets.build("name" => "apet") }
assert !o.save
- assert o.errors[:pets].any?
+ assert_predicate o.errors[:pets], :any?
end
def test_validates_size_of_association_utf8
@owner.validates_size_of :pets, minimum: 1
o = @owner.new("name" => "あいうえおかきくけこ")
assert !o.save
- assert o.errors[:pets].any?
+ assert_predicate o.errors[:pets], :any?
o.pets.build("name" => "あいうえおかきくけこ")
- assert o.valid?
+ assert_predicate o, :valid?
end
def test_validates_size_of_respects_records_marked_for_destruction
@owner.validates_size_of :pets, minimum: 1
owner = @owner.new
assert_not owner.save
- assert owner.errors[:pets].any?
+ assert_predicate owner.errors[:pets], :any?
pet = owner.pets.build
- assert owner.valid?
+ assert_predicate owner, :valid?
assert owner.save
pet_count = Pet.count
assert_not owner.update_attributes pets_attributes: [ { _destroy: 1, id: pet.id } ]
- assert_not owner.valid?
- assert owner.errors[:pets].any?
+ assert_not_predicate owner, :valid?
+ assert_predicate owner.errors[:pets], :any?
assert_equal pet_count, Pet.count
end
@@ -70,11 +70,11 @@ class LengthValidationTest < ActiveRecord::TestCase
pet = Pet.create!(name: "Fancy Pants", nickname: "Fancy")
- assert pet.valid?
+ assert_predicate pet, :valid?
pet.nickname = ""
- assert pet.invalid?
+ assert_predicate pet, :invalid?
end
end
end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 3ab1567b51..63c3f67da2 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -15,10 +15,10 @@ class PresenceValidationTest < ActiveRecord::TestCase
def test_validates_presence_of_non_association
Boy.validates_presence_of(:name)
b = Boy.new
- assert b.invalid?
+ assert_predicate b, :invalid?
b.name = "Alex"
- assert b.valid?
+ assert_predicate b, :valid?
end
def test_validates_presence_of_has_one
@@ -33,23 +33,23 @@ class PresenceValidationTest < ActiveRecord::TestCase
b = Boy.new
f = Face.new
b.face = f
- assert b.valid?
+ assert_predicate b, :valid?
f.mark_for_destruction
- assert b.invalid?
+ assert_predicate b, :invalid?
end
def test_validates_presence_of_has_many_marked_for_destruction
Boy.validates_presence_of(:interests)
b = Boy.new
b.interests << [i1 = Interest.new, i2 = Interest.new]
- assert b.valid?
+ assert_predicate b, :valid?
i1.mark_for_destruction
- assert b.valid?
+ assert_predicate b, :valid?
i2.mark_for_destruction
- assert b.invalid?
+ assert_predicate b, :invalid?
end
def test_validates_presence_doesnt_convert_to_array
@@ -74,11 +74,11 @@ class PresenceValidationTest < ActiveRecord::TestCase
Interest.validates_presence_of(:abbreviation)
interest = Interest.create!(topic: "Thought Leadering", abbreviation: "tl")
- assert interest.valid?
+ assert_predicate interest, :valid?
interest.abbreviation = ""
- assert interest.invalid?
+ assert_predicate interest, :invalid?
end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index a10567f066..1fa94a5b75 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -96,7 +96,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
Topic.validates_uniqueness_of(:new_title)
topic = Topic.new(new_title: "abc")
- assert topic.valid?
+ assert_predicate topic, :valid?
end
def test_validates_uniqueness_with_nil_value
@@ -116,7 +116,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
Topic.create!("title" => "abc")
t2 = Topic.new("title" => "abc")
- assert !t2.valid?
+ assert_not_predicate t2, :valid?
assert t2.errors[:title]
end
@@ -259,15 +259,15 @@ class UniquenessValidationTest < ActiveRecord::TestCase
t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1)
assert !t2.valid?, "Shouldn't be valid"
assert !t2.save, "Shouldn't save t2 as unique"
- assert t2.errors[:title].any?
- assert t2.errors[:parent_id].any?
+ assert_predicate t2.errors[:title], :any?
+ assert_predicate t2.errors[:parent_id], :any?
assert_equal ["has already been taken"], t2.errors[:title]
t2.title = "I'm truly UNIQUE!"
assert !t2.valid?, "Shouldn't be valid"
assert !t2.save, "Shouldn't save t2 as unique"
- assert t2.errors[:title].empty?
- assert t2.errors[:parent_id].any?
+ assert_empty t2.errors[:title]
+ assert_predicate t2.errors[:parent_id], :any?
t2.parent_id = 4
assert t2.save, "Should now save t2 as unique"
@@ -326,15 +326,15 @@ class UniquenessValidationTest < ActiveRecord::TestCase
t2 = Topic.new("title" => "I'M UNIQUE!")
assert t2.valid?, "Should be valid"
assert t2.save, "Should save t2 as unique"
- assert t2.errors[:title].empty?
- assert t2.errors[:parent_id].empty?
+ assert_empty t2.errors[:title]
+ assert_empty t2.errors[:parent_id]
assert_not_equal ["has already been taken"], t2.errors[:title]
t3 = Topic.new("title" => "I'M uNiQUe!")
assert t3.valid?, "Should be valid"
assert t3.save, "Should save t2 as unique"
- assert t3.errors[:title].empty?
- assert t3.errors[:parent_id].empty?
+ assert_empty t3.errors[:title]
+ assert_empty t3.errors[:parent_id]
assert_not_equal ["has already been taken"], t3.errors[:title]
end
@@ -343,7 +343,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
Topic.create!("title" => 101)
t2 = Topic.new("title" => 101)
- assert !t2.valid?
+ assert_not_predicate t2, :valid?
assert t2.errors[:title]
end
@@ -360,7 +360,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary")
assert t1.save
t2 = Topic.new("title" => "I'm unique!", "author_name" => "David")
- assert !t2.valid?
+ assert_not_predicate t2, :valid?
end
end
@@ -460,16 +460,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase
def test_validate_uniqueness_on_existing_relation
event = Event.create
- assert TopicWithUniqEvent.create(event: event).valid?
+ assert_predicate TopicWithUniqEvent.create(event: event), :valid?
topic = TopicWithUniqEvent.new(event: event)
- assert_not topic.valid?
+ assert_not_predicate topic, :valid?
assert_equal ["has already been taken"], topic.errors[:event]
end
def test_validate_uniqueness_on_empty_relation
topic = TopicWithUniqEvent.new
- assert topic.valid?
+ assert_predicate topic, :valid?
end
def test_validate_uniqueness_of_custom_primary_key
@@ -488,7 +488,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
key2 = klass.create!(key_number: 11)
key2.key_number = 10
- assert_not key2.valid?
+ assert_not_predicate key2, :valid?
end
def test_validate_uniqueness_without_primary_key
@@ -501,8 +501,8 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
abc = klass.create!(dashboard_id: "abc")
- assert klass.new(dashboard_id: "xyz").valid?
- assert_not klass.new(dashboard_id: "abc").valid?
+ assert_predicate klass.new(dashboard_id: "xyz"), :valid?
+ assert_not_predicate klass.new(dashboard_id: "abc"), :valid?
abc.dashboard_id = "def"
@@ -530,7 +530,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert topic.author_name.start_with?("Title1")
topic2 = TopicWithAfterCreate.new(title: "Title1")
- refute topic2.valid?
+ assert_not_predicate topic2, :valid?
assert_equal(["has already been taken"], topic2.errors[:title])
end
@@ -550,7 +550,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert_empty item.errors
item2 = CoolTopic.new(id: item.id, title: "MyItem2")
- refute item2.valid?
+ assert_not_predicate item2, :valid?
assert_equal(["has already been taken"], item2.errors[:id])
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 7f84939027..6c83bbd15c 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -19,7 +19,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_valid_uses_create_context_when_new
r = WrongReply.new
r.title = "Wrong Create"
- assert_not r.valid?
+ assert_not_predicate r, :valid?
assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid"
assert_equal ["is Wrong Create"], r.errors[:title], "A reply with a bad content should contain an error"
end
@@ -139,7 +139,7 @@ class ValidationsTest < ActiveRecord::TestCase
def test_throw_away_typing
d = Developer.new("name" => "David", "salary" => "100,000")
- assert !d.valid?
+ assert_not_predicate d, :valid?
assert_equal 100, d.salary
assert_equal "100,000", d.salary_before_type_cast
end
@@ -166,7 +166,7 @@ class ValidationsTest < ActiveRecord::TestCase
topic = klass.new(wibble: "123-4567")
topic.wibble.gsub!("-", "")
- assert topic.valid?
+ assert_predicate topic, :valid?
end
def test_numericality_validation_checks_against_raw_value
@@ -175,12 +175,12 @@ class ValidationsTest < ActiveRecord::TestCase
ActiveModel::Name.new(self, nil, "Topic")
end
attribute :wibble, :decimal, scale: 2, precision: 9
- validates_numericality_of :wibble, greater_than_or_equal_to: BigDecimal.new("97.18")
+ validates_numericality_of :wibble, greater_than_or_equal_to: BigDecimal("97.18")
end
- assert_not klass.new(wibble: "97.179").valid?
- assert_not klass.new(wibble: 97.179).valid?
- assert_not klass.new(wibble: BigDecimal.new("97.179")).valid?
+ assert_not_predicate klass.new(wibble: "97.179"), :valid?
+ assert_not_predicate klass.new(wibble: 97.179), :valid?
+ assert_not_predicate klass.new(wibble: BigDecimal("97.179")), :valid?
end
def test_acceptance_validator_doesnt_require_db_connection
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 578881f754..60ebdce178 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -96,7 +96,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_deserializing_rails_41_yaml
topic = YAML.load(yaml_fixture("rails_4_1"))
- assert topic.new_record?
+ assert_predicate topic, :new_record?
assert_nil topic.id
assert_equal "The First Topic", topic.title
assert_equal({ omg: :lol }, topic.content)
@@ -105,7 +105,7 @@ class YamlSerializationTest < ActiveRecord::TestCase
def test_deserializing_rails_4_2_0_yaml
topic = YAML.load(yaml_fixture("rails_4_2_0"))
- assert_not topic.new_record?
+ assert_not_predicate topic, :new_record?
assert_equal 1, topic.id
assert_equal "The First Topic", topic.title
assert_equal("Have a nice day", topic.content)
diff --git a/activerecord/test/fixtures/customers.yml b/activerecord/test/fixtures/customers.yml
index 0399ff83b9..7d6c1366d0 100644
--- a/activerecord/test/fixtures/customers.yml
+++ b/activerecord/test/fixtures/customers.yml
@@ -23,4 +23,13 @@ barney:
address_street: Quiet Road
address_city: Peaceful Town
address_country: Tranquil Land
- gps_location: NULL \ No newline at end of file
+ gps_location: NULL
+
+mary:
+ id: 4
+ name: Mary
+ balance: 1
+ address_street: Funny Street
+ address_city: Peaceful Town
+ address_country: Nation Land
+ gps_location: NULL
diff --git a/activerecord/test/fixtures/minimalistics.yml b/activerecord/test/fixtures/minimalistics.yml
index c3ec546209..83df0551bc 100644
--- a/activerecord/test/fixtures/minimalistics.yml
+++ b/activerecord/test/fixtures/minimalistics.yml
@@ -1,2 +1,5 @@
+zero:
+ id: 0
+
first:
id: 1
diff --git a/activerecord/test/fixtures/reserved_words/values.yml b/activerecord/test/fixtures/reserved_words/values.yml
index 7d109609ab..9ed9e5edc5 100644
--- a/activerecord/test/fixtures/reserved_words/values.yml
+++ b/activerecord/test/fixtures/reserved_words/values.yml
@@ -1,7 +1,7 @@
values1:
- id: 1
+ as: 1
group_id: 2
values2:
- id: 2
+ as: 2
group_id: 1
diff --git a/activerecord/test/fixtures/teapots.yml b/activerecord/test/fixtures/teapots.yml
deleted file mode 100644
index ff515beb45..0000000000
--- a/activerecord/test/fixtures/teapots.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-bob:
- id: 1
- name: Bob
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index cb8686f315..27da886e1c 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -88,6 +88,9 @@ class Author < ActiveRecord::Base
has_many :special_categories, through: :special_categorizations, source: :category
has_one :special_category, through: :special_categorizations, source: :category
+ has_many :special_categories_with_conditions, -> { where(categorizations: { special: true }) }, through: :categorizations, source: :category
+ has_many :nonspecial_categories_with_conditions, -> { where(categorizations: { special: false }) }, through: :categorizations, source: :category
+
has_many :categories_like_general, -> { where(name: "General") }, through: :categorizations, source: :category, class_name: "Category"
has_many :categorized_posts, through: :categorizations, source: :post
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index bbc5fc2b2d..fc6488f729 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -87,6 +87,8 @@ class Firm < Company
has_many :association_with_references, -> { references(:foo) }, class_name: "Client"
+ has_many :developers_with_select, -> { select("id, name, first_name") }, class_name: "Developer"
+
has_one :lead_developer, class_name: "Developer"
has_many :projects
diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb
index 9454217e8d..f273badd85 100644
--- a/activerecord/test/models/contract.rb
+++ b/activerecord/test/models/contract.rb
@@ -2,7 +2,7 @@
class Contract < ActiveRecord::Base
belongs_to :company
- belongs_to :developer
+ belongs_to :developer, primary_key: :id
belongs_to :firm, foreign_key: "company_id"
before_save :hi
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index 524a9d7bd9..bc501a5ce0 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -4,7 +4,7 @@ class Customer < ActiveRecord::Base
cattr_accessor :gps_conversion_was_run
composed_of :address, mapping: [ %w(address_street street), %w(address_city city), %w(address_country country) ], allow_nil: true
- composed_of :balance, class_name: "Money", mapping: %w(balance amount), converter: Proc.new(&:to_money)
+ composed_of :balance, class_name: "Money", mapping: %w(balance amount)
composed_of :gps_location, allow_nil: true
composed_of :non_blank_gps_location, class_name: "GpsLocation", allow_nil: true, mapping: %w(gps_location gps_location),
converter: lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps) }
diff --git a/activerecord/test/models/face.rb b/activerecord/test/models/face.rb
index 796aaa4dc9..948435136d 100644
--- a/activerecord/test/models/face.rb
+++ b/activerecord/test/models/face.rb
@@ -8,4 +8,8 @@ class Face < ActiveRecord::Base
# These is a "broken" inverse_of for the purposes of testing
belongs_to :horrible_man, class_name: "Man", inverse_of: :horrible_face
belongs_to :horrible_polymorphic_man, polymorphic: true, inverse_of: :horrible_polymorphic_face
+
+ validate do
+ man
+ end
end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 7f064bf3dd..54eb5e6783 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -106,6 +106,9 @@ class Post < ActiveRecord::Base
end
end
+ has_many :indestructible_taggings, as: :taggable, counter_cache: :indestructible_tags_count
+ has_many :indestructible_tags, through: :indestructible_taggings, source: :tag
+
has_many :taggings_with_delete_all, class_name: "Tagging", as: :taggable, dependent: :delete_all, counter_cache: :taggings_with_delete_all_count
has_many :taggings_with_destroy, class_name: "Tagging", as: :taggable, dependent: :destroy, counter_cache: :taggings_with_destroy_count
@@ -319,5 +322,17 @@ class FakeKlass
def arel_attribute(name, table)
table[name]
end
+
+ def enforce_raw_sql_whitelist(*args)
+ # noop
+ end
+
+ def arel_table
+ Post.arel_table
+ end
+
+ def predicate_builder
+ Post.predicate_builder
+ end
end
end
diff --git a/activerecord/test/models/tag.rb b/activerecord/test/models/tag.rb
index bc13c3a42d..c1a8890a8a 100644
--- a/activerecord/test/models/tag.rb
+++ b/activerecord/test/models/tag.rb
@@ -11,6 +11,6 @@ end
class OrderedTag < Tag
self.table_name = "tags"
- has_many :taggings, -> { order("taggings.id DESC") }, foreign_key: "tag_id"
- has_many :tagged_posts, through: :taggings, source: "taggable", source_type: "Post"
+ has_many :ordered_taggings, -> { order("taggings.id DESC") }, foreign_key: "tag_id", class_name: "Tagging"
+ has_many :tagged_posts, through: :ordered_taggings, source: "taggable", source_type: "Post"
end
diff --git a/activerecord/test/models/tagging.rb b/activerecord/test/models/tagging.rb
index 861fde633f..6d4230f6f4 100644
--- a/activerecord/test/models/tagging.rb
+++ b/activerecord/test/models/tagging.rb
@@ -14,3 +14,7 @@ class Tagging < ActiveRecord::Base
belongs_to :taggable, polymorphic: true, counter_cache: :tags_count
has_many :things, through: :taggable
end
+
+class IndestructibleTagging < Tagging
+ before_destroy { throw :abort }
+end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 2154b50ef7..8cd4dc352a 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -65,6 +65,9 @@ class Topic < ActiveRecord::Base
after_initialize :set_email_address
+ attr_accessor :change_approved_before_save
+ before_save :change_approved_callback
+
class_attribute :after_initialize_called
after_initialize do
self.class.after_initialize_called = true
@@ -96,6 +99,10 @@ class Topic < ActiveRecord::Base
def before_destroy_for_transaction; end
def after_save_for_transaction; end
def after_create_for_transaction; end
+
+ def change_approved_callback
+ self.approved = change_approved_before_save unless change_approved_before_save.nil?
+ end
end
class ImportantTopic < Topic
diff --git a/activerecord/test/models/wheel.rb b/activerecord/test/models/wheel.rb
index e05fb64477..8db57d181e 100644
--- a/activerecord/test/models/wheel.rb
+++ b/activerecord/test/models/wheel.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
class Wheel < ActiveRecord::Base
- belongs_to :wheelable, polymorphic: true, counter_cache: true
+ belongs_to :wheelable, polymorphic: true, counter_cache: true, touch: true
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index a4505a4892..7d008eecd5 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -123,7 +123,7 @@ ActiveRecord::Schema.define do
create_table :cars, force: true do |t|
t.string :name
t.integer :engines_count
- t.integer :wheels_count
+ t.integer :wheels_count, default: 0
t.column :lock_version, :integer, null: false, default: 0
t.timestamps null: false
end
@@ -205,6 +205,8 @@ ActiveRecord::Schema.define do
t.bigint :rating, default: 1
t.integer :account_id
t.string :description, default: ""
+ t.index [:name, :rating], order: :desc
+ t.index [:name, :description], length: 10
t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc }
t.index [:firm_id, :type], name: "company_partial_index", where: "(rating > 10)"
t.index :name, name: "company_name_index", using: :btree
@@ -688,6 +690,7 @@ ActiveRecord::Schema.define do
t.integer :taggings_with_delete_all_count, default: 0
t.integer :taggings_with_destroy_count, default: 0
t.integer :tags_count, default: 0
+ t.integer :indestructible_tags_count, default: 0
t.integer :tags_with_destroy_count, default: 0
t.integer :tags_with_nullify_count, default: 0
end
@@ -845,6 +848,7 @@ ActiveRecord::Schema.define do
t.column :taggable_type, :string
t.column :taggable_id, :integer
t.string :comment
+ t.string :type
end
create_table :tasks, force: true do |t|
@@ -962,6 +966,7 @@ ActiveRecord::Schema.define do
end
create_table :wheels, force: true do |t|
+ t.integer :size
t.references :wheelable, polymorphic: true
end
diff --git a/activestorage/.gitignore b/activestorage/.gitignore
index a532335bdd..29bdcc4468 100644
--- a/activestorage/.gitignore
+++ b/activestorage/.gitignore
@@ -1,5 +1,6 @@
.byebug_history
node_modules
+src
test/dummy/db/*.sqlite3
test/dummy/db/*.sqlite3-journal
test/dummy/log/*.log
diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md
index 358552313f..682e35e0a8 100644
--- a/activestorage/CHANGELOG.md
+++ b/activestorage/CHANGELOG.md
@@ -1,3 +1,8 @@
-* Added to Rails.
+* Add source code to published npm package
- *DHH*
+ This allows activestorage users to depend on the javascript source code
+ rather than the compiled code, which can produce smaller javascript bundles.
+
+ *Richard Macklin*
+
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activestorage/CHANGELOG.md) for previous changes.
diff --git a/activestorage/MIT-LICENSE b/activestorage/MIT-LICENSE
index 4e1c6cad79..eed89ac398 100644
--- a/activestorage/MIT-LICENSE
+++ b/activestorage/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2017 David Heinemeier Hansson, Basecamp
+Copyright (c) 2017-2018 David Heinemeier Hansson, Basecamp
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activestorage/README.md b/activestorage/README.md
index 78e4463c5a..85ab70dac6 100644
--- a/activestorage/README.md
+++ b/activestorage/README.md
@@ -1,6 +1,6 @@
# Active Storage
-Active Storage makes it simple to upload and reference files in cloud services like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage, and attach those files to Active Records. Supports having one main service and mirrors in other services for redundancy. It also provides a disk service for testing or local deployments, but the focus is on cloud storage.
+Active Storage makes it simple to upload and reference files in cloud services like [Amazon S3](https://aws.amazon.com/s3/), [Google Cloud Storage](https://cloud.google.com/storage/docs/), or [Microsoft Azure Storage](https://azure.microsoft.com/en-us/services/storage/), and attach those files to Active Records. Supports having one main service and mirrors in other services for redundancy. It also provides a disk service for testing or local deployments, but the focus is on cloud storage.
Files can be uploaded from the server to the cloud or directly from the client to the cloud.
@@ -143,3 +143,17 @@ Active Storage, with its included JavaScript library, supports uploading directl
## License
Active Storage is released under the [MIT License](https://opensource.org/licenses/MIT).
+
+## Support
+
+API documentation is at:
+
+* http://api.rubyonrails.org
+
+Bug reports for the Ruby on Rails project can be filed here:
+
+* https://github.com/rails/rails/issues
+
+Feature requests should be discussed on the rails-core mailing list here:
+
+* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
diff --git a/activestorage/activestorage.gemspec b/activestorage/activestorage.gemspec
index 911e1a0469..d135324700 100644
--- a/activestorage/activestorage.gemspec
+++ b/activestorage/activestorage.gemspec
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
s.email = "david@loudthinking.com"
s.homepage = "http://rubyonrails.org"
- s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*", "app/**/*", "config/**/*"]
+ s.files = Dir["CHANGELOG.md", "MIT-LICENSE", "README.md", "lib/**/*", "app/**/*", "config/**/*", "db/**/*"]
s.require_path = "lib"
s.metadata = {
@@ -27,4 +27,8 @@ Gem::Specification.new do |s|
s.add_dependency "actionpack", version
s.add_dependency "activerecord", version
+
+ s.add_dependency "marcel", "~> 0.3.1"
+
+ s.add_development_dependency "webmock", "~> 3.2.1"
end
diff --git a/activestorage/app/assets/javascripts/activestorage.js b/activestorage/app/assets/javascripts/activestorage.js
index c1c0a2f6d9..a2f06c43a3 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.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
+!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=t.disabled,i=r.bubbles,a=r.cancelable,u=r.detail,o=document.createEvent("Event");o.initEvent(e,i||!0,a||!0),o.detail=u||{};try{t.disabled=!1,t.dispatchEvent(o)}finally{t.disabled=n}return o}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){if(this.status>=200&&this.status<300){var e=this.response,r=e.direct_upload;delete e.direct_upload,this.attributes=e,this.directUploadData=r,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.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}},{key:"status",get:function(){return this.xhr.status}},{key:"response",get:function(){var t=this.xhr,e=t.responseType,r=t.response;return"json"==e?r:JSON.parse(r)}}]),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 a17e3852f9..fa44131048 100644
--- a/activestorage/app/controllers/active_storage/blobs_controller.rb
+++ b/activestorage/app/controllers/active_storage/blobs_controller.rb
@@ -5,12 +5,10 @@
# security-through-obscurity factor of the signed blob references, you'll need to implement your own
# authenticated redirection controller.
class ActiveStorage::BlobsController < ActionController::Base
+ include ActiveStorage::SetBlob
+
def show
- 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
+ expires_in ActiveStorage::Blob.service.url_expires_in
+ redirect_to @blob.service_url(disposition: params[:disposition])
end
end
diff --git a/activestorage/app/controllers/active_storage/disk_controller.rb b/activestorage/app/controllers/active_storage/disk_controller.rb
index a4fd427cb2..a7e10c0696 100644
--- a/activestorage/app/controllers/active_storage/disk_controller.rb
+++ b/activestorage/app/controllers/active_storage/disk_controller.rb
@@ -5,6 +5,8 @@
# Always go through the BlobsController, or your own authenticated controller, rather than directly
# to the service url.
class ActiveStorage::DiskController < ActionController::Base
+ skip_forgery_protection if default_protect_from_forgery
+
def show
if key = decode_verified_key
send_data disk_service.download(key),
diff --git a/activestorage/app/controllers/active_storage/previews_controller.rb b/activestorage/app/controllers/active_storage/previews_controller.rb
index 9e8cf27b6e..aa7ef58ca4 100644
--- a/activestorage/app/controllers/active_storage/previews_controller.rb
+++ b/activestorage/app/controllers/active_storage/previews_controller.rb
@@ -1,12 +1,10 @@
# frozen_string_literal: true
class ActiveStorage::PreviewsController < ActionController::Base
+ include ActiveStorage::SetBlob
+
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
+ expires_in ActiveStorage::Blob.service.url_expires_in
+ redirect_to ActiveStorage::Preview.new(@blob, params[:variation_key]).processed.service_url(disposition: params[:disposition])
end
end
diff --git a/activestorage/app/controllers/active_storage/variants_controller.rb b/activestorage/app/controllers/active_storage/variants_controller.rb
index dc5e78ecc0..e8f8dd592d 100644
--- a/activestorage/app/controllers/active_storage/variants_controller.rb
+++ b/activestorage/app/controllers/active_storage/variants_controller.rb
@@ -5,12 +5,10 @@
# security-through-obscurity factor of the signed blob and variation reference, you'll need to implement your own
# authenticated redirection controller.
class ActiveStorage::VariantsController < ActionController::Base
+ include ActiveStorage::SetBlob
+
def show
- 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
+ expires_in ActiveStorage::Blob.service.url_expires_in
+ redirect_to ActiveStorage::Variant.new(@blob, params[:variation_key]).processed.service_url(disposition: params[:disposition])
end
end
diff --git a/activestorage/app/controllers/concerns/active_storage/set_blob.rb b/activestorage/app/controllers/concerns/active_storage/set_blob.rb
new file mode 100644
index 0000000000..f072954d78
--- /dev/null
+++ b/activestorage/app/controllers/concerns/active_storage/set_blob.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module ActiveStorage::SetBlob #:nodoc:
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :set_blob
+ end
+
+ private
+ def set_blob
+ @blob = ActiveStorage::Blob.find_signed(params[:signed_blob_id] || params[:signed_id])
+ rescue ActiveSupport::MessageVerifier::InvalidSignature
+ head :not_found
+ end
+end
diff --git a/activestorage/app/javascript/activestorage/blob_record.js b/activestorage/app/javascript/activestorage/blob_record.js
index 3c6e6b6ba1..ff847892b2 100644
--- a/activestorage/app/javascript/activestorage/blob_record.js
+++ b/activestorage/app/javascript/activestorage/blob_record.js
@@ -22,14 +22,28 @@ export class BlobRecord {
this.xhr.addEventListener("error", event => this.requestDidError(event))
}
+ get status() {
+ return this.xhr.status
+ }
+
+ get response() {
+ const { responseType, response } = this.xhr
+ if (responseType == "json") {
+ return response
+ } else {
+ // Shim for IE 11: https://connect.microsoft.com/IE/feedback/details/794808
+ return JSON.parse(response)
+ }
+ }
+
create(callback) {
this.callback = callback
this.xhr.send(JSON.stringify({ blob: this.attributes }))
}
requestDidLoad(event) {
- const { status, response } = this.xhr
- if (status >= 200 && status < 300) {
+ if (this.status >= 200 && this.status < 300) {
+ const { response } = this
const { direct_upload } = response
delete response.direct_upload
this.attributes = response
@@ -41,7 +55,7 @@ export class BlobRecord {
}
requestDidError(event) {
- this.callback(`Error creating Blob for "${this.file.name}". Status: ${this.xhr.status}`)
+ this.callback(`Error creating Blob for "${this.file.name}". Status: ${this.status}`)
}
toJSON() {
diff --git a/activestorage/app/javascript/activestorage/helpers.js b/activestorage/app/javascript/activestorage/helpers.js
index 52fec8f6f1..7e83c447e7 100644
--- a/activestorage/app/javascript/activestorage/helpers.js
+++ b/activestorage/app/javascript/activestorage/helpers.js
@@ -23,11 +23,20 @@ export function findElement(root, selector) {
}
export function dispatchEvent(element, type, eventInit = {}) {
+ const { disabled } = element
const { bubbles, cancelable, detail } = eventInit
const event = document.createEvent("Event")
+
event.initEvent(type, bubbles || true, cancelable || true)
event.detail = detail || {}
- element.dispatchEvent(event)
+
+ try {
+ element.disabled = false
+ element.dispatchEvent(event)
+ } finally {
+ element.disabled = disabled
+ }
+
return event
}
diff --git a/activestorage/app/models/active_storage/attachment.rb b/activestorage/app/models/active_storage/attachment.rb
index 9f61a5dbf3..19f48c57d6 100644
--- a/activestorage/app/models/active_storage/attachment.rb
+++ b/activestorage/app/models/active_storage/attachment.rb
@@ -14,7 +14,7 @@ class ActiveStorage::Attachment < ActiveRecord::Base
delegate_missing_to :blob
- after_create_commit :analyze_blob_later
+ after_create_commit :identify_blob, :analyze_blob_later
# Synchronously purges the blob (deletes it from the configured service) and destroys the attachment.
def purge
@@ -29,6 +29,10 @@ class ActiveStorage::Attachment < ActiveRecord::Base
end
private
+ def identify_blob
+ blob.identify
+ end
+
def analyze_blob_later
blob.analyze_later unless blob.analyzed?
end
diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index 99823e14c6..a1e69e2264 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -1,7 +1,5 @@
# 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:
#
@@ -16,20 +14,17 @@ require "active_storage/analyzer/null_analyzer"
# 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
+ include Analyzable, Identifiable, Representable
self.table_name = "active_storage_blobs"
has_secure_token :key
- store :metadata, accessors: [ :analyzed ], coder: JSON
+ store :metadata, accessors: [ :analyzed, :identified ], coder: ActiveRecord::Coders::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
@@ -68,7 +63,6 @@ class ActiveStorage::Blob < ActiveRecord::Base
end
end
-
# Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering.
# It uses the framework-wide verifier on <tt>ActiveStorage.verifier</tt>, but with a dedicated purpose.
def signed_id
@@ -110,85 +104,16 @@ class ActiveStorage::Blob < ActiveRecord::Base
content_type.start_with?("text")
end
- # 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 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 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.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
-
# Returns the URL of the blob on the service. This URL is intended to be short-lived for security and not used directly
# 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: service.url_expires_in, disposition: "inline")
- service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
+ def service_url(expires_in: service.url_expires_in, disposition: :inline, filename: nil, **options)
+ filename = ActiveStorage::Filename.wrap(filename || self.filename)
+
+ service.url key, expires_in: expires_in, filename: filename, content_type: content_type,
+ disposition: forcibly_serve_as_binary? ? :attachment : disposition, **options
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
@@ -202,6 +127,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
service.headers_for_direct_upload key, filename: filename, content_type: content_type, content_length: byte_size, checksum: checksum
end
+
# Uploads the +io+ to the service on the +key+ for this blob. Blobs are intended to be immutable, so you shouldn't be
# using this method after a file has already been uploaded to fit with a blob. If you want to create a derivative blob,
# you should instead simply create a new blob based on the old one.
@@ -213,8 +139,10 @@ class ActiveStorage::Blob < ActiveRecord::Base
# Normally, you do not have to call this method directly at all. Use the factory class methods of +build_after_upload+
# and +create_after_upload!+.
def upload(io)
- self.checksum = compute_checksum_in_chunks(io)
- self.byte_size = io.size
+ self.checksum = compute_checksum_in_chunks(io)
+ self.content_type = extract_content_type(io)
+ self.byte_size = io.size
+ self.identified = true
service.upload(key, io, checksum: checksum)
end
@@ -226,51 +154,12 @@ 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.
def delete
- service.delete key
+ service.delete(key)
+ service.delete_prefixed("variants/#{key}/") if image?
end
# Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted
@@ -298,16 +187,11 @@ class ActiveStorage::Blob < ActiveRecord::Base
end.base64digest
end
-
- def extract_metadata_via_analyzer
- analyzer.metadata.merge(analyzed: true)
- end
-
- def analyzer
- analyzer_class.new(self)
+ def extract_content_type(io)
+ Marcel::MimeType.for io, name: filename.to_s, declared_type: content_type
end
- def analyzer_class
- ActiveStorage.analyzers.detect { |klass| klass.accept?(self) } || ActiveStorage::Analyzer::NullAnalyzer
+ def forcibly_serve_as_binary?
+ ActiveStorage.content_types_to_serve_as_binary.include?(content_type)
end
end
diff --git a/activestorage/app/models/active_storage/blob/analyzable.rb b/activestorage/app/models/active_storage/blob/analyzable.rb
new file mode 100644
index 0000000000..5bda6e6d73
--- /dev/null
+++ b/activestorage/app/models/active_storage/blob/analyzable.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require "active_storage/analyzer/null_analyzer"
+
+module ActiveStorage::Blob::Analyzable
+ # 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: metadata.merge(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
+
+ private
+ 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/blob/identifiable.rb b/activestorage/app/models/active_storage/blob/identifiable.rb
new file mode 100644
index 0000000000..40ca84ac70
--- /dev/null
+++ b/activestorage/app/models/active_storage/blob/identifiable.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module ActiveStorage::Blob::Identifiable
+ def identify
+ ActiveStorage::Identification.new(self).apply
+ end
+
+ def identified?
+ identified
+ end
+end
diff --git a/activestorage/app/models/active_storage/blob/representable.rb b/activestorage/app/models/active_storage/blob/representable.rb
new file mode 100644
index 0000000000..0ad2e2fd77
--- /dev/null
+++ b/activestorage/app/models/active_storage/blob/representable.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+module ActiveStorage::Blob::Representable
+ extend ActiveSupport::Concern
+
+ included do
+ has_one_attached :preview_image
+ end
+
+ # 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 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 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.
+ #
+ # Raises ActiveStorage::InvariableError if ImageMagick cannot transform the blob. To determine whether a blob is
+ # variable, call ActiveStorage::Blob#variable?.
+ def variant(transformations)
+ if variable?
+ ActiveStorage::Variant.new(self, ActiveStorage::Variation.wrap(transformations))
+ else
+ raise ActiveStorage::InvariableError
+ end
+ end
+
+ # Returns true if ImageMagick can transform the blob (its content type is in +ActiveStorage.variable_content_types+).
+ def variable?
+ ActiveStorage.variable_content_types.include?(content_type)
+ 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::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 ActiveStorage::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 for a previewable blob or an ActiveStorage::Variant for a variable image blob.
+ #
+ # blob.representation(resize: "100x100").processed.service_url
+ #
+ # Raises ActiveStorage::UnrepresentableError if the receiving blob is neither variable 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 variable?
+ variant transformations
+ else
+ raise ActiveStorage::UnrepresentableError
+ end
+ end
+
+ # Returns true if the blob is variable or previewable.
+ def representable?
+ variable? || previewable?
+ end
+end
diff --git a/activestorage/app/models/active_storage/filename.rb b/activestorage/app/models/active_storage/filename.rb
index 79d55dc889..2b8880716e 100644
--- a/activestorage/app/models/active_storage/filename.rb
+++ b/activestorage/app/models/active_storage/filename.rb
@@ -5,6 +5,14 @@
class ActiveStorage::Filename
include Comparable
+ class << self
+ # Returns a Filename instance based on the given filename. If the filename is a Filename, it is
+ # returned unmodified. If it is a String, it is passed to ActiveStorage::Filename.new.
+ def wrap(filename)
+ filename.kind_of?(self) ? filename : new(filename)
+ end
+ end
+
def initialize(filename)
@filename = filename
end
@@ -50,7 +58,7 @@ class ActiveStorage::Filename
@filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
end
- def parameters
+ def parameters #:nodoc:
Parameters.new self
end
diff --git a/activestorage/app/models/active_storage/filename/parameters.rb b/activestorage/app/models/active_storage/filename/parameters.rb
index 58ce198d38..fb9ea10e49 100644
--- a/activestorage/app/models/active_storage/filename/parameters.rb
+++ b/activestorage/app/models/active_storage/filename/parameters.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ActiveStorage::Filename::Parameters
+class ActiveStorage::Filename::Parameters #:nodoc:
attr_reader :filename
def initialize(filename)
diff --git a/activestorage/app/models/active_storage/identification.rb b/activestorage/app/models/active_storage/identification.rb
new file mode 100644
index 0000000000..4f295257ae
--- /dev/null
+++ b/activestorage/app/models/active_storage/identification.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+class ActiveStorage::Identification
+ attr_reader :blob
+
+ def initialize(blob)
+ @blob = blob
+ end
+
+ def apply
+ blob.update!(content_type: content_type, identified: true) unless blob.identified?
+ end
+
+ private
+ def content_type
+ Marcel::MimeType.for(identifiable_chunk, name: filename, declared_type: declared_content_type)
+ end
+
+
+ def identifiable_chunk
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client|
+ client.get(uri, "Range" => "0-4096").body
+ end
+ end
+
+ def uri
+ @uri ||= URI.parse(blob.service_url)
+ end
+
+
+ def filename
+ blob.filename.to_s
+ end
+
+ def declared_content_type
+ blob.content_type
+ end
+end
diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb
index fa5aa69bd3..e08a2271ec 100644
--- a/activestorage/app/models/active_storage/variant.rb
+++ b/activestorage/app/models/active_storage/variant.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_storage/downloading"
+
# Image blobs can have variants that are the result of a set of transformations applied to the original.
# These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the
# original.
@@ -16,7 +18,7 @@
# To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided
# by Active Storage 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.
@@ -35,6 +37,10 @@
#
# avatar.variant(resize: "100x100", monochrome: true, flip: "-90")
class ActiveStorage::Variant
+ include ActiveStorage::Downloading
+
+ WEB_IMAGE_CONTENT_TYPES = %w( image/png image/jpeg image/jpg image/gif )
+
attr_reader :blob, :variation
delegate :service, to: :blob
@@ -62,7 +68,7 @@ class ActiveStorage::Variant
# 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: service.url_expires_in, disposition: :inline)
- service.url key, expires_in: expires_in, disposition: disposition, filename: blob.filename, content_type: blob.content_type
+ service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
end
# Returns the receiving variant. Allows ActiveStorage::Variant and ActiveStorage::Preview instances to be used interchangeably.
@@ -76,11 +82,51 @@ class ActiveStorage::Variant
end
def process
- service.upload key, transform(service.download(blob.key))
+ open_image do |image|
+ transform image
+ format image
+ upload image
+ end
+ end
+
+
+ def filename
+ if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
+ blob.filename
+ else
+ ActiveStorage::Filename.new("#{blob.filename.base}.png")
+ end
+ end
+
+ def content_type
+ blob.content_type.presence_in(WEB_IMAGE_CONTENT_TYPES) || "image/png"
+ end
+
+
+ def open_image(&block)
+ image = download_image
+
+ begin
+ yield image
+ ensure
+ image.destroy!
+ end
end
- def transform(io)
+ def download_image
require "mini_magick"
- File.open MiniMagick::Image.read(io).tap { |image| variation.transform(image) }.path
+ MiniMagick::Image.create { |file| download_blob_to(file) }
+ end
+
+ def transform(image)
+ variation.transform(image)
+ end
+
+ def format(image)
+ image.format("PNG") unless WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
+ end
+
+ def upload(image)
+ File.open(image.path, "r") { |file| service.upload(key, file) }
end
end
diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb
index 13bad87cac..12e7f9f0b5 100644
--- a/activestorage/app/models/active_storage/variation.rb
+++ b/activestorage/app/models/active_storage/variation.rb
@@ -8,6 +8,14 @@
#
# ActiveStorage::Variation.new(resize: "100x100", monochrome: true, trim: true, rotate: "-90")
#
+# You can also combine multiple transformations in one step, e.g. for center-weighted cropping:
+#
+# ActiveStorage::Variation.new(combine_options: {
+# resize: "100x100^",
+# gravity: "center",
+# crop: "100x100+0+0",
+# })
+#
# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php.
class ActiveStorage::Variation
attr_reader :transformations
@@ -46,11 +54,17 @@ class ActiveStorage::Variation
# Accepts an open MiniMagick image instance, like what's returned by <tt>MiniMagick::Image.read(io)</tt>,
# and performs the +transformations+ against it. The transformed image instance is then returned.
def transform(image)
- transformations.each do |(method, argument)|
- if eligible_argument?(argument)
- image.public_send(method, argument)
- else
- image.public_send(method)
+ ActiveSupport::Notifications.instrument("transform.active_storage") do
+ transformations.each do |name, argument_or_subtransformations|
+ image.mogrify do |command|
+ if name.to_s == "combine_options"
+ argument_or_subtransformations.each do |subtransformation_name, subtransformation_argument|
+ pass_transform_argument(command, subtransformation_name, subtransformation_argument)
+ end
+ else
+ pass_transform_argument(command, name, argument_or_subtransformations)
+ end
+ end
end
end
end
@@ -61,6 +75,14 @@ class ActiveStorage::Variation
end
private
+ def pass_transform_argument(command, method, argument)
+ if eligible_argument?(argument)
+ command.public_send(method, argument)
+ else
+ command.public_send(method)
+ end
+ end
+
def eligible_argument?(argument)
argument.present? && argument != true
end
diff --git a/activestorage/config/routes.rb b/activestorage/config/routes.rb
index c659e079fd..3f4129d915 100644
--- a/activestorage/config/routes.rb
+++ b/activestorage/config/routes.rb
@@ -1,43 +1,43 @@
# 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, internal: true
+ get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob
- direct :rails_blob do |blob|
- route_for(:rails_service_blob, blob.signed_id, blob.filename)
+ direct :rails_blob do |blob, options|
+ route_for(:rails_service_blob, blob.signed_id, blob.filename, options)
end
- resolve("ActiveStorage::Blob") { |blob| route_for(:rails_blob, blob) }
- resolve("ActiveStorage::Attachment") { |attachment| route_for(:rails_blob, attachment.blob) }
+ resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) }
+ resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) }
- get "/rails/active_storage/variants/:signed_blob_id/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation, internal: true
+ get "/rails/active_storage/variants/:signed_blob_id/:variation_key/*filename" => "active_storage/variants#show", as: :rails_blob_variation
- direct :rails_variant do |variant|
+ direct :rails_variant do |variant, options|
signed_blob_id = variant.blob.signed_id
variation_key = variant.variation.key
filename = variant.blob.filename
- route_for(:rails_blob_variation, signed_blob_id, variation_key, filename)
+ route_for(:rails_blob_variation, signed_blob_id, variation_key, filename, options)
end
- resolve("ActiveStorage::Variant") { |variant| route_for(:rails_variant, variant) }
+ resolve("ActiveStorage::Variant") { |variant, options| route_for(:rails_variant, variant, options) }
- get "/rails/active_storage/previews/:signed_blob_id/:variation_key/*filename" => "active_storage/previews#show", as: :rails_blob_preview, internal: true
+ get "/rails/active_storage/previews/:signed_blob_id/:variation_key/*filename" => "active_storage/previews#show", as: :rails_blob_preview
- direct :rails_preview do |preview|
+ direct :rails_preview do |preview, options|
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)
+ route_for(:rails_blob_preview, signed_blob_id, variation_key, filename, options)
end
- resolve("ActiveStorage::Preview") { |preview| route_for(:rails_preview, preview) }
+ resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_preview, preview, options) }
- 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
+ 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
end
diff --git a/activestorage/lib/active_storage.rb b/activestorage/lib/active_storage.rb
index d1ff6b7032..e1bd974853 100644
--- a/activestorage/lib/active_storage.rb
+++ b/activestorage/lib/active_storage.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2017 David Heinemeier Hansson
+# Copyright (c) 2017-2018 David Heinemeier Hansson, Basecamp
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -26,7 +26,11 @@
require "active_record"
require "active_support"
require "active_support/rails"
+
require "active_storage/version"
+require "active_storage/errors"
+
+require "marcel"
module ActiveStorage
extend ActiveSupport::Autoload
@@ -41,4 +45,7 @@ module ActiveStorage
mattr_accessor :queue
mattr_accessor :previewers, default: []
mattr_accessor :analyzers, default: []
+ mattr_accessor :paths, default: {}
+ mattr_accessor :variable_content_types, default: []
+ mattr_accessor :content_types_to_serve_as_binary, default: []
end
diff --git a/activestorage/lib/active_storage/analyzer.rb b/activestorage/lib/active_storage/analyzer.rb
index 837785a12b..7c4168c1a0 100644
--- a/activestorage/lib/active_storage/analyzer.rb
+++ b/activestorage/lib/active_storage/analyzer.rb
@@ -26,7 +26,7 @@ module ActiveStorage
end
private
- def logger
+ def logger #:doc:
ActiveStorage.logger
end
end
diff --git a/activestorage/lib/active_storage/analyzer/image_analyzer.rb b/activestorage/lib/active_storage/analyzer/image_analyzer.rb
index 25e0251e6e..3b39de91be 100644
--- a/activestorage/lib/active_storage/analyzer/image_analyzer.rb
+++ b/activestorage/lib/active_storage/analyzer/image_analyzer.rb
@@ -3,14 +3,15 @@
module ActiveStorage
# Extracts width and height in pixels from an image blob.
#
+ # If the image contains EXIF data indicating its angle is 90 or 270 degrees, its width and height are swapped for convenience.
+ #
# 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.
+ # the {ImageMagick}[http://www.imagemagick.org] system library.
class Analyzer::ImageAnalyzer < Analyzer
def self.accept?(blob)
blob.image?
@@ -18,7 +19,11 @@ module ActiveStorage
def metadata
read_image do |image|
- { width: image.width, height: image.height }
+ if rotated_image?(image)
+ { width: image.height, height: image.width }
+ else
+ { width: image.width, height: image.height }
+ end
end
rescue LoadError
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
@@ -32,5 +37,9 @@ module ActiveStorage
yield MiniMagick::Image.new(file.path)
end
end
+
+ def rotated_image?(image)
+ %w[ RightTop LeftBottom ].include?(image["%[orientation]"])
+ end
end
end
diff --git a/activestorage/lib/active_storage/analyzer/video_analyzer.rb b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
index 408b5e58e9..656e362187 100644
--- a/activestorage/lib/active_storage/analyzer/video_analyzer.rb
+++ b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
@@ -9,31 +9,40 @@ module ActiveStorage
# * Height (pixels)
# * Duration (seconds)
# * Angle (degrees)
- # * Aspect ratio
+ # * Display aspect ratio
#
# Example:
#
# ActiveStorage::VideoAnalyzer.new(blob).metadata
- # # => { width: 640, height: 480, duration: 5.0, angle: 0, aspect_ratio: [4, 3] }
+ # # => { width: 640.0, height: 480.0, duration: 5.0, angle: 0, display_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.
+ # When a video's angle is 90 or 270 degrees, its width and height are automatically swapped for convenience.
+ #
+ # This analyzer requires the {ffmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
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
+ { width: width, height: height, duration: duration, angle: angle, display_aspect_ratio: display_aspect_ratio }.compact
end
private
def width
- Integer(video_stream["width"]) if video_stream["width"]
+ if rotated?
+ computed_height || encoded_height
+ else
+ encoded_width
+ end
end
def height
- Integer(video_stream["height"]) if video_stream["height"]
+ if rotated?
+ encoded_width
+ else
+ computed_height || encoded_height
+ end
end
def duration
@@ -44,12 +53,40 @@ module ActiveStorage
Integer(tags["rotate"]) if tags["rotate"]
end
- def aspect_ratio
+ def display_aspect_ratio
if descriptor = video_stream["display_aspect_ratio"]
- descriptor.split(":", 2).collect(&:to_i)
+ if terms = descriptor.split(":", 2)
+ numerator = Integer(terms[0])
+ denominator = Integer(terms[1])
+
+ [numerator, denominator] unless numerator == 0
+ end
+ end
+ end
+
+
+ def rotated?
+ angle == 90 || angle == 270
+ end
+
+ def computed_height
+ if encoded_width && display_height_scale
+ encoded_width * display_height_scale
end
end
+ def encoded_width
+ @encoded_width ||= Float(video_stream["width"]) if video_stream["width"]
+ end
+
+ def encoded_height
+ @encoded_height ||= Float(video_stream["height"]) if video_stream["height"]
+ end
+
+ def display_height_scale
+ @display_height_scale ||= Float(display_aspect_ratio.last) / display_aspect_ratio.first if display_aspect_ratio
+ end
+
def tags
@tags ||= video_stream["tags"] || {}
@@ -68,12 +105,16 @@ module ActiveStorage
end
def probe_from(file)
- IO.popen([ "ffprobe", "-print_format", "json", "-show_streams", "-v", "error", file.path ]) do |output|
+ IO.popen([ ffprobe_path, "-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
+
+ def ffprobe_path
+ ActiveStorage.paths[:ffprobe] || "ffprobe"
+ end
end
end
diff --git a/activestorage/lib/active_storage/attached/macros.rb b/activestorage/lib/active_storage/attached/macros.rb
index f0256718ac..c51efa9d6b 100644
--- a/activestorage/lib/active_storage/attached/macros.rb
+++ b/activestorage/lib/active_storage/attached/macros.rb
@@ -32,15 +32,19 @@ module ActiveStorage
def #{name}
@active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"})
end
+
+ def #{name}=(attachable)
+ #{name}.attach(attachable)
+ end
CODE
- has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record
+ has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :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 }
+ after_destroy_commit { public_send(name).purge_later }
end
end
@@ -73,15 +77,19 @@ module ActiveStorage
def #{name}
@active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"})
end
+
+ def #{name}=(attachables)
+ #{name}.attach(attachables)
+ end
CODE
- has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment"
+ has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record
has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
if dependent == :purge_later
- before_destroy { public_send(name).purge_later }
+ after_destroy_commit { public_send(name).purge_later }
end
end
end
diff --git a/activestorage/lib/active_storage/attached/many.rb b/activestorage/lib/active_storage/attached/many.rb
index 1e0657c33c..6eace65b79 100644
--- a/activestorage/lib/active_storage/attached/many.rb
+++ b/activestorage/lib/active_storage/attached/many.rb
@@ -13,7 +13,6 @@ module ActiveStorage
end
# Associates one or several attachments with the current record, saving them to the database.
- # Examples:
#
# 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
@@ -21,7 +20,11 @@ module ActiveStorage
# document.images.attach([ first_blob, second_blob ])
def attach(*attachables)
attachables.flatten.collect do |attachable|
- attachments.create!(name: name, blob: create_blob_from(attachable))
+ if record.new_record?
+ attachments.build(record: record, blob: create_blob_from(attachable))
+ else
+ attachments.create!(record: record, blob: create_blob_from(attachable))
+ end
end
end
@@ -36,6 +39,11 @@ module ActiveStorage
attachments.any?
end
+ # Deletes associated attachments without purging them, leaving their respective blobs in place.
+ def detach
+ attachments.destroy_all if attached?
+ end
+
# Directly purges each associated attachment (i.e. destroys the blobs and
# attachments and deletes the files on the service).
def purge
diff --git a/activestorage/lib/active_storage/attached/one.rb b/activestorage/lib/active_storage/attached/one.rb
index dc19512484..0244232b2c 100644
--- a/activestorage/lib/active_storage/attached/one.rb
+++ b/activestorage/lib/active_storage/attached/one.rb
@@ -14,7 +14,6 @@ module ActiveStorage
end
# Associates a given attachment with the current record, saving it to the database.
- # Examples:
#
# person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
# person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
@@ -24,7 +23,7 @@ module ActiveStorage
if attached? && dependent == :purge_later
replace attachable
else
- write_attachment create_attachment_from(attachable)
+ write_attachment build_attachment_from(attachable)
end
end
@@ -39,6 +38,14 @@ module ActiveStorage
attachment.present?
end
+ # Deletes the attachment without purging it, leaving its blob in place.
+ def detach
+ if attached?
+ attachment.destroy
+ write_attachment nil
+ end
+ end
+
# Directly purges the attachment (i.e. destroys the blob and
# attachment and deletes the file on the service).
def purge
@@ -59,18 +66,14 @@ module ActiveStorage
def replace(attachable)
blob.tap do
transaction do
- destroy_attachment
- write_attachment create_attachment_from(attachable)
+ detach
+ write_attachment build_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))
+ def build_attachment_from(attachable)
+ ActiveStorage::Attachment.new(record: record, name: name, blob: create_blob_from(attachable))
end
def write_attachment(attachment)
diff --git a/activestorage/lib/active_storage/downloading.rb b/activestorage/lib/active_storage/downloading.rb
index 3dac6b116a..f2a1fffdcb 100644
--- a/activestorage/lib/active_storage/downloading.rb
+++ b/activestorage/lib/active_storage/downloading.rb
@@ -1,25 +1,37 @@
# frozen_string_literal: true
+require "tmpdir"
+
module ActiveStorage
module Downloading
private
# Opens a new tempfile in #tempdir and copies blob data into it. Yields the tempfile.
- def download_blob_to_tempfile # :doc:
- Tempfile.open("ActiveStorage", tempdir) do |file|
+ def download_blob_to_tempfile #:doc:
+ open_tempfile_for_blob do |file|
download_blob_to file
yield file
end
end
+ def open_tempfile_for_blob
+ tempfile = Tempfile.open([ "ActiveStorage", blob.filename.extension_with_delimiter ], tempdir)
+
+ begin
+ yield tempfile
+ ensure
+ tempfile.close!
+ end
+ end
+
# Efficiently downloads blob data into the given file.
- def download_blob_to(file) # :doc:
+ def download_blob_to(file) #:doc:
file.binmode
blob.download { |chunk| file.write(chunk) }
file.rewind
end
# Returns the directory in which tempfiles should be opened. Defaults to +Dir.tmpdir+.
- def tempdir # :doc:
+ def tempdir #:doc:
Dir.tmpdir
end
end
diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb
index 6cf6635c4f..8ba32490b1 100644
--- a/activestorage/lib/active_storage/engine.rb
+++ b/activestorage/lib/active_storage/engine.rb
@@ -15,7 +15,20 @@ module 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.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ]
+ config.active_storage.paths = ActiveSupport::OrderedOptions.new
+
+ config.active_storage.variable_content_types = %w( image/png image/gif image/jpg image/jpeg image/vnd.adobe.photoshop )
+ config.active_storage.content_types_to_serve_as_binary = %w(
+ text/html
+ text/javascript
+ image/svg+xml
+ application/postscript
+ application/x-shockwave-flash
+ text/xml
+ application/xml
+ application/xhtml+xml
+ )
config.eager_load_namespaces << ActiveStorage
@@ -25,6 +38,10 @@ module ActiveStorage
ActiveStorage.queue = app.config.active_storage.queue
ActiveStorage.previewers = app.config.active_storage.previewers || []
ActiveStorage.analyzers = app.config.active_storage.analyzers || []
+ ActiveStorage.paths = app.config.active_storage.paths || {}
+
+ ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
+ ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
end
end
diff --git a/activestorage/lib/active_storage/errors.rb b/activestorage/lib/active_storage/errors.rb
new file mode 100644
index 0000000000..f099b13f5b
--- /dev/null
+++ b/activestorage/lib/active_storage/errors.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class InvariableError < StandardError; end
+ class UnpreviewableError < StandardError; end
+ class UnrepresentableError < StandardError; end
+end
diff --git a/activestorage/lib/active_storage/gem_version.rb b/activestorage/lib/active_storage/gem_version.rb
index e1d7b3493a..492620731b 100644
--- a/activestorage/lib/active_storage/gem_version.rb
+++ b/activestorage/lib/active_storage/gem_version.rb
@@ -7,8 +7,8 @@ module ActiveStorage
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/activestorage/lib/active_storage/log_subscriber.rb b/activestorage/lib/active_storage/log_subscriber.rb
index 5cbf4bd1a5..a4e148c1a5 100644
--- a/activestorage/lib/active_storage/log_subscriber.rb
+++ b/activestorage/lib/active_storage/log_subscriber.rb
@@ -18,6 +18,10 @@ module ActiveStorage
info event, color("Deleted file from key: #{key_in(event)}", RED)
end
+ def service_delete_prefixed(event)
+ info event, color("Deleted files by key prefix: #{event.payload[:prefix]}", RED)
+ end
+
def service_exist(event)
debug event, color("Checked if file exists at key: #{key_in(event)} (#{event.payload[:exist] ? "yes" : "no"})", BLUE)
end
diff --git a/activestorage/lib/active_storage/previewer.rb b/activestorage/lib/active_storage/previewer.rb
index ed75bae3b5..cf19987d72 100644
--- a/activestorage/lib/active_storage/previewer.rb
+++ b/activestorage/lib/active_storage/previewer.rb
@@ -42,17 +42,33 @@ module ActiveStorage
# end
#
# The output tempfile is opened in the directory returned by ActiveStorage::Downloading#tempdir.
- def draw(*argv) # :doc:
- Tempfile.open("ActiveStorage", tempdir) do |file|
- capture(*argv, to: file)
- yield file
+ def draw(*argv) #:doc:
+ ActiveSupport::Notifications.instrument("preview.active_storage") do
+ open_tempfile_for_drawing do |file|
+ capture(*argv, to: file)
+ yield file
+ end
+ end
+ end
+
+ def open_tempfile_for_drawing
+ tempfile = Tempfile.open("ActiveStorage", tempdir)
+
+ begin
+ yield tempfile
+ ensure
+ tempfile.close!
end
end
def capture(*argv, to:)
to.binmode
- IO.popen(argv) { |out| IO.copy_stream(out, to) }
+ IO.popen(argv, err: File::NULL) { |out| IO.copy_stream(out, to) }
to.rewind
end
+
+ def logger #:doc:
+ ActiveStorage.logger
+ end
end
end
diff --git a/activestorage/lib/active_storage/previewer/pdf_previewer.rb b/activestorage/lib/active_storage/previewer/pdf_previewer.rb
index a2f05c74a6..426ff51eb1 100644
--- a/activestorage/lib/active_storage/previewer/pdf_previewer.rb
+++ b/activestorage/lib/active_storage/previewer/pdf_previewer.rb
@@ -8,10 +8,19 @@ module ActiveStorage
def preview
download_blob_to_tempfile do |input|
- draw "mutool", "draw", "-F", "png", "-o", "-", input.path, "1" do |output|
+ draw_first_page_from input do |output|
yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
end
end
end
+
+ private
+ def draw_first_page_from(file, &block)
+ draw mutool_path, "draw", "-F", "png", "-o", "-", file.path, "1", &block
+ end
+
+ def mutool_path
+ ActiveStorage.paths[:mutool] || "mutool"
+ end
end
end
diff --git a/activestorage/lib/active_storage/previewer/video_previewer.rb b/activestorage/lib/active_storage/previewer/video_previewer.rb
index 49f128d142..2f28a3d341 100644
--- a/activestorage/lib/active_storage/previewer/video_previewer.rb
+++ b/activestorage/lib/active_storage/previewer/video_previewer.rb
@@ -16,8 +16,12 @@ module ActiveStorage
private
def draw_relevant_frame_from(file, &block)
- draw "ffmpeg", "-i", file.path, "-y", "-vcodec", "png",
+ draw ffmpeg_path, "-i", file.path, "-y", "-vcodec", "png",
"-vf", "thumbnail", "-vframes", "1", "-f", "image2", "-", &block
end
+
+ def ffmpeg_path
+ ActiveStorage.paths[:ffmpeg] || "ffmpeg"
+ end
end
end
diff --git a/activestorage/lib/active_storage/service.rb b/activestorage/lib/active_storage/service.rb
index aa150e4d8a..f2e1269f27 100644
--- a/activestorage/lib/active_storage/service.rb
+++ b/activestorage/lib/active_storage/service.rb
@@ -78,6 +78,11 @@ module ActiveStorage
raise NotImplementedError
end
+ # Delete files at keys starting with the +prefix+.
+ def delete_prefixed(prefix)
+ raise NotImplementedError
+ end
+
# Return +true+ if a file exists at the +key+.
def exist?(key)
raise NotImplementedError
@@ -92,7 +97,7 @@ module ActiveStorage
# Returns a signed, temporary URL that a direct upload file can be PUT to on the +key+.
# The URL will be valid for the amount of seconds specified in +expires_in+.
- # You most also provide the +content_type+, +content_length+, and +checksum+ of the file
+ # You must also provide the +content_type+, +content_length+, and +checksum+ of the file
# that will be uploaded. All these attributes will be validated by the service upon upload.
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
raise NotImplementedError
@@ -104,10 +109,10 @@ module ActiveStorage
end
private
- def instrument(operation, key, payload = {}, &block)
+ def instrument(operation, payload = {}, &block)
ActiveSupport::Notifications.instrument(
"service_#{operation}.active_storage",
- payload.merge(key: key, service: service_name), &block)
+ payload.merge(service: service_name), &block)
end
def service_name
diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb
index f3877ad9c9..0a9eb7f23d 100644
--- a/activestorage/lib/active_storage/service/azure_storage_service.rb
+++ b/activestorage/lib/active_storage/service/azure_storage_service.rb
@@ -19,7 +19,7 @@ module ActiveStorage
end
def upload(key, io, checksum: nil)
- instrument :upload, key, checksum: checksum do
+ instrument :upload, key: key, checksum: checksum do
begin
blobs.create_block_blob(container, key, io, content_md5: checksum)
rescue Azure::Core::Http::HTTPError
@@ -30,11 +30,11 @@ module ActiveStorage
def download(key, &block)
if block_given?
- instrument :streaming_download, key do
+ instrument :streaming_download, key: key do
stream(key, &block)
end
else
- instrument :download, key do
+ instrument :download, key: key do
_, io = blobs.get_blob(container, key)
io.force_encoding(Encoding::BINARY)
end
@@ -42,17 +42,33 @@ module ActiveStorage
end
def delete(key)
- instrument :delete, key do
+ instrument :delete, key: key do
begin
blobs.delete_blob(container, key)
rescue Azure::Core::Http::HTTPError
- false
+ # Ignore files already deleted
+ end
+ end
+ end
+
+ def delete_prefixed(prefix)
+ instrument :delete_prefixed, prefix: prefix do
+ marker = nil
+
+ loop do
+ results = blobs.list_blobs(container, prefix: prefix, marker: marker)
+
+ results.each do |blob|
+ blobs.delete_blob(container, blob.name)
+ end
+
+ break unless marker = results.continuation_token.presence
end
end
end
def exist?(key)
- instrument :exist, key do |payload|
+ instrument :exist, key: key do |payload|
answer = blob_for(key).present?
payload[:exist] = answer
answer
@@ -60,7 +76,7 @@ module ActiveStorage
end
def url(key, expires_in:, filename:, disposition:, content_type:)
- instrument :url, key do |payload|
+ instrument :url, key: key do |payload|
base_url = url_for(key)
generated_url = signer.signed_uri(
URI(base_url), false,
@@ -77,7 +93,7 @@ module ActiveStorage
end
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
- instrument :url, key do |payload|
+ instrument :url, key: key do |payload|
base_url = url_for(key)
generated_url = signer.signed_uri(URI(base_url), false, permissions: "rw",
expiry: format_expiry(expires_in)).to_s
diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb
index 52eaba4e7b..d17eea9046 100644
--- a/activestorage/lib/active_storage/service/disk_service.rb
+++ b/activestorage/lib/active_storage/service/disk_service.rb
@@ -9,14 +9,14 @@ module ActiveStorage
# Wraps a local disk path as an Active Storage service. See ActiveStorage::Service for the generic API
# documentation that applies to all services.
class Service::DiskService < Service
- attr_reader :root
+ attr_reader :root, :host
- def initialize(root:)
- @root = root
+ def initialize(root:, host: "http://localhost:3000")
+ @root, @host = root, host
end
def upload(key, io, checksum: nil)
- instrument :upload, key, checksum: checksum do
+ instrument :upload, key: key, checksum: checksum do
IO.copy_stream(io, make_path_for(key))
ensure_integrity_of(key, checksum) if checksum
end
@@ -24,7 +24,7 @@ module ActiveStorage
def download(key)
if block_given?
- instrument :streaming_download, key do
+ instrument :streaming_download, key: key do
File.open(path_for(key), "rb") do |file|
while data = file.read(64.kilobytes)
yield data
@@ -32,14 +32,14 @@ module ActiveStorage
end
end
else
- instrument :download, key do
+ instrument :download, key: key do
File.binread path_for(key)
end
end
end
def delete(key)
- instrument :delete, key do
+ instrument :delete, key: key do
begin
File.delete path_for(key)
rescue Errno::ENOENT
@@ -48,8 +48,16 @@ module ActiveStorage
end
end
+ def delete_prefixed(prefix)
+ instrument :delete_prefixed, prefix: prefix do
+ Dir.glob(path_for("#{prefix}*")).each do |path|
+ FileUtils.rm_rf(path)
+ end
+ end
+ end
+
def exist?(key)
- instrument :exist, key do |payload|
+ instrument :exist, key: key do |payload|
answer = File.exist? path_for(key)
payload[:exist] = answer
answer
@@ -57,18 +65,17 @@ module ActiveStorage
end
def url(key, expires_in:, filename:, disposition:, content_type:)
- instrument :url, key do |payload|
+ instrument :url, key: key do |payload|
verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key)
generated_url =
- if defined?(Rails.application)
- Rails.application.routes.url_helpers.rails_disk_service_path \
- verified_key_with_expiration,
- 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=#{content_disposition_with(type: disposition, filename: filename)}"
- end
+ Rails.application.routes.url_helpers.rails_disk_service_url(
+ verified_key_with_expiration,
+ filename: filename,
+ disposition: content_disposition_with(type: disposition, filename: filename),
+ content_type: content_type,
+ host: host
+ )
payload[:url] = generated_url
@@ -77,7 +84,7 @@ module ActiveStorage
end
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
- instrument :url, key do |payload|
+ instrument :url, key: key do |payload|
verified_token_with_expiration = ActiveStorage.verifier.generate(
{
key: key,
@@ -89,12 +96,7 @@ module ActiveStorage
purpose: :blob_token }
)
- generated_url =
- if defined?(Rails.application)
- Rails.application.routes.url_helpers.update_rails_disk_service_path verified_token_with_expiration
- else
- "/rails/active_storage/disk/#{verified_token_with_expiration}"
- end
+ generated_url = Rails.application.routes.url_helpers.update_rails_disk_service_url(verified_token_with_expiration, host: host)
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 b4ffeeeb8a..6f6f4105fe 100644
--- a/activestorage/lib/active_storage/service/gcs_service.rb
+++ b/activestorage/lib/active_storage/service/gcs_service.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+gem "google-cloud-storage", "~> 1.8"
+
require "google/cloud/storage"
require "active_support/core_ext/object/to_query"
@@ -7,17 +9,20 @@ module ActiveStorage
# Wraps the Google Cloud Storage as an Active Storage service. See ActiveStorage::Service for the generic API
# documentation that applies to all services.
class Service::GCSService < Service
- attr_reader :client, :bucket
-
- def initialize(project:, keyfile:, bucket:, **options)
- @client = Google::Cloud::Storage.new(project: project, keyfile: keyfile, **options)
- @bucket = @client.bucket(bucket)
+ def initialize(**config)
+ @config = config
end
def upload(key, io, checksum: nil)
- instrument :upload, key, checksum: checksum do
+ instrument :upload, key: key, checksum: checksum do
begin
- bucket.create_file(io, key, md5: checksum)
+ # The official GCS client library doesn't allow us to create a file with no Content-Type metadata.
+ # We need the file we create to have no Content-Type so we can control it via the response-content-type
+ # param in signed URLs. Workaround: let the GCS client create the file with an inferred
+ # Content-Type (usually "application/octet-stream") then clear it.
+ bucket.create_file(io, key, md5: checksum).update do |file|
+ file.content_type = nil
+ end
rescue Google::Cloud::InvalidArgumentError
raise ActiveStorage::IntegrityError
end
@@ -26,7 +31,7 @@ module ActiveStorage
# FIXME: Download in chunks when given a block.
def download(key)
- instrument :download, key do
+ instrument :download, key: key do
io = file_for(key).download
io.rewind
@@ -39,7 +44,7 @@ module ActiveStorage
end
def delete(key)
- instrument :delete, key do
+ instrument :delete, key: key do
begin
file_for(key).delete
rescue Google::Cloud::NotFoundError
@@ -48,8 +53,14 @@ module ActiveStorage
end
end
+ def delete_prefixed(prefix)
+ instrument :delete_prefixed, prefix: prefix do
+ bucket.files(prefix: prefix).all(&:delete)
+ end
+ end
+
def exist?(key)
- instrument :exist, key do |payload|
+ instrument :exist, key: key do |payload|
answer = file_for(key).exists?
payload[:exist] = answer
answer
@@ -57,7 +68,7 @@ module ActiveStorage
end
def url(key, expires_in:, filename:, content_type:, disposition:)
- instrument :url, key do |payload|
+ instrument :url, key: key do |payload|
generated_url = file_for(key).signed_url expires: expires_in, query: {
"response-content-disposition" => content_disposition_with(type: disposition, filename: filename),
"response-content-type" => content_type
@@ -70,7 +81,7 @@ module ActiveStorage
end
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
- instrument :url, key do |payload|
+ instrument :url, key: key do |payload|
generated_url = bucket.signed_url key, method: "PUT", expires: expires_in,
content_type: content_type, content_md5: checksum
@@ -85,8 +96,18 @@ module ActiveStorage
end
private
+ attr_reader :config
+
def file_for(key)
bucket.file(key, skip_lookup: true)
end
+
+ def bucket
+ @bucket ||= client.bucket(config.fetch(:bucket))
+ end
+
+ def client
+ @client ||= Google::Cloud::Storage.new(config.except(:bucket))
+ end
end
end
diff --git a/activestorage/lib/active_storage/service/mirror_service.rb b/activestorage/lib/active_storage/service/mirror_service.rb
index 39e922f7ab..7eca8ce7f4 100644
--- a/activestorage/lib/active_storage/service/mirror_service.rb
+++ b/activestorage/lib/active_storage/service/mirror_service.rb
@@ -35,6 +35,11 @@ module ActiveStorage
perform_across_services :delete, key
end
+ # Delete files at keys starting with the +prefix+ on all services.
+ def delete_prefixed(prefix)
+ perform_across_services :delete_prefixed, prefix
+ end
+
private
def each_service(&block)
[ primary, *mirrors ].each(&block)
diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb
index 6957119780..c95672f338 100644
--- a/activestorage/lib/active_storage/service/s3_service.rb
+++ b/activestorage/lib/active_storage/service/s3_service.rb
@@ -17,7 +17,7 @@ module ActiveStorage
end
def upload(key, io, checksum: nil)
- instrument :upload, key, checksum: checksum do
+ instrument :upload, key: key, checksum: checksum do
begin
object_for(key).put(upload_options.merge(body: io, content_md5: checksum))
rescue Aws::S3::Errors::BadDigest
@@ -28,24 +28,30 @@ module ActiveStorage
def download(key, &block)
if block_given?
- instrument :streaming_download, key do
+ instrument :streaming_download, key: key do
stream(key, &block)
end
else
- instrument :download, key do
+ instrument :download, key: key do
object_for(key).get.body.read.force_encoding(Encoding::BINARY)
end
end
end
def delete(key)
- instrument :delete, key do
+ instrument :delete, key: key do
object_for(key).delete
end
end
+ def delete_prefixed(prefix)
+ instrument :delete_prefixed, prefix: prefix do
+ bucket.objects(prefix: prefix).batch_delete!
+ end
+ end
+
def exist?(key)
- instrument :exist, key do |payload|
+ instrument :exist, key: key do |payload|
answer = object_for(key).exists?
payload[:exist] = answer
answer
@@ -53,7 +59,7 @@ module ActiveStorage
end
def url(key, expires_in:, filename:, disposition:, content_type:)
- instrument :url, key do |payload|
+ instrument :url, key: key do |payload|
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
@@ -65,7 +71,7 @@ module ActiveStorage
end
def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
- instrument :url, key do |payload|
+ instrument :url, key: key do |payload|
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
diff --git a/activestorage/lib/tasks/activestorage.rake b/activestorage/lib/tasks/activestorage.rake
index ef923e5926..296e91afa1 100644
--- a/activestorage/lib/tasks/activestorage.rake
+++ b/activestorage/lib/tasks/activestorage.rake
@@ -3,6 +3,10 @@
namespace :active_storage do
desc "Copy over the migration needed to the application"
task install: :environment do
- Rake::Task["active_storage:install:migrations"].invoke
+ if Rake::Task.task_defined?("active_storage:install:migrations")
+ Rake::Task["active_storage:install:migrations"].invoke
+ else
+ Rake::Task["app:active_storage:install:migrations"].invoke
+ end
end
end
diff --git a/activestorage/package.json b/activestorage/package.json
index 8e6dd1c57f..d23f8b179e 100644
--- a/activestorage/package.json
+++ b/activestorage/package.json
@@ -1,10 +1,11 @@
{
"name": "activestorage",
- "version": "5.2.0-alpha",
+ "version": "6.0.0-alpha",
"description": "Attach cloud and local files in Rails applications",
"main": "app/assets/javascripts/activestorage.js",
"files": [
- "app/assets/javascripts/*.js"
+ "app/assets/javascripts/*.js",
+ "src/*.js"
],
"homepage": "http://rubyonrails.org/",
"repository": {
@@ -16,18 +17,21 @@
},
"author": "Javan Makhmali <javan@javan.us>",
"license": "MIT",
+ "dependencies": {
+ "spark-md5": "^3.0.0"
+ },
"devDependencies": {
"babel-core": "^6.25.0",
"babel-loader": "^7.1.1",
"babel-preset-env": "^1.6.0",
"eslint": "^4.3.0",
"eslint-plugin-import": "^2.7.0",
- "spark-md5": "^3.0.0",
"webpack": "^3.4.0"
},
"scripts": {
"prebuild": "yarn lint",
"build": "webpack -p",
- "lint": "eslint app/javascript"
+ "lint": "eslint app/javascript",
+ "prepublishOnly": "rm -rf src && cp -R app/javascript/activestorage src"
}
}
diff --git a/activestorage/test/analyzer/image_analyzer_test.rb b/activestorage/test/analyzer/image_analyzer_test.rb
index 9087072215..f8a38f001a 100644
--- a/activestorage/test/analyzer/image_analyzer_test.rb
+++ b/activestorage/test/analyzer/image_analyzer_test.rb
@@ -6,11 +6,32 @@ require "database/setup"
require "active_storage/analyzer/image_analyzer"
class ActiveStorage::Analyzer::ImageAnalyzerTest < ActiveSupport::TestCase
- test "analyzing an image" do
+ test "analyzing a JPEG image" do
blob = create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg")
- metadata = blob.tap(&:analyze).metadata
+ metadata = extract_metadata_from(blob)
assert_equal 4104, metadata[:width]
assert_equal 2736, metadata[:height]
end
+
+ test "analyzing a rotated JPEG image" do
+ blob = create_file_blob(filename: "racecar_rotated.jpg", content_type: "image/jpeg")
+ metadata = extract_metadata_from(blob)
+
+ assert_equal 2736, metadata[:width]
+ assert_equal 4104, metadata[:height]
+ end
+
+ test "analyzing an SVG image without an XML declaration" do
+ blob = create_file_blob(filename: "icon.svg", content_type: "image/svg+xml")
+ metadata = extract_metadata_from(blob)
+
+ assert_equal 792, metadata[:width]
+ assert_equal 584, metadata[:height]
+ end
+
+ private
+ def extract_metadata_from(blob)
+ blob.tap(&:analyze).metadata
+ end
end
diff --git a/activestorage/test/analyzer/video_analyzer_test.rb b/activestorage/test/analyzer/video_analyzer_test.rb
index 4a3c4a8bfc..2612006551 100644
--- a/activestorage/test/analyzer/video_analyzer_test.rb
+++ b/activestorage/test/analyzer/video_analyzer_test.rb
@@ -8,28 +8,52 @@ 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
+ metadata = extract_metadata_from(blob)
assert_equal 640, metadata[:width]
assert_equal 480, metadata[:height]
- assert_equal [4, 3], metadata[:aspect_ratio]
+ assert_equal [4, 3], metadata[:display_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
+ metadata = extract_metadata_from(blob)
- assert_equal 640, metadata[:width]
- assert_equal 480, metadata[:height]
- assert_equal [4, 3], metadata[:aspect_ratio]
+ assert_equal 480, metadata[:width]
+ assert_equal 640, metadata[:height]
+ assert_equal [4, 3], metadata[:display_aspect_ratio]
assert_equal 5.227975, metadata[:duration]
assert_equal 90, metadata[:angle]
end
+ test "analyzing a video with rectangular samples" do
+ blob = create_file_blob(filename: "video_with_rectangular_samples.mp4", content_type: "video/mp4")
+ metadata = extract_metadata_from(blob)
+
+ assert_equal 1280, metadata[:width]
+ assert_equal 720, metadata[:height]
+ assert_equal [16, 9], metadata[:display_aspect_ratio]
+ end
+
+ test "analyzing a video with an undefined display aspect ratio" do
+ blob = create_file_blob(filename: "video_with_undefined_display_aspect_ratio.mp4", content_type: "video/mp4")
+ metadata = extract_metadata_from(blob)
+
+ assert_equal 640, metadata[:width]
+ assert_equal 480, metadata[:height]
+ assert_nil metadata[:display_aspect_ratio]
+ 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)
+ metadata = extract_metadata_from(blob)
+ assert_equal({ "analyzed" => true, "identified" => true }, metadata)
end
+
+ private
+ def extract_metadata_from(blob)
+ 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 97177e64c2..9c811df895 100644
--- a/activestorage/test/controllers/blobs_controller_test.rb
+++ b/activestorage/test/controllers/blobs_controller_test.rb
@@ -8,6 +8,11 @@ class ActiveStorage::BlobsControllerTest < ActionDispatch::IntegrationTest
@blob = create_file_blob filename: "racecar.jpg"
end
+ test "showing blob with invalid signed ID" do
+ get rails_service_blob_url("invalid", "racecar.jpg")
+ assert_response :not_found
+ end
+
test "showing blob utilizes browser caching" do
get rails_blob_url(@blob)
diff --git a/activestorage/test/controllers/previews_controller_test.rb b/activestorage/test/controllers/previews_controller_test.rb
index c3151a710e..b87be6c8b2 100644
--- a/activestorage/test/controllers/previews_controller_test.rb
+++ b/activestorage/test/controllers/previews_controller_test.rb
@@ -14,11 +14,20 @@ class ActiveStorage::PreviewsControllerTest < ActionDispatch::IntegrationTest
signed_blob_id: @blob.signed_id,
variation_key: ActiveStorage::Variation.encode(resize: "100x100"))
- assert @blob.preview_image.attached?
+ assert_predicate @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
+
+ test "showing preview with invalid signed blob ID" do
+ get rails_blob_preview_url(
+ filename: @blob.filename,
+ signed_blob_id: "invalid",
+ variation_key: ActiveStorage::Variation.encode(resize: "100x100"))
+
+ assert_response :not_found
+ end
end
diff --git a/activestorage/test/controllers/variants_controller_test.rb b/activestorage/test/controllers/variants_controller_test.rb
index 6c70d73786..a0642f9bed 100644
--- a/activestorage/test/controllers/variants_controller_test.rb
+++ b/activestorage/test/controllers/variants_controller_test.rb
@@ -20,4 +20,13 @@ class ActiveStorage::VariantsControllerTest < ActionDispatch::IntegrationTest
assert_equal 100, image.width
assert_equal 67, image.height
end
+
+ test "showing variant with invalid signed blob ID" do
+ get rails_blob_variation_url(
+ filename: @blob.filename,
+ signed_blob_id: "invalid",
+ variation_key: ActiveStorage::Variation.encode(resize: "100x100"))
+
+ assert_response :not_found
+ end
end
diff --git a/activestorage/test/database/setup.rb b/activestorage/test/database/setup.rb
index 705650a25d..daeeb5695b 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", __dir__)
+ActiveRecord::Base.connection.migration_context.migrate
ActiveStorageCreateUsers.migrate(:up)
diff --git a/activestorage/test/dummy/config/application.rb b/activestorage/test/dummy/config/application.rb
index 7ee6625bb5..bd14ac0b1a 100644
--- a/activestorage/test/dummy/config/application.rb
+++ b/activestorage/test/dummy/config/application.rb
@@ -10,16 +10,12 @@ require "action_controller/railtie"
require "action_view/railtie"
require "sprockets/railtie"
require "active_storage/engine"
-#require "action_mailer/railtie"
-#require "rails/test_unit/railtie"
-#require "action_cable/engine"
-
Bundler.require(*Rails.groups)
module Dummy
class Application < Rails::Application
- config.load_defaults 5.1
+ config.load_defaults 5.2
config.active_storage.service = :local
end
diff --git a/activestorage/test/dummy/config/environments/test.rb b/activestorage/test/dummy/config/environments/test.rb
index ce0889e8ae..74a802d98c 100644
--- a/activestorage/test/dummy/config/environments/test.rb
+++ b/activestorage/test/dummy/config/environments/test.rb
@@ -30,6 +30,9 @@ Rails.application.configure do
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
+ # Disable request forgery protection in test environment.
+ config.action_controller.allow_forgery_protection = false
+
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
end
diff --git a/activestorage/test/fixtures/files/icon.psd b/activestorage/test/fixtures/files/icon.psd
new file mode 100644
index 0000000000..631fceeaab
--- /dev/null
+++ b/activestorage/test/fixtures/files/icon.psd
Binary files differ
diff --git a/activestorage/test/fixtures/files/icon.svg b/activestorage/test/fixtures/files/icon.svg
new file mode 100644
index 0000000000..6cfb0e241e
--- /dev/null
+++ b/activestorage/test/fixtures/files/icon.svg
@@ -0,0 +1,13 @@
+<!-- The XML declaration is intentionally omitted. -->
+<svg width="792px" height="584px" viewBox="0 0 792 584" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <path d="M9.51802657,28.724593 C9.51802657,18.2155955 18.1343454,9.60822622 28.6542694,9.60822622 L763.245541,9.60822622 C773.765465,9.60822622 782.381784,18.2155955 782.381784,28.724593 L782.381784,584 L792,584 L792,28.724593 C792,12.911054 779.075522,0 763.245541,0 L28.7544592,0 C12.9244782,0 0,12.911054 0,28.724593 L0,584 L9.61821632,584 C9.51802657,584 9.51802657,28.724593 9.51802657,28.724593 L9.51802657,28.724593 Z" id="Shape" opacity="0.3" fill="#CCCCCC"></path>
+ <circle id="Oval" fill="#FFCC33" cx="119.1" cy="147.2" r="33"></circle>
+ <circle id="Oval" fill="#3399FF" cx="119.1" cy="281.1" r="33"></circle>
+ <circle id="Oval" fill="#FF3333" cx="676.1" cy="376.8" r="33"></circle>
+ <circle id="Oval" fill="#FFCC33" cx="119.1" cy="477.2" r="33"></circle>
+ <rect id="Rectangle-path" opacity="0.75" fill="#CCCCCC" x="176.5" y="130.5" width="442.1" height="33.5"></rect>
+ <rect id="Rectangle-path" opacity="0.75" fill="#CCCCCC" x="176.5" y="183.1" width="442.1" height="33.5"></rect>
+ <rect id="Rectangle-path" opacity="0.75" fill="#CCCCCC" x="176.5" y="265.8" width="442.1" height="33.5"></rect>
+ <rect id="Rectangle-path" opacity="0.75" fill="#CCCCCC" x="176.5" y="363.4" width="442.1" height="33.5"></rect>
+ <rect id="Rectangle-path" opacity="0.75" fill="#CCCCCC" x="176.5" y="465.3" width="442.1" height="33.5"></rect>
+</svg>
diff --git a/activestorage/test/fixtures/files/image.gif b/activestorage/test/fixtures/files/image.gif
new file mode 100644
index 0000000000..90c05f671c
--- /dev/null
+++ b/activestorage/test/fixtures/files/image.gif
Binary files differ
diff --git a/activestorage/test/fixtures/files/racecar_rotated.jpg b/activestorage/test/fixtures/files/racecar_rotated.jpg
new file mode 100644
index 0000000000..89e6d54f98
--- /dev/null
+++ b/activestorage/test/fixtures/files/racecar_rotated.jpg
Binary files differ
diff --git a/activestorage/test/fixtures/files/video_with_rectangular_samples.mp4 b/activestorage/test/fixtures/files/video_with_rectangular_samples.mp4
new file mode 100644
index 0000000000..12b04afc87
--- /dev/null
+++ b/activestorage/test/fixtures/files/video_with_rectangular_samples.mp4
Binary files differ
diff --git a/activestorage/test/fixtures/files/video_with_undefined_display_aspect_ratio.mp4 b/activestorage/test/fixtures/files/video_with_undefined_display_aspect_ratio.mp4
new file mode 100644
index 0000000000..eb354e756f
--- /dev/null
+++ b/activestorage/test/fixtures/files/video_with_undefined_display_aspect_ratio.mp4
Binary files differ
diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb
index 47f2bd7911..9ba2759893 100644
--- a/activestorage/test/models/attachments_test.rb
+++ b/activestorage/test/models/attachments_test.rb
@@ -27,7 +27,7 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
test "attach new blob from an UploadedFile" do
file = file_fixture "racecar.jpg"
- @user.avatar.attach Rack::Test::UploadedFile.new file
+ @user.avatar.attach Rack::Test::UploadedFile.new file.to_s
assert_equal "racecar.jpg", @user.avatar.filename.to_s
end
@@ -56,6 +56,40 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
assert ActiveStorage::Blob.service.exist?(@user.avatar.key)
end
+ test "attach blob to new record" do
+ user = User.new(name: "Jason")
+
+ assert_no_changes -> { user.new_record? } do
+ assert_no_difference -> { ActiveStorage::Attachment.count } do
+ user.avatar.attach create_blob(filename: "funky.jpg")
+ end
+ end
+
+ assert_predicate user.avatar, :attached?
+ assert_equal "funky.jpg", user.avatar.filename.to_s
+
+ assert_difference -> { ActiveStorage::Attachment.count }, +1 do
+ user.save!
+ end
+
+ assert_predicate user.reload.avatar, :attached?
+ assert_equal "funky.jpg", user.avatar.filename.to_s
+ end
+
+ test "build new record with attached blob" do
+ assert_no_difference -> { ActiveStorage::Attachment.count } do
+ @user = User.new(name: "Jason", avatar: { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" })
+ end
+
+ assert_predicate @user, :new_record?
+ assert_predicate @user.avatar, :attached?
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+
+ @user.save!
+ assert_predicate @user.reload.avatar, :attached?
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+ end
+
test "access underlying associations of new blob" do
@user.avatar.attach create_blob(filename: "funky.jpg")
assert_equal @user, @user.avatar_attachment.record
@@ -63,6 +97,29 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
assert_equal "funky.jpg", @user.avatar_attachment.blob.filename.to_s
end
+ test "identify newly-attached, directly-uploaded blob" do
+ # Simulate a direct upload.
+ blob = create_blob_before_direct_upload(filename: "racecar.jpg", content_type: "application/octet-stream", byte_size: 1124062, checksum: "7GjDDNEQb4mzMzsW+MS0JQ==")
+ ActiveStorage::Blob.service.upload(blob.key, file_fixture("racecar.jpg").open)
+
+ stub_request(:get, %r{localhost:3000/rails/active_storage/disk/.*}).to_return(body: file_fixture("racecar.jpg"))
+ @user.avatar.attach(blob)
+
+ assert_equal "image/jpeg", @user.avatar.reload.content_type
+ assert_predicate @user.avatar, :identified?
+ end
+
+ test "identify newly-attached blob only once" do
+ blob = create_file_blob
+ assert_predicate blob, :identified?
+
+ # The blob's backing file is a PNG image. Fudge its content type so we can tell if it's identified when we attach it.
+ blob.update! content_type: "application/octet-stream"
+
+ @user.avatar.attach blob
+ assert_equal "application/octet-stream", blob.content_type
+ end
+
test "analyze newly-attached blob" do
perform_enqueued_jobs do
@user.avatar.attach create_file_blob
@@ -79,21 +136,42 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
@user.avatar.attach blob
end
- assert blob.reload.analyzed?
+ assert_predicate blob.reload, :analyzed?
- @user.avatar.attachment.destroy
+ @user.avatar.detach
assert_no_enqueued_jobs do
@user.reload.avatar.attach blob
end
end
+ test "preserve existing metadata when analyzing a newly-attached blob" do
+ blob = create_file_blob(metadata: { foo: "bar" })
+
+ perform_enqueued_jobs do
+ @user.avatar.attach blob
+ end
+
+ assert_equal "bar", blob.reload.metadata[:foo]
+ end
+
+ test "detach blob" do
+ @user.avatar.attach create_blob(filename: "funky.jpg")
+ avatar_blob_id = @user.avatar.blob.id
+ avatar_key = @user.avatar.key
+
+ @user.avatar.detach
+ assert_not_predicate @user.avatar, :attached?
+ assert ActiveStorage::Blob.exists?(avatar_blob_id)
+ assert ActiveStorage::Blob.service.exist?(avatar_key)
+ end
+
test "purge attached blob" do
@user.avatar.attach create_blob(filename: "funky.jpg")
avatar_key = @user.avatar.key
@user.avatar.purge
- assert_not @user.avatar.attached?
+ assert_not_predicate @user.avatar, :attached?
assert_not ActiveStorage::Blob.service.exist?(avatar_key)
end
@@ -139,6 +217,48 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
assert_equal "country.jpg", @user.highlights.second.filename.to_s
end
+ test "attach blobs to new record" do
+ user = User.new(name: "Jason")
+
+ assert_no_changes -> { user.new_record? } do
+ assert_no_difference -> { ActiveStorage::Attachment.count } do
+ user.highlights.attach(
+ { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
+ { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" })
+ end
+ end
+
+ assert_predicate user.highlights, :attached?
+ assert_equal "town.jpg", user.highlights.first.filename.to_s
+ assert_equal "country.jpg", user.highlights.second.filename.to_s
+
+ assert_difference -> { ActiveStorage::Attachment.count }, +2 do
+ user.save!
+ end
+
+ assert_predicate user.reload.highlights, :attached?
+ assert_equal "town.jpg", user.highlights.first.filename.to_s
+ assert_equal "country.jpg", user.highlights.second.filename.to_s
+ end
+
+ test "build new record with attached blobs" do
+ assert_no_difference -> { ActiveStorage::Attachment.count } do
+ @user = User.new(name: "Jason", highlights: [
+ { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
+ { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }])
+ end
+
+ assert_predicate @user, :new_record?
+ assert_predicate @user.highlights, :attached?
+ assert_equal "town.jpg", @user.highlights.first.filename.to_s
+ assert_equal "country.jpg", @user.highlights.second.filename.to_s
+
+ @user.save!
+ assert_predicate @user.reload.highlights, :attached?
+ assert_equal "town.jpg", @user.highlights.first.filename.to_s
+ assert_equal "country.jpg", @user.highlights.second.filename.to_s
+ end
+
test "find attached blobs" do
@user.highlights.attach(
{ io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
@@ -193,12 +313,42 @@ class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
end
end
+ test "preserve existing metadata when analyzing newly-attached blobs" do
+ blobs = [
+ create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", metadata: { foo: "bar" }),
+ create_file_blob(filename: "video.mp4", content_type: "video/mp4", metadata: { foo: "bar" })
+ ]
+
+ perform_enqueued_jobs do
+ @user.highlights.attach(blobs)
+ end
+
+ blobs.each do |blob|
+ assert_equal "bar", blob.reload.metadata[:foo]
+ end
+ end
+
+ test "detach blobs" do
+ @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
+ highlight_blob_ids = @user.highlights.collect { |highlight| highlight.blob.id }
+ highlight_keys = @user.highlights.collect(&:key)
+
+ @user.highlights.detach
+ assert_not_predicate @user.highlights, :attached?
+
+ assert ActiveStorage::Blob.exists?(highlight_blob_ids.first)
+ assert ActiveStorage::Blob.exists?(highlight_blob_ids.second)
+
+ assert ActiveStorage::Blob.service.exist?(highlight_keys.first)
+ assert ActiveStorage::Blob.service.exist?(highlight_keys.second)
+ end
+
test "purge attached blobs" do
@user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
highlight_keys = @user.highlights.collect(&:key)
@user.highlights.purge
- assert_not @user.highlights.attached?
+ assert_not_predicate @user.highlights, :attached?
assert_not ActiveStorage::Blob.service.exist?(highlight_keys.first)
assert_not ActiveStorage::Blob.service.exist?(highlight_keys.second)
end
diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb
index 6e815997ba..9b555f9a1d 100644
--- a/activestorage/test/models/blob_test.rb
+++ b/activestorage/test/models/blob_test.rb
@@ -2,8 +2,11 @@
require "test_helper"
require "database/setup"
+require "active_support/testing/method_call_assertions"
class ActiveStorage::BlobTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+
test "create after upload sets byte size and checksum" do
data = "Hello world!"
blob = create_blob data: data
@@ -13,10 +16,32 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
assert_equal Digest::MD5.base64digest(data), blob.checksum
end
+ test "create after upload extracts content type from data" do
+ blob = create_file_blob content_type: "application/octet-stream"
+ assert_equal "image/jpeg", blob.content_type
+ end
+
+ test "create after upload extracts content type from filename" do
+ blob = create_blob content_type: "application/octet-stream"
+ assert_equal "text/plain", blob.content_type
+ end
+
+ test "image?" do
+ blob = create_file_blob filename: "racecar.jpg"
+ assert_predicate blob, :image?
+ assert_not_predicate blob, :audio?
+ end
+
+ test "video?" do
+ blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4")
+ assert_predicate blob, :video?
+ assert_not_predicate blob, :audio?
+ end
+
test "text?" do
blob = create_blob data: "Hello world!"
- assert blob.text?
- assert_not blob.audio?
+ assert_predicate blob, :text?
+ assert_not_predicate blob, :audio?
end
test "download yields chunks" do
@@ -41,16 +66,63 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
end
end
- test "purge removes from external service" do
+ test "urls force attachment as content disposition for content types served as binary" do
+ blob = create_blob(content_type: "text/html")
+
+ freeze_time do
+ assert_equal expected_url_for(blob, disposition: :attachment), blob.service_url
+ assert_equal expected_url_for(blob, disposition: :attachment), blob.service_url(disposition: :inline)
+ end
+ end
+
+ test "urls allow for custom filename" do
+ blob = create_blob(filename: "original.txt")
+ new_filename = ActiveStorage::Filename.new("new.txt")
+
+ freeze_time do
+ assert_equal expected_url_for(blob), blob.service_url
+ assert_equal expected_url_for(blob, filename: new_filename), blob.service_url(filename: new_filename)
+ assert_equal expected_url_for(blob, filename: new_filename), blob.service_url(filename: "new.txt")
+ assert_equal expected_url_for(blob, filename: blob.filename), blob.service_url(filename: nil)
+ end
+ end
+
+ test "urls allow for custom options" do
+ blob = create_blob(filename: "original.txt")
+
+ options = [
+ blob.key,
+ expires_in: blob.service.url_expires_in,
+ disposition: :inline,
+ content_type: blob.content_type,
+ filename: blob.filename,
+ thumb_size: "300x300",
+ thumb_mode: "crop"
+ ]
+ assert_called_with(blob.service, :url, options) do
+ blob.service_url(thumb_size: "300x300", thumb_mode: "crop")
+ end
+ end
+
+ test "purge deletes file from external service" do
blob = create_blob
blob.purge
assert_not ActiveStorage::Blob.service.exist?(blob.key)
end
+ test "purge deletes variants from external service" do
+ blob = create_file_blob
+ variant = blob.variant(resize: "100>").processed
+
+ blob.purge
+ assert_not ActiveStorage::Blob.service.exist?(variant.key)
+ end
+
private
- def expected_url_for(blob, disposition: :inline)
- query_string = { content_type: blob.content_type, disposition: "#{disposition}; #{blob.filename.parameters}" }.to_param
- "/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{blob.filename}?#{query_string}"
+ def expected_url_for(blob, disposition: :inline, filename: nil)
+ filename ||= blob.filename
+ query_string = { content_type: blob.content_type, disposition: "#{disposition}; #{filename.parameters}" }.to_param
+ "http://localhost:3000/rails/active_storage/disk/#{ActiveStorage.verifier.generate(blob.key, expires_in: 5.minutes, purpose: :blob_key)}/#{filename}?#{query_string}"
end
end
diff --git a/activestorage/test/models/preview_test.rb b/activestorage/test/models/preview_test.rb
index bcd8442f4b..55fdc228c8 100644
--- a/activestorage/test/models/preview_test.rb
+++ b/activestorage/test/models/preview_test.rb
@@ -8,7 +8,7 @@ class ActiveStorage::PreviewTest < ActiveSupport::TestCase
blob = create_file_blob(filename: "report.pdf", content_type: "application/pdf")
preview = blob.preview(resize: "640x280").processed
- assert preview.image.attached?
+ assert_predicate preview.image, :attached?
assert_equal "report.png", preview.image.filename.to_s
assert_equal "image/png", preview.image.content_type
@@ -21,7 +21,7 @@ class ActiveStorage::PreviewTest < ActiveSupport::TestCase
blob = create_file_blob(filename: "video.mp4", content_type: "video/mp4")
preview = blob.preview(resize: "640x280").processed
- assert preview.image.attached?
+ assert_predicate preview.image, :attached?
assert_equal "video.png", preview.image.filename.to_s
assert_equal "image/png", preview.image.content_type
@@ -33,7 +33,7 @@ class ActiveStorage::PreviewTest < ActiveSupport::TestCase
test "previewing an unpreviewable blob" do
blob = create_file_blob
- assert_raises ActiveStorage::Blob::UnpreviewableError do
+ assert_raises ActiveStorage::UnpreviewableError do
blob.preview resize: "640x280"
end
end
diff --git a/activestorage/test/models/representation_test.rb b/activestorage/test/models/representation_test.rb
index 29fe61aee4..2a06b31c77 100644
--- a/activestorage/test/models/representation_test.rb
+++ b/activestorage/test/models/representation_test.rb
@@ -34,7 +34,7 @@ class ActiveStorage::RepresentationTest < ActiveSupport::TestCase
test "representing an unrepresentable blob" do
blob = create_blob
- assert_raises ActiveStorage::Blob::UnrepresentableError do
+ assert_raises ActiveStorage::UnrepresentableError do
blob.representation resize: "100x100"
end
end
diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb
index b7d20ab55a..0cf8a583bd 100644
--- a/activestorage/test/models/variant_test.rb
+++ b/activestorage/test/models/variant_test.rb
@@ -4,12 +4,9 @@ require "test_helper"
require "database/setup"
class ActiveStorage::VariantTest < ActiveSupport::TestCase
- setup do
- @blob = create_file_blob filename: "racecar.jpg"
- end
-
- test "resized variation" do
- variant = @blob.variant(resize: "100x100").processed
+ test "resized variation of JPEG blob" do
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = blob.variant(resize: "100x100").processed
assert_match(/racecar\.jpg/, variant.service_url)
image = read_image(variant)
@@ -17,8 +14,9 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
assert_equal 67, image.height
end
- test "resized and monochrome variation" do
- variant = @blob.variant(resize: "100x100", monochrome: true).processed
+ test "resized and monochrome variation of JPEG blob" do
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = blob.variant(resize: "100x100", monochrome: true).processed
assert_match(/racecar\.jpg/, variant.service_url)
image = read_image(variant)
@@ -27,8 +25,48 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
assert_match(/Gray/, image.colorspace)
end
+ test "center-weighted crop of JPEG blob" do
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = blob.variant(combine_options: {
+ gravity: "center",
+ resize: "100x100^",
+ crop: "100x100+0+0",
+ }).processed
+ assert_match(/racecar\.jpg/, variant.service_url)
+
+ image = read_image(variant)
+ assert_equal 100, image.width
+ assert_equal 100, image.height
+ end
+
+ test "resized variation of PSD blob" do
+ blob = create_file_blob(filename: "icon.psd", content_type: "image/vnd.adobe.photoshop")
+ variant = blob.variant(resize: "20x20").processed
+ assert_match(/icon\.png/, variant.service_url)
+
+ image = read_image(variant)
+ assert_equal "PNG", image.type
+ assert_equal 20, image.width
+ assert_equal 20, image.height
+ end
+
+ test "optimized variation of GIF blob" do
+ blob = create_file_blob(filename: "image.gif", content_type: "image/gif")
+
+ assert_nothing_raised do
+ blob.variant(layers: "Optimize").processed
+ end
+ end
+
+ test "variation of invariable blob" do
+ assert_raises ActiveStorage::InvariableError do
+ create_file_blob(filename: "report.pdf", content_type: "application/pdf").variant(resize: "100x100")
+ end
+ 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
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = blob.variant(font: "a" * 10_000).processed
+ assert_operator variant.service_url.length, :<, 525
end
end
diff --git a/activestorage/test/service/configurations.example.yml b/activestorage/test/service/configurations.example.yml
index 56ed37be5d..43cc013bc8 100644
--- a/activestorage/test/service/configurations.example.yml
+++ b/activestorage/test/service/configurations.example.yml
@@ -7,7 +7,7 @@
#
# gcs:
# service: GCS
-# keyfile: {
+# credentials: {
# type: "service_account",
# project_id: "",
# private_key_id: "",
diff --git a/activestorage/test/service/configurator_test.rb b/activestorage/test/service/configurator_test.rb
index a2fd035e02..fe8a637ad0 100644
--- a/activestorage/test/service/configurator_test.rb
+++ b/activestorage/test/service/configurator_test.rb
@@ -5,7 +5,10 @@ require "service/shared_service_tests"
class ActiveStorage::Service::ConfiguratorTest < ActiveSupport::TestCase
test "builds correct service instance based on service name" do
service = ActiveStorage::Service::Configurator.build(:foo, foo: { service: "Disk", root: "path" })
+
assert_instance_of ActiveStorage::Service::DiskService, service
+ assert_equal "path", service.root
+ assert_equal "http://localhost:3000", service.host
end
test "raises error when passing non-existent service name" do
diff --git a/activestorage/test/service/gcs_service_test.rb b/activestorage/test/service/gcs_service_test.rb
index 5566c664a9..7efcd60fb7 100644
--- a/activestorage/test/service/gcs_service_test.rb
+++ b/activestorage/test/service/gcs_service_test.rb
@@ -32,12 +32,21 @@ if SERVICE_CONFIGURATIONS[:gcs]
end
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%3B+filename%2A%3DUTF-8%27%27test.txt" +
- "&response-content-type=text%2Fplain"
+ assert_match(/storage\.googleapis\.com\/.*response-content-disposition=inline.*test\.txt.*response-content-type=text%2Fplain/,
+ @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain"))
+ end
+
+ test "signed URL response headers" do
+ begin
+ key = SecureRandom.base58(24)
+ data = "Something else entirely!"
+ @service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data))
- assert_equal url, @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
+ url = @service.url(key, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
+ response = Net::HTTP.get_response(URI(url))
+ assert_equal "text/plain", response.header["Content-Type"]
+ ensure
+ @service.delete key
end
end
end
diff --git a/activestorage/test/service/mirror_service_test.rb b/activestorage/test/service/mirror_service_test.rb
index 92101b1282..87306644c5 100644
--- a/activestorage/test/service/mirror_service_test.rb
+++ b/activestorage/test/service/mirror_service_test.rb
@@ -10,8 +10,8 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
end.to_h
config = mirror_config.merge \
- mirror: { service: "Mirror", primary: "primary", mirrors: mirror_config.keys },
- primary: { service: "Disk", root: Dir.mktmpdir("active_storage_tests_primary") }
+ mirror: { service: "Mirror", primary: "primary", mirrors: mirror_config.keys },
+ primary: { service: "Disk", root: Dir.mktmpdir("active_storage_tests_primary") }
SERVICE = ActiveStorage::Service.configure :mirror, config
@@ -19,11 +19,16 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
test "uploading to all services" do
begin
- data = "Something else entirely!"
- key = upload(data, to: @service)
+ key = SecureRandom.base58(24)
+ data = "Something else entirely!"
+ io = StringIO.new(data)
+ checksum = Digest::MD5.base64digest(data)
- assert_equal data, SERVICE.primary.download(key)
- SERVICE.mirrors.each do |mirror|
+ @service.upload key, io.tap(&:read), checksum: checksum
+ assert_predicate io, :eof?
+
+ assert_equal data, @service.primary.download(key)
+ @service.mirrors.each do |mirror|
assert_equal data, mirror.download(key)
end
ensure
@@ -32,14 +37,18 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
end
test "downloading from primary service" do
- data = "Something else entirely!"
- key = upload(data, to: SERVICE.primary)
+ key = SecureRandom.base58(24)
+ data = "Something else entirely!"
+ checksum = Digest::MD5.base64digest(data)
+
+ @service.primary.upload key, StringIO.new(data), checksum: checksum
assert_equal data, @service.download(key)
end
test "deleting from all services" do
@service.delete FIXTURE_KEY
+
assert_not SERVICE.primary.exist?(FIXTURE_KEY)
SERVICE.mirrors.each do |mirror|
assert_not mirror.exist?(FIXTURE_KEY)
@@ -50,17 +59,8 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
filename = ActiveStorage::Filename.new("test.txt")
freeze_time do
- assert_equal SERVICE.primary.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: filename, 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
-
- private
- def upload(data, to:)
- SecureRandom.base58(24).tap do |key|
- io = StringIO.new(data).tap(&:read)
- @service.upload key, io, checksum: Digest::MD5.base64digest(data)
- assert io.eof?
- end
- end
end
diff --git a/activestorage/test/service/shared_service_tests.rb b/activestorage/test/service/shared_service_tests.rb
index ade91ab89a..ce28c4393a 100644
--- a/activestorage/test/service/shared_service_tests.rb
+++ b/activestorage/test/service/shared_service_tests.rb
@@ -75,5 +75,22 @@ module ActiveStorage::Service::SharedServiceTests
@service.delete SecureRandom.base58(24)
end
end
+
+ test "deleting by prefix" do
+ begin
+ @service.upload("a/a/a", StringIO.new(FIXTURE_DATA))
+ @service.upload("a/a/b", StringIO.new(FIXTURE_DATA))
+ @service.upload("a/b/a", StringIO.new(FIXTURE_DATA))
+
+ @service.delete_prefixed("a/a/")
+ assert_not @service.exist?("a/a/a")
+ assert_not @service.exist?("a/a/b")
+ assert @service.exist?("a/b/a")
+ ensure
+ @service.delete("a/a/a")
+ @service.delete("a/a/b")
+ @service.delete("a/b/a")
+ end
+ end
end
end
diff --git a/activestorage/test/template/image_tag_test.rb b/activestorage/test/template/image_tag_test.rb
index 80f183e0e3..f0b166c225 100644
--- a/activestorage/test/template/image_tag_test.rb
+++ b/activestorage/test/template/image_tag_test.rb
@@ -33,7 +33,7 @@ class ActiveStorage::ImageTagTest < ActionView::TestCase
test "error when attachment's empty" do
@user = User.create!(name: "DHH")
- assert_not @user.avatar.attached?
+ assert_not_predicate @user.avatar, :attached?
assert_raises(ArgumentError) { image_tag(@user.avatar) }
end
diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb
index 38408cdad3..98fa44a604 100644
--- a/activestorage/test/test_helper.rb
+++ b/activestorage/test/test_helper.rb
@@ -1,11 +1,13 @@
# frozen_string_literal: true
+ENV["RAILS_ENV"] ||= "test"
require_relative "dummy/config/environment.rb"
require "bundler/setup"
require "active_support"
require "active_support/test_case"
require "active_support/testing/autorun"
+require "webmock/minitest"
require "mini_magick"
begin
@@ -40,13 +42,15 @@ ActiveStorage.verifier = ActiveSupport::MessageVerifier.new("Testing")
class ActiveSupport::TestCase
self.file_fixture_path = File.expand_path("fixtures/files", __dir__)
+ setup { WebMock.allow_net_connect! }
+
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_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
+ def create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", metadata: nil)
+ ActiveStorage::Blob.create_after_upload! io: file_fixture(filename).open, filename: filename, content_type: content_type, metadata: metadata
end
def create_blob_before_direct_upload(filename: "hello.txt", byte_size:, checksum:, content_type: "text/plain")
diff --git a/activestorage/webpack.config.js b/activestorage/webpack.config.js
index 92c4530e7f..3a50eef470 100644
--- a/activestorage/webpack.config.js
+++ b/activestorage/webpack.config.js
@@ -1,4 +1,3 @@
-const webpack = require("webpack")
const path = require("path")
module.exports = {
diff --git a/activestorage/yarn.lock b/activestorage/yarn.lock
index dd09577445..41742be201 100644
--- a/activestorage/yarn.lock
+++ b/activestorage/yarn.lock
@@ -1219,12 +1219,6 @@ escope@^3.6.0:
esrecurse "^4.1.0"
estraverse "^4.1.1"
-eslint-config-airbnb-base@^11.3.1:
- version "11.3.1"
- resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.3.1.tgz#c0ab108c9beed503cb999e4c60f4ef98eda0ed30"
- dependencies:
- eslint-restricted-globals "^0.1.1"
-
eslint-import-resolver-node@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc"
@@ -1254,10 +1248,6 @@ eslint-plugin-import@^2.7.0:
minimatch "^3.0.3"
read-pkg-up "^2.0.0"
-eslint-restricted-globals@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7"
-
eslint-scope@^3.7.1:
version "3.7.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 889919855c..c9cf63f7b5 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,365 +1,3 @@
-* Allow `Range#include?` on TWZ ranges
- In #11474 we prevented TWZ ranges being iterated over which matched
- Ruby's handling of Time ranges and as a consequence `include?`
- stopped working with both Time ranges and TWZ ranges. However in
- ruby/ruby@b061634 support was added for `include?` to use `cover?`
- for 'linear' objects. Since we have no way of making Ruby consider
- TWZ instances as 'linear' we have to override `Range#include?`.
- Fixes #30799.
-
- *Andrew White*
-
-* Fix acronym support in `humanize`
-
- Acronym inflections are stored with lowercase keys in the hash but
- the match wasn't being lowercased before being looked up in the hash.
- This shouldn't have any performance impact because before it would
- fail to find the acronym and perform the `downcase` operation anyway.
-
- Fixes #31052.
-
- *Andrew White*
-
-* Add same method signature for `Time#prev_year` and `Time#next_year`
- in accordance with `Date#prev_year`, `Date#next_year`.
-
- Allows pass argument for `Time#prev_year` and `Time#next_year`.
-
- Before:
- ```
- Time.new(2017, 9, 16, 17, 0).prev_year # => 2016-09-16 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).prev_year(1)
- # => ArgumentError: wrong number of arguments (given 1, expected 0)
-
- Time.new(2017, 9, 16, 17, 0).next_year # => 2018-09-16 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).next_year(1)
- # => ArgumentError: wrong number of arguments (given 1, expected 0)
- ```
-
- After:
- ```
- Time.new(2017, 9, 16, 17, 0).prev_year # => 2016-09-16 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).prev_year(1) # => 2016-09-16 17:00:00 +0300
-
- Time.new(2017, 9, 16, 17, 0).next_year # => 2018-09-16 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).next_year(1) # => 2018-09-16 17:00:00 +0300
- ```
-
- *bogdanvlviv*
-
-* Add same method signature for `Time#prev_month` and `Time#next_month`
- in accordance with `Date#prev_month`, `Date#next_month`.
-
- Allows pass argument for `Time#prev_month` and `Time#next_month`.
-
- Before:
- ```
- Time.new(2017, 9, 16, 17, 0).prev_month # => 2017-08-16 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).prev_month(1)
- # => ArgumentError: wrong number of arguments (given 1, expected 0)
-
- Time.new(2017, 9, 16, 17, 0).next_month # => 2017-10-16 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).next_month(1)
- # => ArgumentError: wrong number of arguments (given 1, expected 0)
- ```
-
- After:
- ```
- Time.new(2017, 9, 16, 17, 0).prev_month # => 2017-08-16 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).prev_month(1) # => 2017-08-16 17:00:00 +0300
-
- Time.new(2017, 9, 16, 17, 0).next_month # => 2017-10-16 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).next_month(1) # => 2017-10-16 17:00:00 +0300
- ```
-
- *bogdanvlviv*
-
-* Add same method signature for `Time#prev_day` and `Time#next_day`
- in accordance with `Date#prev_day`, `Date#next_day`.
-
- Allows pass argument for `Time#prev_day` and `Time#next_day`.
-
- Before:
- ```
- Time.new(2017, 9, 16, 17, 0).prev_day # => 2017-09-15 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).prev_day(1)
- # => ArgumentError: wrong number of arguments (given 1, expected 0)
-
- Time.new(2017, 9, 16, 17, 0).next_day # => 2017-09-17 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).next_day(1)
- # => ArgumentError: wrong number of arguments (given 1, expected 0)
- ```
-
- After:
- ```
- Time.new(2017, 9, 16, 17, 0).prev_day # => 2017-09-15 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).prev_day(1) # => 2017-09-15 17:00:00 +0300
-
- Time.new(2017, 9, 16, 17, 0).next_day # => 2017-09-17 17:00:00 +0300
- Time.new(2017, 9, 16, 17, 0).next_day(1) # => 2017-09-17 17:00:00 +0300
- ```
-
- *bogdanvlviv*
-
-* `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`.
-
- *Anton Khamets*
-
-* Update `String#camelize` to provide feedback when wrong option is passed
-
- `String#camelize` was returning nil without any feedback when an
- invalid option was passed as a parameter.
-
- Previously:
-
- 'one_two'.camelize(true)
- # => nil
-
- Now:
-
- 'one_two'.camelize(true)
- # => ArgumentError: Invalid option, use either :upper or :lower.
-
- *Ricardo Díaz*
-
-* Fix modulo operations involving durations
-
- Rails 5.1 introduced `ActiveSupport::Duration::Scalar` as a wrapper
- around numeric values as a way of ensuring a duration was the outcome of
- an expression. However, the implementation was missing support for modulo
- operations. This support has now been added and should result in a duration
- being returned from expressions involving modulo operations.
-
- Prior to Rails 5.1:
-
- 5.minutes % 2.minutes
- # => 60
-
- Now:
-
- 5.minutes % 2.minutes
- # => 1 minute
-
- Fixes #29603 and #29743.
-
- *Sayan Chakraborty*, *Andrew White*
-
-* Fix division where a duration is the denominator
-
- PR #29163 introduced a change in behavior when a duration was the denominator
- in a calculation - this was incorrect as dividing by a duration should always
- return a `Numeric`. The behavior of previous versions of Rails has been restored.
-
- Fixes #29592.
-
- *Andrew White*
-
-* Add purpose and expiry support to `ActiveSupport::MessageVerifier` &
- `ActiveSupport::MessageEncryptor`.
-
- For instance, to ensure a message is only usable for one intended purpose:
-
- token = @verifier.generate("x", purpose: :shipping)
-
- @verifier.verified(token, purpose: :shipping) # => "x"
- @verifier.verified(token) # => nil
-
- Or make it expire after a set time:
-
- @verifier.generate("x", expires_in: 1.month)
- @verifier.generate("y", expires_at: Time.now.end_of_year)
-
- Showcased with `ActiveSupport::MessageVerifier`, but works the same for
- `ActiveSupport::MessageEncryptor`'s `encrypt_and_sign` and `decrypt_and_verify`.
-
- Pull requests: #29599, #29854
-
- *Assain Jaleel*
-
-* Make the order of `Hash#reverse_merge!` consistent with `HashWithIndifferentAccess`.
-
- *Erol Fornoles*
-
-* Add `freeze_time` helper which freezes time to `Time.now` in tests.
-
- *Prathamesh Sonpatki*
-
-* Default `ActiveSupport::MessageEncryptor` to use AES 256 GCM encryption.
-
- On for new Rails 5.2 apps. Upgrading apps can find the config as a new
- framework default.
-
- *Assain Jaleel*
-
-* Cache: `write_multi`
-
- Rails.cache.write_multi foo: 'bar', baz: 'qux'
-
- Plus faster fetch_multi with stores that implement `write_multi_entries`.
- Keys that aren't found may be written to the cache store in one shot
- instead of separate writes.
-
- The default implementation simply calls `write_entry` for each entry.
- Stores may override if they're capable of one-shot bulk writes, like
- Redis `MSET`.
-
- *Jeremy Daer*
-
-* Add default option to module and class attribute accessors.
-
- mattr_accessor :settings, default: {}
-
- Works for `mattr_reader`, `mattr_writer`, `cattr_accessor`, `cattr_reader`,
- and `cattr_writer` as well.
-
- *Genadi Samokovarov*
-
-* Add `Date#prev_occurring` and `Date#next_occurring` to return specified next/previous occurring day of week.
-
- *Shota Iguchi*
-
-* Add default option to `class_attribute`.
-
- Before:
-
- class_attribute :settings
- self.settings = {}
-
- Now:
-
- class_attribute :settings, default: {}
-
- *DHH*
-
-* `#singularize` and `#pluralize` now respect uncountables for the specified locale.
-
- *Eilis Hamilton*
-
-* Add `ActiveSupport::CurrentAttributes` to provide a thread-isolated attributes singleton.
- Primary use case is keeping all the per-request attributes easily available to the whole system.
-
- *DHH*
-
-* Fix implicit coercion calculations with scalars and durations
-
- Previously, calculations where the scalar is first would be converted to a duration
- of seconds, but this causes issues with dates being converted to times, e.g:
-
- Time.zone = "Beijing" # => Asia/Shanghai
- date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017
- 2 * 1.day # => 172800 seconds
- date + 2 * 1.day # => Mon, 22 May 2017 00:00:00 CST +08:00
-
- Now, the `ActiveSupport::Duration::Scalar` calculation methods will try to maintain
- the part structure of the duration where possible, e.g:
-
- Time.zone = "Beijing" # => Asia/Shanghai
- date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017
- 2 * 1.day # => 2 days
- date + 2 * 1.day # => Mon, 22 May 2017
-
- Fixes #29160, #28970.
-
- *Andrew White*
-
-* Add support for versioned cache entries. This enables the cache stores to recycle cache keys, greatly saving
- on storage in cases with frequent churn. Works together with the separation of `#cache_key` and `#cache_version`
- in Active Record and its use in Action Pack's fragment caching.
-
- *DHH*
-
-* Pass gem name and deprecation horizon to deprecation notifications.
-
- *Willem van Bergen*
-
-* Add support for `:offset` and `:zone` to `ActiveSupport::TimeWithZone#change`
-
- *Andrew White*
-
-* Add support for `:offset` to `Time#change`
-
- Fixes #28723.
-
- *Andrew White*
-
-* Add `fetch_values` for `HashWithIndifferentAccess`
-
- The method was originally added to `Hash` in Ruby 2.3.0.
-
- *Josh Pencheon*
-
-
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 6b3cead1a7..8f769c0767 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2017 David Heinemeier Hansson
+Copyright (c) 2005-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index 8b47933bd2..c770324be8 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -30,7 +30,7 @@ API documentation is at:
* http://api.rubyonrails.org
-Bug reports can be filed for the Ruby on Rails project here:
+Bug reports for the Ruby on Rails project can be filed here:
* https://github.com/rails/rails/issues
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 8672ab1542..f10f19be0a 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -14,10 +14,30 @@ Rake::TestTask.new do |t|
t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
end
+Rake::Task[:test].enhance do
+ Rake::Task["test:cache_stores:redis:ruby"].invoke
+end
+
namespace :test do
task :isolated do
Dir.glob("test/**/*_test.rb").all? do |file|
sh(Gem.ruby, "-w", "-Ilib:test", file)
end || raise("Failures")
end
+
+ namespace :cache_stores do
+ namespace :redis do
+ %w[ ruby hiredis ].each do |driver|
+ task("env:#{driver}") { ENV["REDIS_DRIVER"] = driver }
+
+ Rake::TestTask.new(driver => "env:#{driver}") do |t|
+ t.libs << "test"
+ t.test_files = ["test/cache/stores/redis_cache_store_test.rb"]
+ t.warning = true
+ t.verbose = true
+ t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION)
+ end
+ end
+ end
+ end
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 68be94f99d..a4fb697669 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
#--
-# Copyright (c) 2005-2017 David Heinemeier Hansson
+# Copyright (c) 2005-2018 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -53,6 +53,7 @@ module ActiveSupport
autoload :Callbacks
autoload :Configurable
autoload :Deprecation
+ autoload :Digest
autoload :Gzip
autoload :Inflector
autoload :JSON
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 9f91f9b576..2d038dba77 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -12,10 +12,11 @@ require "active_support/core_ext/string/inflections"
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
module Cache
- autoload :FileStore, "active_support/cache/file_store"
- autoload :MemoryStore, "active_support/cache/memory_store"
- autoload :MemCacheStore, "active_support/cache/mem_cache_store"
- autoload :NullStore, "active_support/cache/null_store"
+ autoload :FileStore, "active_support/cache/file_store"
+ autoload :MemoryStore, "active_support/cache/memory_store"
+ autoload :MemCacheStore, "active_support/cache/mem_cache_store"
+ autoload :NullStore, "active_support/cache/null_store"
+ autoload :RedisCacheStore, "active_support/cache/redis_cache_store"
# These options mean something to all cache implementations. Individual cache
# implementations may support additional options.
@@ -90,16 +91,11 @@ module ActiveSupport
private
def retrieve_cache_key(key)
case
- when key.respond_to?(:cache_key_with_version)
- key.cache_key_with_version
- when key.respond_to?(:cache_key)
- key.cache_key
- when key.is_a?(Hash)
- key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }.to_param
- when key.respond_to?(:to_a)
- key.to_a.collect { |element| retrieve_cache_key(element) }.to_param
- else
- key.to_param
+ when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version
+ when key.respond_to?(:cache_key) then key.cache_key
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
+ else key.to_param
end.to_s
end
@@ -153,12 +149,11 @@ module ActiveSupport
# cache.namespace = -> { @last_mod_time } # Set the namespace to a variable
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
#
- # Caches can also store values in a compressed format to save space and
- # reduce time spent sending data. Since there is overhead, values must be
- # large enough to warrant compression. To turn on compression either pass
- # <tt>compress: true</tt> in the initializer or as an option to +fetch+
- # or +write+. To specify the threshold at which to compress values, set the
- # <tt>:compress_threshold</tt> option. The default threshold is 16K.
+ # Cached data larger than 1kB are compressed by default. To turn off
+ # compression, pass <tt>compress: false</tt> to the initializer or to
+ # individual +fetch+ or +write+ method calls. The 1kB compression
+ # threshold is configurable with the <tt>:compress_threshold</tt> option,
+ # specified in bytes.
class Store
cattr_accessor :logger, instance_writer: true
@@ -217,8 +212,7 @@ module ActiveSupport
# ask whether you should force a cache write. Otherwise, it's clearer to
# just call <tt>Cache#write</tt>.
#
- # Setting <tt>:compress</tt> will store a large cache entry set by the call
- # in a compressed format.
+ # Setting <tt>compress: false</tt> disables compression of the cache entry.
#
# Setting <tt>:expires_in</tt> will set an expiration time on the cache.
# All caches support auto-expiring content after a specified number of
@@ -363,23 +357,11 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- results = {}
- names.each do |name|
- key = normalize_key(name, options)
- version = normalize_version(name, options)
- entry = read_entry(key, options)
-
- if entry
- if entry.expired?
- delete_entry(key, options)
- elsif entry.mismatched?(version)
- # Skip mismatched versions
- else
- results[name] = entry.value
- end
+ instrument :read_multi, names, options do |payload|
+ read_multi_entries(names, options).tap do |results|
+ payload[:hits] = results.keys
end
end
- results
end
# Cache Storage API to write multiple values at once.
@@ -420,14 +402,19 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- read_multi(*names, options).tap do |results|
- writes = {}
+ instrument :read_multi, names, options do |payload|
+ read_multi_entries(names, options).tap do |results|
+ payload[:hits] = results.keys
+ payload[:super_operation] = :fetch_multi
- (names - results.keys).each do |name|
- results[name] = writes[name] = yield(name)
- end
+ writes = {}
+
+ (names - results.keys).each do |name|
+ results[name] = writes[name] = yield(name)
+ end
- write_multi writes, options
+ write_multi writes, options
+ end
end
end
@@ -544,6 +531,28 @@ module ActiveSupport
raise NotImplementedError.new
end
+ # Reads multiple entries from the cache implementation. Subclasses MAY
+ # implement this method.
+ def read_multi_entries(names, options)
+ results = {}
+ names.each do |name|
+ key = normalize_key(name, options)
+ version = normalize_version(name, options)
+ entry = read_entry(key, options)
+
+ if entry
+ if entry.expired?
+ delete_entry(key, options)
+ elsif entry.mismatched?(version)
+ # Skip mismatched versions
+ else
+ results[name] = entry.value
+ end
+ end
+ end
+ results
+ end
+
# Writes multiple entries to the cache implementation. Subclasses MAY
# implement this method.
def write_multi_entries(hash, options)
@@ -567,14 +576,54 @@ module ActiveSupport
end
end
- # Prefixes a key with the namespace. Namespace and key will be delimited
- # with a colon.
- def normalize_key(key, options)
- key = Cache.expand_cache_key(key)
- namespace = options[:namespace] if options
- prefix = namespace.is_a?(Proc) ? namespace.call : namespace
- key = "#{prefix}:#{key}" if prefix
- key
+ # Expands and namespaces the cache key. May be overridden by
+ # cache stores to do additional normalization.
+ def normalize_key(key, options = nil)
+ namespace_key expanded_key(key), options
+ end
+
+ # Prefix the key with a namespace string:
+ #
+ # namespace_key 'foo', namespace: 'cache'
+ # # => 'cache:foo'
+ #
+ # With a namespace block:
+ #
+ # namespace_key 'foo', namespace: -> { 'cache' }
+ # # => 'cache:foo'
+ def namespace_key(key, options = nil)
+ options = merged_options(options)
+ namespace = options[:namespace]
+
+ if namespace.respond_to?(:call)
+ namespace = namespace.call
+ end
+
+ if namespace
+ "#{namespace}:#{key}"
+ else
+ key
+ end
+ end
+
+ # Expands key to be a consistent string value. Invokes +cache_key+ if
+ # object responds to +cache_key+. Otherwise, +to_param+ method will be
+ # called. If the key is a Hash, then keys will be sorted alphabetically.
+ def expanded_key(key)
+ return key.cache_key.to_s if key.respond_to?(:cache_key)
+
+ case key
+ when Array
+ if key.size > 1
+ key = key.collect { |element| expanded_key(element) }
+ else
+ key = key.first
+ end
+ when Hash
+ key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" }
+ end
+
+ key.to_param
end
def normalize_version(key, options = nil)
@@ -643,7 +692,7 @@ module ActiveSupport
class Entry # :nodoc:
attr_reader :version
- DEFAULT_COMPRESS_LIMIT = 16.kilobytes
+ DEFAULT_COMPRESS_LIMIT = 1.kilobyte
# Creates a new cache entry for the specified value. Options supported are
# +:compress+, +:compress_threshold+, and +:expires_in+.
@@ -718,8 +767,8 @@ module ActiveSupport
private
def should_compress?(value, options)
- if value && options[:compress]
- compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
+ if value && options.fetch(:compress, true)
+ compress_threshold = options.fetch(:compress_threshold, DEFAULT_COMPRESS_LIMIT)
serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize
return true if serialized_value_size >= compress_threshold
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 0812cc34c7..a0f44aac0f 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -121,7 +121,7 @@ module ActiveSupport
fname = URI.encode_www_form_component(key)
if fname.size > FILEPATH_MAX_SIZE
- fname = Digest::MD5.hexdigest(key)
+ fname = ActiveSupport::Digest.hexdigest(key)
end
hash = Zlib.adler32(fname)
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 9242a334f2..cae0d44e7d 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -7,7 +7,6 @@ rescue LoadError => e
raise e
end
-require "digest/md5"
require "active_support/core_ext/marshal"
require "active_support/core_ext/array/extract_options"
@@ -64,7 +63,23 @@ module ActiveSupport
addresses = addresses.flatten
options = addresses.extract_options!
addresses = ["localhost:11211"] if addresses.empty?
- Dalli::Client.new(addresses, options)
+
+ pool_options = {}
+ pool_options[:size] = options[:pool_size] if options[:pool_size]
+ pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout]
+
+ if pool_options.empty?
+ Dalli::Client.new(addresses, options)
+ else
+ begin
+ require "connection_pool"
+ rescue LoadError => e
+ $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install"
+ raise e
+ end
+
+ ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) }
+ end
end
# Creates a new MemCacheStore object, with the given memcached server
@@ -92,28 +107,6 @@ module ActiveSupport
end
end
- # Reads multiple values from the cache using a single call to the
- # servers for all keys. Options can be passed in the last argument.
- def read_multi(*names)
- options = names.extract_options!
- options = merged_options(options)
-
- keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
-
- raw_values = @data.get_multi(keys_to_names.keys)
- values = {}
-
- raw_values.each do |key, value|
- entry = deserialize_entry(value)
-
- unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
- values[keys_to_names[key]] = entry.value
- end
- end
-
- values
- end
-
# Increment a cached value. This method uses the memcached incr atomic
# operator and can only be used on values written with the :raw option.
# Calling it on a value not stored with :raw will initialize that value
@@ -122,7 +115,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:increment, name, amount: amount) do
rescue_error_with nil do
- @data.incr(normalize_key(name, options), amount)
+ @data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) }
end
end
end
@@ -135,7 +128,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:decrement, name, amount: amount) do
rescue_error_with nil do
- @data.decr(normalize_key(name, options), amount)
+ @data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) }
end
end
end
@@ -143,18 +136,18 @@ module ActiveSupport
# Clear the entire cache on all memcached servers. This method should
# be used with care when shared cache is being used.
def clear(options = nil)
- rescue_error_with(nil) { @data.flush_all }
+ rescue_error_with(nil) { @data.with { |c| c.flush_all } }
end
# Get the statistics from the memcached servers.
def stats
- @data.stats
+ @data.with { |c| c.stats }
end
private
# Read an entry from the cache.
def read_entry(key, options)
- rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) }
+ rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) }
end
# Write an entry to the cache.
@@ -167,13 +160,31 @@ module ActiveSupport
expires_in += 5.minutes
end
rescue_error_with false do
- @data.send(method, key, value, expires_in, options)
+ @data.with { |c| c.send(method, key, value, expires_in, options) }
+ end
+ end
+
+ # Reads multiple entries from the cache implementation.
+ def read_multi_entries(names, options)
+ keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }]
+
+ raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
+ values = {}
+
+ raw_values.each do |key, value|
+ entry = deserialize_entry(value)
+
+ unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
+ values[keys_to_names[key]] = entry.value
+ end
end
+
+ values
end
# Delete an entry from the cache.
def delete_entry(key, options)
- rescue_error_with(false) { @data.delete(key) }
+ rescue_error_with(false) { @data.with { |c| c.delete(key) } }
end
# Memcache keys are binaries. So we need to force their encoding to binary
@@ -183,7 +194,7 @@ module ActiveSupport
key = super.dup
key = key.force_encoding(Encoding::ASCII_8BIT)
key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" }
- key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
+ key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250
key
end
diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb
new file mode 100644
index 0000000000..af14c28408
--- /dev/null
+++ b/activesupport/lib/active_support/cache/redis_cache_store.rb
@@ -0,0 +1,436 @@
+# frozen_string_literal: true
+
+begin
+ gem "redis", ">= 4.0.1"
+ require "redis"
+ require "redis/distributed"
+rescue LoadError
+ warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \"~> 4.0\"`"
+ raise
+end
+
+# Prefer the hiredis driver but don't require it.
+begin
+ require "redis/connection/hiredis"
+rescue LoadError
+end
+
+require "digest/sha2"
+require "active_support/core_ext/marshal"
+require "active_support/core_ext/hash/transform_values"
+
+module ActiveSupport
+ module Cache
+ # Redis cache store.
+ #
+ # Deployment note: Take care to use a *dedicated Redis cache* rather
+ # than pointing this at your existing Redis server. It won't cope well
+ # with mixed usage patterns and it won't expire cache entries by default.
+ #
+ # Redis cache server setup guide: https://redis.io/topics/lru-cache
+ #
+ # * Supports vanilla Redis, hiredis, and Redis::Distributed.
+ # * Supports Memcached-like sharding across Redises with Redis::Distributed.
+ # * Fault tolerant. If the Redis server is unavailable, no exceptions are
+ # raised. Cache fetches are all misses and writes are dropped.
+ # * Local cache. Hot in-memory primary cache within block/middleware scope.
+ # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use Redis::Distributed
+ # 4.0.1+ for distributed mget support.
+ # * +delete_matched+ support for Redis KEYS globs.
+ class RedisCacheStore < Store
+ # Keys are truncated with their own SHA2 digest if they exceed 1kB
+ MAX_KEY_BYTESIZE = 1024
+
+ DEFAULT_REDIS_OPTIONS = {
+ connect_timeout: 20,
+ read_timeout: 1,
+ write_timeout: 1,
+ reconnect_attempts: 0,
+ }
+
+ DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do
+ if logger
+ logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
+ end
+ end
+
+ DELETE_GLOB_LUA = "for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end"
+ private_constant :DELETE_GLOB_LUA
+
+ # Support raw values in the local cache strategy.
+ module LocalCacheWithRaw # :nodoc:
+ private
+ def read_entry(key, options)
+ entry = super
+ if options[:raw] && local_cache && entry
+ entry = deserialize_entry(entry.value)
+ end
+ entry
+ end
+
+ def write_entry(key, entry, options)
+ if options[:raw] && local_cache
+ raw_entry = Entry.new(serialize_entry(entry, raw: true))
+ raw_entry.expires_at = entry.expires_at
+ super(key, raw_entry, options)
+ else
+ super
+ end
+ end
+
+ def write_multi_entries(entries, options)
+ if options[:raw] && local_cache
+ raw_entries = entries.map do |key, entry|
+ raw_entry = Entry.new(serialize_entry(entry, raw: true))
+ raw_entry.expires_at = entry.expires_at
+ end.to_h
+
+ super(raw_entries, options)
+ else
+ super
+ end
+ end
+ end
+
+ prepend Strategy::LocalCache
+ prepend LocalCacheWithRaw
+
+ class << self
+ # Factory method to create a new Redis instance.
+ #
+ # Handles four options: :redis block, :redis instance, single :url
+ # string, and multiple :url strings.
+ #
+ # Option Class Result
+ # :redis Proc -> options[:redis].call
+ # :redis Object -> options[:redis]
+ # :url String -> Redis.new(url: …)
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
+ #
+ def build_redis(redis: nil, url: nil, **redis_options) #:nodoc:
+ urls = Array(url)
+
+ if redis.respond_to?(:call)
+ redis.call
+ elsif redis
+ redis
+ elsif urls.size > 1
+ build_redis_distributed_client urls: urls, **redis_options
+ else
+ build_redis_client url: urls.first, **redis_options
+ end
+ end
+
+ private
+ def build_redis_distributed_client(urls:, **redis_options)
+ ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist|
+ urls.each { |u| dist.add_node url: u }
+ end
+ end
+
+ def build_redis_client(url:, **redis_options)
+ ::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url))
+ end
+ end
+
+ attr_reader :redis_options
+ attr_reader :max_key_bytesize
+
+ # Creates a new Redis cache store.
+ #
+ # Handles three options: block provided to instantiate, single URL
+ # provided, and multiple URLs provided.
+ #
+ # :redis Proc -> options[:redis].call
+ # :url String -> Redis.new(url: …)
+ # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …])
+ #
+ # No namespace is set by default. Provide one if the Redis cache
+ # server is shared with other apps: <tt>namespace: 'myapp-cache'<tt>.
+ #
+ # Compression is enabled by default with a 1kB threshold, so cached
+ # values larger than 1kB are automatically compressed. Disable by
+ # passing <tt>cache: false</tt> or change the threshold by passing
+ # <tt>compress_threshold: 4.kilobytes</tt>.
+ #
+ # No expiry is set on cache entries by default. Redis is expected to
+ # be configured with an eviction policy that automatically deletes
+ # least-recently or -frequently used keys when it reaches max memory.
+ # See https://redis.io/topics/lru-cache for cache server setup.
+ #
+ # Race condition TTL is not set by default. This can be used to avoid
+ # "thundering herd" cache writes when hot cache entries are expired.
+ # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more.
+ def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options)
+ @redis_options = redis_options
+
+ @max_key_bytesize = MAX_KEY_BYTESIZE
+ @error_handler = error_handler
+
+ super namespace: namespace,
+ compress: compress, compress_threshold: compress_threshold,
+ expires_in: expires_in, race_condition_ttl: race_condition_ttl
+ end
+
+ def redis
+ @redis ||= self.class.build_redis(**redis_options)
+ end
+
+ def inspect
+ instance = @redis || @redis_options
+ "<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>"
+ end
+
+ # Cache Store API implementation.
+ #
+ # Read multiple values at once. Returns a hash of requested keys ->
+ # fetched values.
+ def read_multi(*names)
+ if mget_capable?
+ instrument(:read_multi, names, options) do |payload|
+ read_multi_mget(*names).tap do |results|
+ payload[:hits] = results.keys
+ end
+ end
+ else
+ super
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Supports Redis KEYS glob patterns:
+ #
+ # h?llo matches hello, hallo and hxllo
+ # h*llo matches hllo and heeeello
+ # h[ae]llo matches hello and hallo, but not hillo
+ # h[^e]llo matches hallo, hbllo, ... but not hello
+ # h[a-b]llo matches hallo and hbllo
+ #
+ # Use \ to escape special characters if you want to match them verbatim.
+ #
+ # See https://redis.io/commands/KEYS for more.
+ #
+ # Failsafe: Raises errors.
+ def delete_matched(matcher, options = nil)
+ instrument :delete_matched, matcher do
+ case matcher
+ when String
+ redis.eval DELETE_GLOB_LUA, [], [namespace_key(matcher, options)]
+ else
+ raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}"
+ end
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Increment a cached value. This method uses the Redis incr atomic
+ # operator and can only be used on values written with the :raw option.
+ # Calling it on a value not stored with :raw will initialize that value
+ # to zero.
+ #
+ # Failsafe: Raises errors.
+ def increment(name, amount = 1, options = nil)
+ instrument :increment, name, amount: amount do
+ failsafe :increment do
+ redis.incrby normalize_key(name, options), amount
+ end
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Decrement a cached value. This method uses the Redis decr atomic
+ # operator and can only be used on values written with the :raw option.
+ # Calling it on a value not stored with :raw will initialize that value
+ # to zero.
+ #
+ # Failsafe: Raises errors.
+ def decrement(name, amount = 1, options = nil)
+ instrument :decrement, name, amount: amount do
+ failsafe :decrement do
+ redis.decrby normalize_key(name, options), amount
+ end
+ end
+ end
+
+ # Cache Store API implementation.
+ #
+ # Removes expired entries. Handled natively by Redis least-recently-/
+ # least-frequently-used expiry, so manual cleanup is not supported.
+ def cleanup(options = nil)
+ super
+ end
+
+ # Clear the entire cache on all Redis servers. Safe to use on
+ # shared servers if the cache is namespaced.
+ #
+ # Failsafe: Raises errors.
+ def clear(options = nil)
+ failsafe :clear do
+ if namespace = merged_options(options)[namespace]
+ delete_matched "*", namespace: namespace
+ else
+ redis.flushdb
+ end
+ end
+ end
+
+ def mget_capable? #:nodoc:
+ set_redis_capabilities unless defined? @mget_capable
+ @mget_capable
+ end
+
+ def mset_capable? #:nodoc:
+ set_redis_capabilities unless defined? @mset_capable
+ @mset_capable
+ end
+
+ private
+ def set_redis_capabilities
+ case redis
+ when Redis::Distributed
+ @mget_capable = true
+ @mset_capable = false
+ else
+ @mget_capable = true
+ @mset_capable = true
+ end
+ end
+
+ # Store provider interface:
+ # Read an entry from the cache.
+ def read_entry(key, options = nil)
+ failsafe :read_entry do
+ deserialize_entry redis.get(key)
+ end
+ end
+
+ def read_multi_entries(names, _options)
+ if mget_capable?
+ read_multi_mget(*names)
+ else
+ super
+ end
+ end
+
+ def read_multi_mget(*names)
+ options = names.extract_options!
+ options = merged_options(options)
+
+ keys = names.map { |name| normalize_key(name, options) }
+
+ values = failsafe(:read_multi_mget, returning: {}) do
+ redis.mget(*keys)
+ end
+
+ names.zip(values).each_with_object({}) do |(name, value), results|
+ if value
+ entry = deserialize_entry(value)
+ unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
+ results[name] = entry.value
+ end
+ end
+ end
+ end
+
+ # Write an entry to the cache.
+ #
+ # Requires Redis 2.6.12+ for extended SET options.
+ def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options)
+ serialized_entry = serialize_entry(entry, raw: raw)
+
+ # If race condition TTL is in use, ensure that cache entries
+ # stick around a bit longer after they would have expired
+ # so we can purposefully serve stale entries.
+ if race_condition_ttl && expires_in && expires_in > 0 && !raw
+ expires_in += 5.minutes
+ end
+
+ failsafe :write_entry, returning: false do
+ if unless_exist || expires_in
+ modifiers = {}
+ modifiers[:nx] = unless_exist
+ modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in
+
+ redis.set key, serialized_entry, modifiers
+ else
+ redis.set key, serialized_entry
+ end
+ end
+ end
+
+ # Delete an entry from the cache.
+ def delete_entry(key, options)
+ failsafe :delete_entry, returning: false do
+ redis.del key
+ end
+ end
+
+ # Nonstandard store provider API to write multiple values at once.
+ def write_multi_entries(entries, expires_in: nil, **options)
+ if entries.any?
+ if mset_capable? && expires_in.nil?
+ failsafe :write_multi_entries do
+ redis.mapped_mset(serialize_entries(entries, raw: options[:raw]))
+ end
+ else
+ super
+ end
+ end
+ end
+
+ # Truncate keys that exceed 1kB.
+ def normalize_key(key, options)
+ truncate_key super.b
+ end
+
+ def truncate_key(key)
+ if key.bytesize > max_key_bytesize
+ suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}"
+ truncate_at = max_key_bytesize - suffix.bytesize
+ "#{key.byteslice(0, truncate_at)}#{suffix}"
+ else
+ key
+ end
+ end
+
+ def deserialize_entry(serialized_entry)
+ if serialized_entry
+ entry = Marshal.load(serialized_entry) rescue serialized_entry
+ entry.is_a?(Entry) ? entry : Entry.new(entry)
+ end
+ end
+
+ def serialize_entry(entry, raw: false)
+ if raw
+ entry.value.to_s
+ else
+ Marshal.dump(entry)
+ end
+ end
+
+ def serialize_entries(entries, raw: false)
+ entries.transform_values do |entry|
+ serialize_entry entry, raw: raw
+ end
+ end
+
+ def failsafe(method, returning: nil)
+ yield
+ rescue ::Redis::BaseConnectionError => e
+ handle_exception exception: e, method: method, returning: returning
+ returning
+ end
+
+ def handle_exception(exception:, method:, returning:)
+ if @error_handler
+ @error_handler.(method: method, exception: exception, returning: returning)
+ end
+ rescue => failsafe
+ warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}"
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb b/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb
new file mode 100644
index 0000000000..a8455c0048
--- /dev/null
+++ b/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require "monitor"
+
+module ActiveSupport
+ module Concurrency
+ # A monitor that will permit dependency loading while blocked waiting for
+ # the lock.
+ class LoadInterlockAwareMonitor < Monitor
+ # Enters an exclusive section, but allows dependency loading while blocked
+ def mon_enter
+ mon_try_enter ||
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super }
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index d67f99df0e..b7ff7a3907 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -35,8 +35,8 @@ class Array
# people.without "Aaron", "Todd"
# # => ["David", "Rafael"]
#
- # Note: This is an optimization of `Enumerable#without` that uses `Array#-`
- # instead of `Array#reject` for performance reasons.
+ # Note: This is an optimization of <tt>Enumerable#without</tt> that uses <tt>Array#-</tt>
+ # instead of <tt>Array#reject</tt> for performance reasons.
def without(*elements)
self - elements
end
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 061b79e098..f6cb1a384c 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
@@ -330,20 +330,28 @@ module DateAndTime
beginning_of_year..end_of_year
end
- # Returns specific next occurring day of week
+ # Returns a new date/time representing the next occurrence of the specified day of week.
+ #
+ # today = Date.today # => Thu, 14 Dec 2017
+ # today.next_occurring(:monday) # => Mon, 18 Dec 2017
+ # today.next_occurring(:thursday) # => Thu, 21 Dec 2017
def next_occurring(day_of_week)
current_day_number = wday != 0 ? wday - 1 : 6
from_now = DAYS_INTO_WEEK.fetch(day_of_week) - current_day_number
from_now += 7 unless from_now > 0
- since(from_now.days)
+ advance(days: from_now)
end
- # Returns specific previous occurring day of week
+ # Returns a new date/time representing the previous occurrence of the specified day of week.
+ #
+ # today = Date.today # => Thu, 14 Dec 2017
+ # today.prev_occurring(:monday) # => Mon, 11 Dec 2017
+ # today.prev_occurring(:thursday) # => Thu, 07 Dec 2017
def prev_occurring(day_of_week)
current_day_number = wday != 0 ? wday - 1 : 6
ago = current_day_number - DAYS_INTO_WEEK.fetch(day_of_week)
ago += 7 unless ago > 0
- ago(ago.days)
+ advance(days: -ago)
end
private
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 424f64d6fa..7600a067cc 100644
--- a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -8,9 +8,9 @@ class DateTime
silence_redefinition_of_method :to_time
- # Either return an instance of `Time` with the same UTC offset
- # as +self+ or an instance of `Time` representing the same time
- # in the the local system timezone depending on the setting of
+ # Either return an instance of +Time+ with the same UTC offset
+ # as +self+ or an instance of +Time+ representing the same time
+ # in the local system timezone depending on the setting of
# on the setting of +ActiveSupport.to_time_preserves_timezone+.
def to_time
preserve_timezone ? getlocal(utc_offset) : getlocal
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 11d28d12a1..5b48254646 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -165,7 +165,7 @@ module ActiveSupport
Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ]
when Array
params.map { |v| normalize_keys(v) }
- else
+ else
params
end
end
@@ -178,7 +178,7 @@ module ActiveSupport
process_array(value)
when String
value
- else
+ else
raise "can't typecast #{value.class.name} - #{value.inspect}"
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index 370a948eea..7bbbf321ab 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -22,7 +22,7 @@ class Module
#
# == Using comments:
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -30,7 +30,6 @@ class Module
# has_many :events
#
# before_create :track_creation
- # after_destroy :track_deletion
#
# private
# def track_creation
@@ -42,7 +41,7 @@ class Module
#
# Noisy syntax.
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -52,7 +51,6 @@ class Module
# included do
# has_many :events
# before_create :track_creation
- # after_destroy :track_deletion
# end
#
# private
@@ -70,7 +68,7 @@ class Module
# increased overhead can be a reasonable tradeoff even if it reduces our
# at-a-glance perception of how things work.
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -82,7 +80,7 @@ class Module
# By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
# separate bite-sized concerns.
#
- # class Todo
+ # class Todo < ApplicationRecord
# # Other todo implementation
# # ...
#
@@ -90,7 +88,6 @@ class Module
# included do
# has_many :events
# before_create :track_creation
- # after_destroy :track_deletion
# end
#
# private
@@ -101,7 +98,7 @@ class Module
# end
#
# Todo.ancestors
- # # => [Todo, Todo::EventTracking, Object]
+ # # => [Todo, Todo::EventTracking, ApplicationRecord, Object]
#
# This small step has some wonderful ripple effects. We can
# * grok the behavior of our class in one glance,
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index a77f903db5..4310df3024 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -115,11 +115,8 @@ class Module
# invoice.customer_address # => 'Vimmersvej 13'
#
# If the target is +nil+ and does not respond to the delegated method a
- # +Module::DelegationError+ is raised, as with any other value. Sometimes,
- # however, it makes sense to be robust to that situation and that is the
- # purpose of the <tt>:allow_nil</tt> option: If the target is not +nil+, or it
- # is and responds to the method, everything works as usual. But if it is +nil+
- # and does not respond to the delegated method, +nil+ is returned.
+ # +Module::DelegationError+ is raised. If you wish to instead return +nil+,
+ # use the <tt>:allow_nil</tt> option.
#
# class User < ActiveRecord::Base
# has_one :profile
diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb
index d4f1e01140..6d37cd9dfd 100644
--- a/activesupport/lib/active_support/core_ext/name_error.rb
+++ b/activesupport/lib/active_support/core_ext/name_error.rb
@@ -10,6 +10,11 @@ class NameError
# end
# # => "HelloWorld"
def missing_name
+ # Since ruby v2.3.0 `did_you_mean` gem is loaded by default.
+ # It extends NameError#message with spell corrections which are SLOW.
+ # We should use original_message message instead.
+ message = respond_to?(:original_message) ? original_message : self.message
+
if /undefined local variable or method/ !~ message
$1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message
end
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index e42ad852dd..2ca431ab10 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require "active_support/core_ext/regexp"
+require "concurrent/map"
class Object
# An object is blank if it's false, empty, or a whitespace string.
@@ -102,6 +103,9 @@ end
class String
BLANK_RE = /\A[[:space:]]*\z/
+ ENCODED_BLANKS = Concurrent::Map.new do |h, enc|
+ h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING)
+ end
# A string is blank if it's empty or contains whitespaces only:
#
@@ -119,7 +123,12 @@ class String
# The regexp that matches blank strings is expensive. For the case of empty
# strings we can speed up this method (~3.5x) with an empty? call. The
# penalty for the rest of strings is marginal.
- empty? || BLANK_RE.match?(self)
+ empty? ||
+ begin
+ BLANK_RE.match?(self)
+ rescue Encoding::CompatibilityError
+ ENCODED_BLANKS[self.encoding].match?(self)
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 1744a44df6..9bb99087bc 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -108,8 +108,8 @@ require "bigdecimal"
class BigDecimal
# BigDecimals are duplicable:
#
- # BigDecimal.new("1.2").duplicable? # => true
- # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
+ # BigDecimal("1.2").duplicable? # => true
+ # BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
def duplicable?
true
end
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 b6c464db33..2838fd76be 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -62,7 +62,7 @@ class Object
#
# validates :content, length: { minimum: 50 }, if: -> { content.present? }
#
- # Hence the inherited default for `if` key is ignored.
+ # Hence the inherited default for +if+ key is ignored.
#
# NOTE: You cannot call class methods implicitly inside of with_options.
# You can access these methods using the class name instead:
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index da53739efc..8af301734a 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -174,7 +174,7 @@ class String
# <%= link_to(@person.name, person_path) %>
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
#
- # To preserve the case of the characters in a string, use the `preserve_case` argument.
+ # To preserve the case of the characters in a string, use the +preserve_case+ argument.
#
# class Person
# def to_param
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 38224ea5da..07c0d16398 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -16,6 +16,8 @@ class String
# >> "lj".mb_chars.upcase.to_s
# => "LJ"
#
+ # NOTE: An above example is useful for pre Ruby 2.4. Ruby 2.4 supports Unicode case mappings.
+ #
# == Method chaining
#
# All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
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 b712200959..f3bdc2977e 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -250,7 +250,7 @@ class String
# Marks a string as trusted safe. It will be inserted into HTML with no
# additional escaping performed. It is your responsibility to ensure that the
# string contains no malicious content. This method is equivalent to the
- # `raw` helper in views. It is recommended that you use `sanitize` instead of
+ # +raw+ helper in views. It is recommended that you use +sanitize+ instead of
# this method. It should never be called on user input.
def html_safe
ActiveSupport::SafeBuffer.new(self)
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 82c10b3079..0f59558bb5 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -447,6 +447,7 @@ module ActiveSupport #:nodoc:
mod = Module.new
into.const_set const_name, mod
autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path)
+ autoloaded_constants.uniq!
mod
end
@@ -670,7 +671,7 @@ module ActiveSupport #:nodoc:
when Module
desc.name ||
raise(ArgumentError, "Anonymous modules have no name to be referenced by")
- else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
+ else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
end
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index a1ad2ca465..7271ab565b 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -35,7 +35,7 @@ module ActiveSupport
# and the second is a library name.
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = "6.0", gem_name = "Rails")
+ def initialize(deprecation_horizon = "6.1", gem_name = "Rails")
self.gem_name = gem_name
self.deprecation_horizon = deprecation_horizon
# By default, warnings are not silenced and debugging is off.
diff --git a/activesupport/lib/active_support/deprecation/constant_accessor.rb b/activesupport/lib/active_support/deprecation/constant_accessor.rb
index 3d7eedf637..1ed0015812 100644
--- a/activesupport/lib/active_support/deprecation/constant_accessor.rb
+++ b/activesupport/lib/active_support/deprecation/constant_accessor.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-require "active_support/inflector/methods"
-
module ActiveSupport
class Deprecation
# DeprecatedConstantAccessor transforms a constant into a deprecated one by
@@ -17,7 +15,7 @@ module ActiveSupport
#
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
#
- # (In a later update, the original implementation of `PLANETS` has been removed.)
+ # # (In a later update, the original implementation of `PLANETS` has been removed.)
#
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
# include ActiveSupport::Deprecation::DeprecatedConstantAccessor
@@ -29,6 +27,8 @@ module ActiveSupport
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
module DeprecatedConstantAccessor
def self.included(base)
+ require "active_support/inflector/methods"
+
extension = Module.new do
def const_missing(missing_const_name)
if class_variable_defined?(:@@_deprecated_constants)
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index d359992bf5..5be893d281 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -8,9 +8,7 @@ module ActiveSupport
module MethodWrapper
# Declare that a method has been deprecated.
#
- # module Fred
- # extend self
- #
+ # class Fred
# def aaa; end
# def bbb; end
# def ccc; end
@@ -22,15 +20,15 @@ module ActiveSupport
# ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
# # => Fred
#
- # Fred.aaa
+ # Fred.new.aaa
# # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
# # => nil
#
- # Fred.bbb
+ # Fred.new.bbb
# # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11)
# # => nil
#
- # Fred.ccc
+ # Fred.new.ccc
# # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12)
# # => nil
#
@@ -39,7 +37,7 @@ module ActiveSupport
# ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator)
# # => [:ddd]
#
- # Fred.ddd
+ # Fred.new.ddd
# DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15)
# # => nil
#
@@ -48,7 +46,7 @@ module ActiveSupport
# custom_deprecator.deprecate_methods(Fred, eee: :zzz)
# # => [:eee]
#
- # Fred.eee
+ # Fred.new.eee
# DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18)
# # => nil
def deprecate_methods(target_module, *method_names)
@@ -62,6 +60,13 @@ module ActiveSupport
deprecator.deprecation_warning(method_name, options[method_name])
super(*args, &block)
end
+
+ case
+ when target_module.protected_method_defined?(method_name)
+ protected method_name
+ when target_module.private_method_defined?(method_name)
+ private method_name
+ end
end
end
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index f6c6648917..896c0d2d8e 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-require "active_support/inflector/methods"
require "active_support/core_ext/regexp"
module ActiveSupport
@@ -114,7 +113,7 @@ module ActiveSupport
#
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
#
- # (In a later update, the original implementation of `PLANETS` has been removed.)
+ # # (In a later update, the original implementation of `PLANETS` has been removed.)
#
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
@@ -125,6 +124,8 @@ module ActiveSupport
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.")
+ require "active_support/inflector/methods"
+
@old_const = old_const
@new_const = new_const
@deprecator = deprecator
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index 242e21b782..2c004f4c9e 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -61,7 +61,7 @@ module ActiveSupport
case message
when Symbol then "#{warning} (use #{message} instead)"
when String then "#{warning} (#{message})"
- else warning
+ else warning
end
end
diff --git a/activesupport/lib/active_support/digest.rb b/activesupport/lib/active_support/digest.rb
new file mode 100644
index 0000000000..fba10fbdcf
--- /dev/null
+++ b/activesupport/lib/active_support/digest.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module ActiveSupport
+ class Digest #:nodoc:
+ class <<self
+ def hash_digest_class
+ @hash_digest_class ||= ::Digest::MD5
+ end
+
+ def hash_digest_class=(klass)
+ raise ArgumentError, "#{klass} is expected to implement hexdigest class method" unless klass.respond_to?(:hexdigest)
+ @hash_digest_class = klass
+ end
+
+ def hexdigest(arg)
+ hash_digest_class.hexdigest(arg)[0...32]
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 1af3411a8a..88897f811e 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -194,7 +194,6 @@ module ActiveSupport
end
parts[:seconds] = remainder
- parts.reject! { |k, v| v.zero? }
new(value, parts)
end
@@ -211,6 +210,7 @@ module ActiveSupport
def initialize(value, parts) #:nodoc:
@value, @parts = value, parts.to_h
@parts.default = 0
+ @parts.reject! { |k, v| v.zero? }
end
def coerce(other) #:nodoc:
@@ -370,6 +370,8 @@ module ActiveSupport
alias :before :ago
def inspect #:nodoc:
+ return "0 seconds" if parts.empty?
+
parts.
reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }.
sort_by { |unit, _ | PARTS.index(unit) }.
@@ -381,6 +383,14 @@ module ActiveSupport
to_i
end
+ def init_with(coder) #:nodoc:
+ initialize(coder["value"], coder["parts"])
+ end
+
+ def encode_with(coder) #:nodoc:
+ coder.map = { "value" => @value, "parts" => @parts }
+ end
+
# Build ISO 8601 Duration string for this duration.
# The +precision+ parameter can be used to limit seconds' precision of duration.
def iso8601(precision: nil)
diff --git a/activesupport/lib/active_support/encrypted_configuration.rb b/activesupport/lib/active_support/encrypted_configuration.rb
index b403048627..dab953d5d5 100644
--- a/activesupport/lib/active_support/encrypted_configuration.rb
+++ b/activesupport/lib/active_support/encrypted_configuration.rb
@@ -11,8 +11,9 @@ module ActiveSupport
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
+ def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:)
+ super content_path: config_path, key_path: key_path,
+ env_key: env_key, raise_if_missing_key: raise_if_missing_key
end
# Allow a config to be started without a file present
@@ -22,6 +23,12 @@ module ActiveSupport
""
end
+ def write(contents)
+ deserialize(contents)
+
+ super
+ end
+
def config
@config ||= deserialize(read).deep_symbolize_keys
end
@@ -36,7 +43,7 @@ module ActiveSupport
end
def deserialize(config)
- config.present? ? YAML.load(config) : {}
+ config.present? ? YAML.load(config, content_path) : {}
end
end
end
diff --git a/activesupport/lib/active_support/encrypted_file.rb b/activesupport/lib/active_support/encrypted_file.rb
index 3d1455fb95..671b6b6a69 100644
--- a/activesupport/lib/active_support/encrypted_file.rb
+++ b/activesupport/lib/active_support/encrypted_file.rb
@@ -26,11 +26,11 @@ module ActiveSupport
end
- attr_reader :content_path, :key_path, :env_key
+ attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key
- def initialize(content_path:, key_path:, env_key:)
+ def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:)
@content_path, @key_path = Pathname.new(content_path), Pathname.new(key_path)
- @env_key = env_key
+ @env_key, @raise_if_missing_key = env_key, raise_if_missing_key
end
def key
@@ -38,7 +38,7 @@ module ActiveSupport
end
def read
- if content_path.exist?
+ if !key.nil? && content_path.exist?
decrypt content_path.binread
else
raise MissingContentError, content_path
@@ -93,7 +93,7 @@ module ActiveSupport
end
def handle_missing_key
- raise MissingKeyError, key_path: key_path, env_key: env_key
+ raise MissingKeyError, key_path: key_path, env_key: env_key if raise_if_missing_key
end
end
end
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index 2a7ef2f820..c951ad16a3 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -7,8 +7,8 @@ module ActiveSupport
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index a639eddf10..7e5dff1d6d 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -4,6 +4,7 @@ require "concurrent/map"
require "active_support/core_ext/array/prepend_and_append"
require "active_support/core_ext/regexp"
require "active_support/i18n"
+require "active_support/deprecation"
module ActiveSupport
module Inflector
@@ -67,16 +68,21 @@ module ActiveSupport
end
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
+ deprecate :acronym_regex
+
+ attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc:
def initialize
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
+ @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {}
+ define_acronym_regex_patterns
end
# Private, for the test suite.
def initialize_dup(orig) # :nodoc:
- %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
+ %w(plurals singulars uncountables humans acronyms).each do |scope|
instance_variable_set("@#{scope}", orig.send(scope).dup)
end
+ define_acronym_regex_patterns
end
# Specifies a new acronym. An acronym must be specified as it will appear
@@ -130,7 +136,7 @@ module ActiveSupport
# camelize 'mcdonald' # => 'McDonald'
def acronym(word)
@acronyms[word.downcase] = word
- @acronym_regex = /#{@acronyms.values.join("|")}/
+ define_acronym_regex_patterns
end
# Specifies a new pluralization rule and its replacement. The rule can
@@ -221,10 +227,18 @@ module ActiveSupport
case scope
when :all
@plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
- else
+ else
instance_variable_set "@#{scope}", []
end
end
+
+ private
+
+ def define_acronym_regex_patterns
+ @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/
+ @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/
+ @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/
+ end
end
# Yields a singleton instance of Inflector::Inflections so you can specify
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 3357b5eb32..7e782e2a93 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -71,7 +71,7 @@ module ActiveSupport
if uppercase_first_letter
string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
else
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
+ string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase }
end
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
string.gsub!("/".freeze, "::".freeze)
@@ -92,7 +92,7 @@ module ActiveSupport
def underscore(camel_cased_word)
return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze)
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
+ word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_'.freeze }#{$2.downcase}" }
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
word.tr!("-".freeze, "_".freeze)
@@ -350,7 +350,7 @@ module ActiveSupport
when 1; "st"
when 2; "nd"
when 3; "rd"
- else "th"
+ else "th"
end
end
end
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 9fb3a2e0af..6f2ca4999c 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -73,12 +73,12 @@ module ActiveSupport
# parameterize("Donald E. Knuth") # => "donald-e-knuth"
# parameterize("^très|Jolie-- ") # => "tres-jolie"
#
- # To use a custom separator, override the `separator` argument.
+ # To use a custom separator, override the +separator+ argument.
#
# parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
# parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie"
#
- # To preserve the case of the characters in a string, use the `preserve_case` argument.
+ # To preserve the case of the characters in a string, use the +preserve_case+ argument.
#
# parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
# parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie"
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 69c95e0622..5236c776dd 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -58,8 +58,8 @@ module ActiveSupport
# === 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.
+ # 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.
@@ -81,9 +81,9 @@ module ActiveSupport
class MessageEncryptor
prepend Messages::Rotator::Encryptor
- class << self
- attr_accessor :use_authenticated_message_encryption #:nodoc:
+ cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
+ class << self
def default_cipher #:nodoc:
if use_authenticated_message_encryption
"aes-256-gcm"
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 622b66ee55..83c39c0a86 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -31,7 +31,7 @@ module ActiveSupport
#
# +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default.
# If you want to use a different hash algorithm, you can change it by providing
- # `:digest` key as an option while initializing the verifier:
+ # +:digest+ key as an option while initializing the verifier:
#
# @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
#
@@ -78,8 +78,8 @@ module ActiveSupport
# === 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.
+ # 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.
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index a64223c0e0..f923061fae 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -276,7 +276,7 @@ module ActiveSupport
reorder_characters(decompose(:compatibility, codepoints))
when :kc
compose(reorder_characters(decompose(:compatibility, codepoints)))
- else
+ else
raise ArgumentError, "#{form} is not a valid normalization variant", caller
end.pack("U*".freeze)
end
diff --git a/activesupport/lib/active_support/number_helper/rounding_helper.rb b/activesupport/lib/active_support/number_helper/rounding_helper.rb
index a5b28296a2..2ad8d49c4e 100644
--- a/activesupport/lib/active_support/number_helper/rounding_helper.rb
+++ b/activesupport/lib/active_support/number_helper/rounding_helper.rb
@@ -36,7 +36,7 @@ module ActiveSupport
return 0 if number.zero?
digits = digit_count(number)
multiplier = 10**(digits - precision)
- (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier
+ (number / BigDecimal(multiplier.to_f.to_s)).round * multiplier
end
def convert_to_decimal(number)
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 8560eae110..605b50d346 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -10,9 +10,11 @@ module ActiveSupport
config.eager_load_namespaces << ActiveSupport
initializer "active_support.set_authenticated_message_encryption" do |app|
- if app.config.active_support.respond_to?(:use_authenticated_message_encryption)
- ActiveSupport::MessageEncryptor.use_authenticated_message_encryption =
- app.config.active_support.use_authenticated_message_encryption
+ config.after_initialize do
+ unless app.config.active_support.use_authenticated_message_encryption.nil?
+ ActiveSupport::MessageEncryptor.use_authenticated_message_encryption =
+ app.config.active_support.use_authenticated_message_encryption
+ end
end
end
@@ -66,5 +68,13 @@ module ActiveSupport
ActiveSupport.send(k, v) if ActiveSupport.respond_to? k
end
end
+
+ initializer "active_support.set_hash_digest_class" do |app|
+ config.after_initialize do
+ if app.config.active_support.use_sha1_digests
+ ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1
+ end
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
index 6cd11b1a26..b26d9c3665 100644
--- a/activesupport/lib/active_support/reloader.rb
+++ b/activesupport/lib/active_support/reloader.rb
@@ -28,14 +28,17 @@ module ActiveSupport
define_callbacks :class_unload
+ # Registers a callback that will run once at application startup and every time the code is reloaded.
def self.to_prepare(*args, &block)
set_callback(:prepare, *args, &block)
end
+ # Registers a callback that will run immediately before the classes are unloaded.
def self.before_class_unload(*args, &block)
set_callback(:class_unload, *args, &block)
end
+ # Registers a callback that will run immediately after the classes are unloaded.
def self.after_class_unload(*args, &block)
set_callback(:class_unload, :after, *args, &block)
end
diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
index b6b31ef140..20b6b9cd3f 100644
--- a/activesupport/lib/active_support/security_utils.rb
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -4,14 +4,12 @@ require "digest/sha2"
module ActiveSupport
module SecurityUtils
- # Constant time string comparison.
+ # Constant time string comparison, for fixed length strings.
#
# The values compared should be of fixed length, such as strings
- # that have already been processed by HMAC. This should not be used
- # on variable length plaintext strings because it could leak length info
- # via timing attacks.
- def secure_compare(a, b)
- return false unless a.bytesize == b.bytesize
+ # that have already been processed by HMAC. Raises in case of length mismatch.
+ def fixed_length_secure_compare(a, b)
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
l = a.unpack "C#{a.bytesize}"
@@ -19,11 +17,15 @@ module ActiveSupport
b.each_byte { |byte| res |= byte ^ l.shift }
res == 0
end
- module_function :secure_compare
+ module_function :fixed_length_secure_compare
- def variable_size_secure_compare(a, b) # :nodoc:
- secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b))
+ # Constant time string comparison, for variable length strings.
+ #
+ # The values are first processed by SHA256, so that we don't leak length info
+ # via timing attacks.
+ def secure_compare(a, b)
+ fixed_length_secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) && a == b
end
- module_function :variable_size_secure_compare
+ module_function :secure_compare
end
end
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index b24aa36ede..a891ff616d 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -58,6 +58,12 @@ module ActiveSupport
# post :create, params: { article: {...} }
# end
#
+ # A hash of expressions/numeric differences can also be passed in and evaluated.
+ #
+ # assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do
+ # post :create, params: { article: {...} }
+ # end
+ #
# A lambda or a list of lambdas can be passed in and evaluated:
#
# assert_difference ->{ Article.count }, 2 do
@@ -73,20 +79,28 @@ module ActiveSupport
# assert_difference 'Article.count', -1, 'An Article should be destroyed' do
# post :delete, params: { id: ... }
# end
- def assert_difference(expression, difference = 1, message = nil, &block)
- expressions = Array(expression)
-
- exps = expressions.map { |e|
+ def assert_difference(expression, *args, &block)
+ expressions =
+ if expression.is_a?(Hash)
+ message = args[0]
+ expression
+ else
+ difference = args[0] || 1
+ message = args[1]
+ Hash[Array(expression).map { |e| [e, difference] }]
+ end
+
+ exps = expressions.keys.map { |e|
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
}
before = exps.map(&:call)
retval = yield
- expressions.zip(exps).each_with_index do |(code, e), i|
- error = "#{code.inspect} didn't change by #{difference}"
+ expressions.zip(exps, before) do |(code, diff), exp, before_value|
+ error = "#{code.inspect} didn't change by #{diff}"
error = "#{message}.\n#{error}" if message
- assert_equal(before[i] + difference, e.call, error)
+ assert_equal(before_value + diff, exp.call, error)
end
retval
@@ -156,11 +170,12 @@ module ActiveSupport
after = exp.call
- if to == UNTRACKED
- error = "#{expression.inspect} didn't change"
- error = "#{message}.\n#{error}" if message
- assert before != after, error
- else
+ error = "#{expression.inspect} didn't change"
+ error = "#{error}. It was already #{to}" if before == to
+ error = "#{message}.\n#{error}" if message
+ assert before != after, error
+
+ unless to == UNTRACKED
error = "#{expression.inspect} didn't change to #{to}"
error = "#{message}.\n#{error}" if message
assert to === after, error
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index fa9bebb181..562f985f1b 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -45,7 +45,8 @@ module ActiveSupport
end
}
end
- result = Marshal.dump(dup)
+ test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
+ result = Marshal.dump(test_result)
end
write.puts [result].pack("m")
@@ -69,8 +70,9 @@ module ActiveSupport
if ENV["ISOLATION_TEST"]
yield
+ test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup
File.open(ENV["ISOLATION_OUTPUT"], "w") do |file|
- file.puts [Marshal.dump(dup)].pack("m")
+ file.puts [Marshal.dump(test_result)].pack("m")
end
exit!
else
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index 8c620e7f8c..998a51a34c 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require "active_support/core_ext/module/redefine_method"
require "active_support/core_ext/string/strip" # for strip_heredoc
require "active_support/core_ext/time/calculations"
require "concurrent/map"
@@ -43,7 +44,7 @@ module ActiveSupport
def unstub_object(stub)
singleton_class = stub.object.singleton_class
- singleton_class.send :undef_method, stub.method_name
+ singleton_class.send :silence_redefinition_of_method, stub.method_name
singleton_class.send :alias_method, stub.method_name, stub.original_method
singleton_class.send :undef_method, stub.original_method
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 4d1fbd4453..b75f5733b5 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -30,7 +30,7 @@ module ActiveSupport
class TimeZone
# Keys are Rails TimeZone names, values are TZInfo identifiers.
MAPPING = {
- "International Date Line West" => "Pacific/Midway",
+ "International Date Line West" => "Etc/GMT+12",
"Midway Island" => "Pacific/Midway",
"American Samoa" => "Pacific/Pago_Pago",
"Hawaii" => "Pacific/Honolulu",
@@ -238,7 +238,7 @@ module ActiveSupport
when Numeric, ActiveSupport::Duration
arg *= 3600 if arg.abs <= 13
all.find { |z| z.utc_offset == arg.to_i }
- else
+ else
raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}"
end
end
@@ -256,6 +256,13 @@ module ActiveSupport
@country_zones[code] ||= load_country_zones(code)
end
+ def clear #:nodoc:
+ @lazy_zones_map = Concurrent::Map.new
+ @country_zones = Concurrent::Map.new
+ @zones = nil
+ @zones_map = nil
+ end
+
private
def load_country_zones(code)
country = TZInfo::Country.get(code)
@@ -269,9 +276,8 @@ module ActiveSupport
end
def zones_map
- @zones_map ||= begin
- MAPPING.each_key { |place| self[place] } # load all the zones
- @lazy_zones_map
+ @zones_map ||= MAPPING.each_with_object({}) do |(name, _), zones|
+ zones[name] = self[name]
end
end
end
@@ -506,7 +512,7 @@ module ActiveSupport
# Available so that TimeZone instances respond like TZInfo::Timezone
# instances.
def period_for_local(time, dst = true)
- tzinfo.period_for_local(time, dst)
+ tzinfo.period_for_local(time, dst) { |periods| periods.last }
end
def periods_for_local(time) #:nodoc:
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 3ac11e0fe0..f214898145 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -38,4 +38,8 @@ class ActiveSupport::TestCase
private def jruby_skip(message = "")
skip message if defined?(JRUBY_VERSION)
end
+
+ def frozen_error_class
+ Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError
+ end
end
diff --git a/activesupport/test/array_inquirer_test.rb b/activesupport/test/array_inquirer_test.rb
index d5419b862d..9a260edd8a 100644
--- a/activesupport/test/array_inquirer_test.rb
+++ b/activesupport/test/array_inquirer_test.rb
@@ -9,9 +9,9 @@ class ArrayInquirerTest < ActiveSupport::TestCase
end
def test_individual
- assert @array_inquirer.mobile?
- assert @array_inquirer.tablet?
- assert_not @array_inquirer.desktop?
+ assert_predicate @array_inquirer, :mobile?
+ assert_predicate @array_inquirer, :tablet?
+ assert_not_predicate @array_inquirer, :desktop?
end
def test_any
diff --git a/activesupport/test/benchmarkable_test.rb b/activesupport/test/benchmarkable_test.rb
index 424da0a52c..cb7a69cccf 100644
--- a/activesupport/test/benchmarkable_test.rb
+++ b/activesupport/test/benchmarkable_test.rb
@@ -26,7 +26,7 @@ class BenchmarkableTest < ActiveSupport::TestCase
def test_without_block
assert_raise(LocalJumpError) { benchmark }
- assert buffer.empty?
+ assert_empty buffer
end
def test_defaults
diff --git a/activesupport/test/cache/behaviors.rb b/activesupport/test/cache/behaviors.rb
index cb08a10bba..2d39976be3 100644
--- a/activesupport/test/cache/behaviors.rb
+++ b/activesupport/test/cache/behaviors.rb
@@ -3,7 +3,10 @@
require_relative "behaviors/autoloading_cache_behavior"
require_relative "behaviors/cache_delete_matched_behavior"
require_relative "behaviors/cache_increment_decrement_behavior"
+require_relative "behaviors/cache_instrumentation_behavior"
require_relative "behaviors/cache_store_behavior"
require_relative "behaviors/cache_store_version_behavior"
+require_relative "behaviors/connection_pool_behavior"
require_relative "behaviors/encoded_key_cache_behavior"
+require_relative "behaviors/failure_safety_behavior"
require_relative "behaviors/local_cache_behavior"
diff --git a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb
index 2fa2d7af88..16b7abc679 100644
--- a/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb
+++ b/activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb
@@ -8,7 +8,9 @@ module CacheIncrementDecrementBehavior
assert_equal 2, @cache.read("foo").to_i
assert_equal 3, @cache.increment("foo")
assert_equal 3, @cache.read("foo").to_i
- assert_nil @cache.increment("bar")
+
+ missing = @cache.increment("bar")
+ assert(missing.nil? || missing == 1)
end
def test_decrement
@@ -18,6 +20,8 @@ module CacheIncrementDecrementBehavior
assert_equal 2, @cache.read("foo").to_i
assert_equal 1, @cache.decrement("foo")
assert_equal 1, @cache.read("foo").to_i
- assert_nil @cache.decrement("bar")
+
+ missing = @cache.decrement("bar")
+ assert(missing.nil? || missing == -1)
end
end
diff --git a/activesupport/test/cache/cache_store_write_multi_test.rb b/activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb
index 5b6fd678c5..4e8ff60eb3 100644
--- a/activesupport/test/cache/cache_store_write_multi_test.rb
+++ b/activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb
@@ -1,28 +1,15 @@
# frozen_string_literal: true
-require "abstract_unit"
-require "active_support/cache"
-
-class CacheStoreWriteMultiEntriesStoreProviderInterfaceTest < ActiveSupport::TestCase
- setup do
- @cache = ActiveSupport::Cache.lookup_store(:null_store)
- end
-
- test "fetch_multi uses write_multi_entries store provider interface" do
+module CacheInstrumentationBehavior
+ def test_fetch_multi_uses_write_multi_entries_store_provider_interface
assert_called_with(@cache, :write_multi_entries) do
@cache.fetch_multi "a", "b", "c" do |key|
key * 2
end
end
end
-end
-class CacheStoreWriteMultiInstrumentationTest < ActiveSupport::TestCase
- setup do
- @cache = ActiveSupport::Cache.lookup_store(:null_store)
- end
-
- test "instrumentation" do
+ def test_write_multi_instrumentation
writes = { "a" => "aa", "b" => "bb" }
events = with_instrumentation "write_multi" do
@@ -34,16 +21,27 @@ class CacheStoreWriteMultiInstrumentationTest < ActiveSupport::TestCase
assert_equal({ "a" => "aa", "b" => "bb" }, events[0].payload[:key])
end
- test "instrumentation with fetch_multi as super operation" do
- skip "fetch_multi isn't instrumented yet"
+ def test_instrumentation_with_fetch_multi_as_super_operation
+ @cache.write("b", "bb")
- events = with_instrumentation "write_multi" do
+ events = with_instrumentation "read_multi" do
@cache.fetch_multi("a", "b") { |key| key * 2 }
end
- assert_equal %w[ cache_write_multi.active_support ], events.map(&:name)
- assert_nil events[0].payload[:super_operation]
- assert !events[0].payload[:hit]
+ assert_equal %w[ cache_read_multi.active_support ], events.map(&:name)
+ assert_equal :fetch_multi, events[0].payload[:super_operation]
+ assert_equal ["b"], events[0].payload[:hits]
+ end
+
+ def test_read_multi_instrumentation
+ @cache.write("b", "bb")
+
+ events = with_instrumentation "read_multi" do
+ @cache.read_multi("a", "b") { |key| key * 2 }
+ end
+
+ assert_equal %w[ cache_read_multi.active_support ], events.map(&:name)
+ assert_equal ["b"], events[0].payload[:hits]
end
private
diff --git a/activesupport/test/cache/behaviors/cache_store_behavior.rb b/activesupport/test/cache/behaviors/cache_store_behavior.rb
index 582e902f72..efb57d34a2 100644
--- a/activesupport/test/cache/behaviors/cache_store_behavior.rb
+++ b/activesupport/test/cache/behaviors/cache_store_behavior.rb
@@ -113,6 +113,16 @@ module CacheStoreBehavior
assert_equal("fufu", @cache.read("fu"))
end
+ def test_fetch_multi_without_expires_in
+ @cache.write("foo", "bar")
+ @cache.write("fud", "biz")
+
+ values = @cache.fetch_multi("foo", "fu", "fud", expires_in: nil) { |value| value * 2 }
+
+ assert_equal({ "foo" => "bar", "fu" => "fufu", "fud" => "biz" }, values)
+ assert_equal("fufu", @cache.read("fu"))
+ end
+
def test_multi_with_objects
cache_struct = Struct.new(:cache_key, :title)
foo = cache_struct.new("foo", "FOO!")
@@ -146,6 +156,16 @@ module CacheStoreBehavior
assert_nil @cache.read("foo")
end
+ def test_read_and_write_uncompressed_small_data
+ @cache.write("foo", "bar", compress: false)
+ assert_equal "bar", @cache.read("foo")
+ end
+
+ def test_read_and_write_uncompressed_nil
+ @cache.write("foo", nil, compress: false)
+ assert_nil @cache.read("foo")
+ end
+
def test_cache_key
obj = Object.new
def obj.cache_key
@@ -164,6 +184,18 @@ module CacheStoreBehavior
assert_equal "bar", @cache.read("foo")
end
+ def test_unversioned_cache_key
+ obj = Object.new
+ def obj.cache_key
+ "foo"
+ end
+ def obj.cache_key_with_version
+ "foo-v1"
+ end
+ @cache.write(obj, "bar")
+ assert_equal "bar", @cache.read("foo")
+ end
+
def test_array_as_cache_key
@cache.write([:fu, "foo"], "bar")
assert_equal "bar", @cache.read("fu/foo")
@@ -287,8 +319,7 @@ module CacheStoreBehavior
end
def test_really_long_keys
- key = "".dup
- 900.times { key << "x" }
+ key = "x" * 2048
assert @cache.write(key, "bar")
assert_equal "bar", @cache.read(key)
assert_equal "bar", @cache.fetch(key)
diff --git a/activesupport/test/cache/behaviors/cache_store_version_behavior.rb b/activesupport/test/cache/behaviors/cache_store_version_behavior.rb
index c2e4d046af..62c0bb4f6f 100644
--- a/activesupport/test/cache/behaviors/cache_store_version_behavior.rb
+++ b/activesupport/test/cache/behaviors/cache_store_version_behavior.rb
@@ -65,7 +65,7 @@ module CacheStoreVersionBehavior
m1v2 = ModelWithKeyAndVersion.new("model/1", 2)
@cache.write(m1v1, "bar")
- assert @cache.exist?(m1v1)
+ assert @cache.exist?(m1v1)
assert_not @cache.fetch(m1v2)
end
diff --git a/activesupport/test/cache/behaviors/connection_pool_behavior.rb b/activesupport/test/cache/behaviors/connection_pool_behavior.rb
new file mode 100644
index 0000000000..0d46f88552
--- /dev/null
+++ b/activesupport/test/cache/behaviors/connection_pool_behavior.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module ConnectionPoolBehavior
+ def test_connection_pool
+ Thread.report_on_exception, original_report_on_exception = false, Thread.report_on_exception if Thread.respond_to?(:report_on_exception)
+
+ emulating_latency do
+ begin
+ cache = ActiveSupport::Cache.lookup_store(store, pool_size: 2, pool_timeout: 1)
+ cache.clear
+
+ threads = []
+
+ assert_raises Timeout::Error do
+ # One of the three threads will fail in 1 second because our pool size
+ # is only two.
+ 3.times do
+ threads << Thread.new do
+ cache.read("latency")
+ end
+ end
+
+ threads.each(&:join)
+ end
+ ensure
+ threads.each(&:kill)
+ end
+ end
+ ensure
+ Thread.report_on_exception = original_report_on_exception if Thread.respond_to?(:report_on_exception)
+ end
+
+ def test_no_connection_pool
+ emulating_latency do
+ begin
+ cache = ActiveSupport::Cache.lookup_store(store)
+ cache.clear
+
+ threads = []
+
+ assert_nothing_raised do
+ # Default connection pool size is 5, assuming 10 will make sure that
+ # the connection pool isn't used at all.
+ 10.times do
+ threads << Thread.new do
+ cache.read("latency")
+ end
+ end
+
+ threads.each(&:join)
+ end
+ ensure
+ threads.each(&:kill)
+ end
+ end
+ end
+end
diff --git a/activesupport/test/cache/behaviors/failure_safety_behavior.rb b/activesupport/test/cache/behaviors/failure_safety_behavior.rb
new file mode 100644
index 0000000000..53bda4f942
--- /dev/null
+++ b/activesupport/test/cache/behaviors/failure_safety_behavior.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+module FailureSafetyBehavior
+ def test_fetch_read_failure_returns_nil
+ @cache.write("foo", "bar")
+
+ emulating_unavailability do |cache|
+ assert_nil cache.fetch("foo")
+ end
+ end
+
+ def test_fetch_read_failure_does_not_attempt_to_write
+ end
+
+ def test_read_failure_returns_nil
+ @cache.write("foo", "bar")
+
+ emulating_unavailability do |cache|
+ assert_nil cache.read("foo")
+ end
+ end
+
+ def test_read_multi_failure_returns_empty_hash
+ @cache.write_multi("foo" => "bar", "baz" => "quux")
+
+ emulating_unavailability do |cache|
+ assert_equal Hash.new, cache.read_multi("foo", "baz")
+ end
+ end
+
+ def test_write_failure_returns_false
+ emulating_unavailability do |cache|
+ assert_equal false, cache.write("foo", "bar")
+ end
+ end
+
+ def test_write_multi_failure_not_raises
+ emulating_unavailability do |cache|
+ assert_nothing_raised do
+ cache.write_multi("foo" => "bar", "baz" => "quux")
+ end
+ end
+ end
+
+ def test_fetch_multi_failure_returns_fallback_results
+ @cache.write_multi("foo" => "bar", "baz" => "quux")
+
+ emulating_unavailability do |cache|
+ fetched = cache.fetch_multi("foo", "baz") { |k| "unavailable" }
+ assert_equal Hash["foo" => "unavailable", "baz" => "unavailable"], fetched
+ end
+ end
+
+ def test_delete_failure_returns_false
+ @cache.write("foo", "bar")
+
+ emulating_unavailability do |cache|
+ assert_equal false, cache.delete("foo")
+ end
+ end
+
+ def test_exist_failure_returns_false
+ @cache.write("foo", "bar")
+
+ emulating_unavailability do |cache|
+ assert !cache.exist?("foo")
+ end
+ end
+
+ def test_increment_failure_returns_nil
+ @cache.write("foo", 1, raw: true)
+
+ emulating_unavailability do |cache|
+ assert_nil cache.increment("foo")
+ end
+ end
+
+ def test_decrement_failure_returns_nil
+ @cache.write("foo", 1, raw: true)
+
+ emulating_unavailability do |cache|
+ assert_nil cache.decrement("foo")
+ end
+ end
+
+ def test_clear_failure_returns_nil
+ emulating_unavailability do |cache|
+ assert_nil cache.clear
+ end
+ end
+end
diff --git a/activesupport/test/cache/behaviors/local_cache_behavior.rb b/activesupport/test/cache/behaviors/local_cache_behavior.rb
index 8dec8090b1..f7302df4c8 100644
--- a/activesupport/test/cache/behaviors/local_cache_behavior.rb
+++ b/activesupport/test/cache/behaviors/local_cache_behavior.rb
@@ -20,7 +20,11 @@ module LocalCacheBehavior
end
def test_cleanup_clears_local_cache_but_not_remote_cache
- skip unless @cache.class.instance_methods(false).include?(:cleanup)
+ begin
+ @cache.cleanup
+ rescue NotImplementedError
+ skip
+ end
@cache.with_local_cache do
@cache.write("foo", "bar")
diff --git a/activesupport/test/cache/cache_entry_test.rb b/activesupport/test/cache/cache_entry_test.rb
index 51b214ad8f..80ff7ad564 100644
--- a/activesupport/test/cache/cache_entry_test.rb
+++ b/activesupport/test/cache/cache_entry_test.rb
@@ -14,16 +14,23 @@ class CacheEntryTest < ActiveSupport::TestCase
end
end
- def test_compress_values
+ def test_compressed_values
value = "value" * 100
entry = ActiveSupport::Cache::Entry.new(value, compress: true, compress_threshold: 1)
assert_equal value, entry.value
assert(value.bytesize > entry.size, "value is compressed")
end
- def test_non_compress_values
+ def test_compressed_by_default
value = "value" * 100
- entry = ActiveSupport::Cache::Entry.new(value)
+ entry = ActiveSupport::Cache::Entry.new(value, compress_threshold: 1)
+ assert_equal value, entry.value
+ assert(value.bytesize > entry.size, "value is compressed")
+ end
+
+ def test_uncompressed_values
+ value = "value" * 100
+ entry = ActiveSupport::Cache::Entry.new(value, compress: false)
assert_equal value, entry.value
assert_equal value.bytesize, entry.size
end
diff --git a/activesupport/test/cache/cache_store_logger_test.rb b/activesupport/test/cache/cache_store_logger_test.rb
index 1af6893cc9..4648b2d361 100644
--- a/activesupport/test/cache/cache_store_logger_test.rb
+++ b/activesupport/test/cache/cache_store_logger_test.rb
@@ -13,7 +13,7 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase
def test_logging
@cache.fetch("foo") { "bar" }
- assert @buffer.string.present?
+ assert_predicate @buffer.string, :present?
end
def test_log_with_string_namespace
@@ -31,6 +31,6 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase
def test_mute_logging
@cache.mute { @cache.fetch("foo") { "bar" } }
- assert @buffer.string.blank?
+ assert_predicate @buffer.string, :blank?
end
end
diff --git a/activesupport/test/cache/stores/file_store_test.rb b/activesupport/test/cache/stores/file_store_test.rb
index 66231b0a82..c3c35a7bcc 100644
--- a/activesupport/test/cache/stores/file_store_test.rb
+++ b/activesupport/test/cache/stores/file_store_test.rb
@@ -30,6 +30,7 @@ class FileStoreTest < ActiveSupport::TestCase
include LocalCacheBehavior
include CacheDeleteMatchedBehavior
include CacheIncrementDecrementBehavior
+ include CacheInstrumentationBehavior
include AutoloadingCacheBehavior
def test_clear
@@ -98,13 +99,13 @@ class FileStoreTest < ActiveSupport::TestCase
end
assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!"
assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!"
- assert Dir.entries(sub_cache_dir).reject { |f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f) }.empty?
+ assert_empty Dir.entries(sub_cache_dir).reject { |f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f) }
end
def test_log_exception_when_cache_read_fails
File.stub(:exist?, -> { raise StandardError.new("failed") }) do
@cache.send(:read_entry, "winston", {})
- assert @buffer.string.present?
+ assert_predicate @buffer.string, :present?
end
end
diff --git a/activesupport/test/cache/stores/mem_cache_store_test.rb b/activesupport/test/cache/stores/mem_cache_store_test.rb
index 1b73fb65eb..f426a37c66 100644
--- a/activesupport/test/cache/stores/mem_cache_store_test.rb
+++ b/activesupport/test/cache/stores/mem_cache_store_test.rb
@@ -5,6 +5,24 @@ require "active_support/cache"
require_relative "../behaviors"
require "dalli"
+# Emulates a latency on Dalli's back-end for the key latency to facilitate
+# connection pool testing.
+class SlowDalliClient < Dalli::Client
+ def get(key, options = {})
+ if key =~ /latency/
+ sleep 3
+ else
+ super
+ end
+ end
+end
+
+class UnavailableDalliServer < Dalli::Server
+ def alive?
+ false
+ end
+end
+
class MemCacheStoreTest < ActiveSupport::TestCase
begin
ss = Dalli::Client.new("localhost:11211").stats
@@ -31,8 +49,11 @@ class MemCacheStoreTest < ActiveSupport::TestCase
include CacheStoreVersionBehavior
include LocalCacheBehavior
include CacheIncrementDecrementBehavior
+ include CacheInstrumentationBehavior
include EncodedKeyCacheBehavior
include AutoloadingCacheBehavior
+ include ConnectionPoolBehavior
+ include FailureSafetyBehavior
def test_raw_values
cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
@@ -57,6 +78,22 @@ class MemCacheStoreTest < ActiveSupport::TestCase
end
end
+ def test_increment_expires_in
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ assert_called_with cache.instance_variable_get(:@data), :incr, [ "foo", 1, 60 ] do
+ cache.increment("foo", 1, expires_in: 60)
+ end
+ end
+
+ def test_decrement_expires_in
+ cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
+ cache.clear
+ assert_called_with cache.instance_variable_get(:@data), :decr, [ "foo", 1, 60 ] do
+ cache.decrement("foo", 1, expires_in: 60)
+ end
+ end
+
def test_local_cache_raw_values_with_marshal
cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, raw: true)
cache.clear
@@ -73,4 +110,30 @@ class MemCacheStoreTest < ActiveSupport::TestCase
value << "bingo"
assert_not_equal value, @cache.read("foo")
end
+
+ private
+
+ def store
+ :mem_cache_store
+ end
+
+ def emulating_latency
+ old_client = Dalli.send(:remove_const, :Client)
+ Dalli.const_set(:Client, SlowDalliClient)
+
+ yield
+ ensure
+ Dalli.send(:remove_const, :Client)
+ Dalli.const_set(:Client, old_client)
+ end
+
+ def emulating_unavailability
+ old_server = Dalli.send(:remove_const, :Server)
+ Dalli.const_set(:Server, UnavailableDalliServer)
+
+ yield ActiveSupport::Cache::MemCacheStore.new
+ ensure
+ Dalli.send(:remove_const, :Server)
+ Dalli.const_set(:Server, old_server)
+ end
end
diff --git a/activesupport/test/cache/stores/memory_store_test.rb b/activesupport/test/cache/stores/memory_store_test.rb
index 3981f05331..72fafc187b 100644
--- a/activesupport/test/cache/stores/memory_store_test.rb
+++ b/activesupport/test/cache/stores/memory_store_test.rb
@@ -14,6 +14,7 @@ class MemoryStoreTest < ActiveSupport::TestCase
include CacheStoreVersionBehavior
include CacheDeleteMatchedBehavior
include CacheIncrementDecrementBehavior
+ include CacheInstrumentationBehavior
def test_prune_size
@cache.write(1, "aaaaaaaaaa") && sleep(0.001)
diff --git a/activesupport/test/cache/stores/redis_cache_store_test.rb b/activesupport/test/cache/stores/redis_cache_store_test.rb
new file mode 100644
index 0000000000..9daa0b8796
--- /dev/null
+++ b/activesupport/test/cache/stores/redis_cache_store_test.rb
@@ -0,0 +1,173 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/cache"
+require "active_support/cache/redis_cache_store"
+require_relative "../behaviors"
+
+module ActiveSupport::Cache::RedisCacheStoreTests
+ DRIVER = %w[ ruby hiredis ].include?(ENV["REDIS_DRIVER"]) ? ENV["REDIS_DRIVER"] : "hiredis"
+
+ class LookupTest < ActiveSupport::TestCase
+ test "may be looked up as :redis_cache_store" do
+ assert_kind_of ActiveSupport::Cache::RedisCacheStore,
+ ActiveSupport::Cache.lookup_store(:redis_cache_store)
+ end
+ end
+
+ class InitializationTest < ActiveSupport::TestCase
+ test "omitted URL uses Redis client with default settings" do
+ assert_called_with Redis, :new, [
+ url: nil,
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0, driver: DRIVER
+ ] do
+ build
+ end
+ end
+
+ test "no URLs uses Redis client with default settings" do
+ assert_called_with Redis, :new, [
+ url: nil,
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0, driver: DRIVER
+ ] do
+ build url: []
+ end
+ end
+
+ test "singular URL uses Redis client" do
+ assert_called_with Redis, :new, [
+ url: "redis://localhost:6379/0",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0, driver: DRIVER
+ ] do
+ build url: "redis://localhost:6379/0"
+ end
+ end
+
+ test "one URL uses Redis client" do
+ assert_called_with Redis, :new, [
+ url: "redis://localhost:6379/0",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0, driver: DRIVER
+ ] do
+ build url: %w[ redis://localhost:6379/0 ]
+ end
+ end
+
+ test "multiple URLs uses Redis::Distributed client" do
+ assert_called_with Redis, :new, [
+ [ url: "redis://localhost:6379/0",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0, driver: DRIVER ],
+ [ url: "redis://localhost:6379/1",
+ connect_timeout: 20, read_timeout: 1, write_timeout: 1,
+ reconnect_attempts: 0, driver: DRIVER ],
+ ], returns: Redis.new do
+ @cache = build url: %w[ redis://localhost:6379/0 redis://localhost:6379/1 ]
+ assert_kind_of ::Redis::Distributed, @cache.redis
+ end
+ end
+
+ test "block argument uses yielded client" do
+ block = -> { :custom_redis_client }
+ assert_called block, :call do
+ build redis: block
+ end
+ end
+
+ private
+ def build(**kwargs)
+ ActiveSupport::Cache::RedisCacheStore.new(driver: DRIVER, **kwargs).tap do |cache|
+ cache.redis
+ end
+ end
+ end
+
+ class StoreTest < ActiveSupport::TestCase
+ setup do
+ @namespace = "namespace"
+
+ @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace, expires_in: 60, driver: DRIVER)
+ # @cache.logger = Logger.new($stdout) # For test debugging
+
+ # For LocalCacheBehavior tests
+ @peek = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, namespace: @namespace, driver: DRIVER)
+ end
+
+ teardown do
+ @cache.clear
+ @cache.redis.disconnect!
+ end
+ end
+
+ class RedisCacheStoreCommonBehaviorTest < StoreTest
+ include CacheStoreBehavior
+ include CacheStoreVersionBehavior
+ include LocalCacheBehavior
+ include CacheIncrementDecrementBehavior
+ include CacheInstrumentationBehavior
+ include AutoloadingCacheBehavior
+
+ def test_fetch_multi_uses_redis_mget
+ assert_called(@cache.redis, :mget, returns: []) do
+ @cache.fetch_multi("a", "b", "c") do |key|
+ key * 2
+ end
+ end
+ end
+ end
+
+ # Separate test class so we can omit the namespace which causes expected,
+ # appropriate complaints about incompatible string encodings.
+ class KeyEncodingSafetyTest < StoreTest
+ include EncodedKeyCacheBehavior
+
+ setup do
+ @cache = ActiveSupport::Cache::RedisCacheStore.new(timeout: 0.1, driver: DRIVER)
+ @cache.logger = nil
+ end
+ end
+
+ class StoreAPITest < StoreTest
+ end
+
+ class UnavailableRedisClient < Redis::Client
+ def ensure_connected
+ raise Redis::BaseConnectionError
+ end
+ end
+
+ class FailureSafetyTest < StoreTest
+ include FailureSafetyBehavior
+
+ private
+
+ def emulating_unavailability
+ old_client = Redis.send(:remove_const, :Client)
+ Redis.const_set(:Client, UnavailableRedisClient)
+
+ yield ActiveSupport::Cache::RedisCacheStore.new
+ ensure
+ Redis.send(:remove_const, :Client)
+ Redis.const_set(:Client, old_client)
+ end
+ end
+
+ class DeleteMatchedTest < StoreTest
+ test "deletes keys matching glob" do
+ @cache.write("foo", "bar")
+ @cache.write("fu", "baz")
+ @cache.delete_matched("foo*")
+ assert !@cache.exist?("foo")
+ assert @cache.exist?("fu")
+ end
+
+ test "fails with regexp matchers" do
+ assert_raise ArgumentError do
+ @cache.delete_matched(/OO/i)
+ end
+ end
+ end
+end
diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb
index 67813a749e..015e17deb9 100644
--- a/activesupport/test/callback_inheritance_test.rb
+++ b/activesupport/test/callback_inheritance_test.rb
@@ -164,10 +164,10 @@ end
class DynamicInheritedCallbacks < ActiveSupport::TestCase
def test_callbacks_looks_to_the_superclass_before_running
child = EmptyChild.new.dispatch
- assert !child.performed?
+ assert_not_predicate child, :performed?
EmptyParent.set_callback :dispatch, :before, :perform!
child = EmptyChild.new.dispatch
- assert child.performed?
+ assert_predicate child, :performed?
end
def test_callbacks_should_be_performed_once_in_child_class
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 5f894db46f..30f1632460 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -77,7 +77,7 @@ module CallbacksTest
skip_callback :save, :after, :after_save_method, unless: :yes
skip_callback :save, :after, :after_save_method, if: :no
skip_callback :save, :before, :before_save_method, unless: :no
- skip_callback :save, :before, CallbackClass , if: :yes
+ skip_callback :save, :before, CallbackClass, if: :yes
def yes; true; end
def no; false; end
end
diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb
index 7b97028e8c..8cfcaedafd 100644
--- a/activesupport/test/class_cache_test.rb
+++ b/activesupport/test/class_cache_test.rb
@@ -11,17 +11,17 @@ module ActiveSupport
end
def test_empty?
- assert @cache.empty?
+ assert_empty @cache
@cache.store(ClassCacheTest)
- assert !@cache.empty?
+ assert_not_empty @cache
end
def test_clear!
- assert @cache.empty?
+ assert_empty @cache
@cache.store(ClassCacheTest)
- assert !@cache.empty?
+ assert_not_empty @cache
@cache.clear!
- assert @cache.empty?
+ assert_empty @cache
end
def test_set_key
@@ -40,29 +40,29 @@ module ActiveSupport
end
def test_get_constantizes
- assert @cache.empty?
+ assert_empty @cache
assert_equal ClassCacheTest, @cache.get(ClassCacheTest.name)
end
def test_get_constantizes_fails_on_invalid_names
- assert @cache.empty?
+ assert_empty @cache
assert_raise NameError do
@cache.get("OmgTotallyInvalidConstantName")
end
end
def test_get_alias
- assert @cache.empty?
+ assert_empty @cache
assert_equal @cache[ClassCacheTest.name], @cache.get(ClassCacheTest.name)
end
def test_safe_get_constantizes
- assert @cache.empty?
+ assert_empty @cache
assert_equal ClassCacheTest, @cache.safe_get(ClassCacheTest.name)
end
def test_safe_get_constantizes_doesnt_fail_on_invalid_names
- assert @cache.empty?
+ assert_empty @cache
assert_nil @cache.safe_get("OmgTotallyInvalidConstantName")
end
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index ef75a320d1..98d8f3ee0d 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -91,7 +91,7 @@ class ConcernTest < ActiveSupport::TestCase
end
end
@klass.include test_module
- assert_equal false, Object.respond_to?(:test)
+ assert_not_respond_to Object, :test
Qux.class_eval do
remove_const :ClassMethods
end
diff --git a/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb b/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb
new file mode 100644
index 0000000000..2d0f45ec5f
--- /dev/null
+++ b/activesupport/test/concurrency/load_interlock_aware_monitor_test.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "concurrent/atomic/count_down_latch"
+require "active_support/concurrency/load_interlock_aware_monitor"
+
+module ActiveSupport
+ module Concurrency
+ class LoadInterlockAwareMonitorTest < ActiveSupport::TestCase
+ def setup
+ @monitor = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
+ end
+
+ def test_entering_with_no_blocking
+ assert @monitor.mon_enter
+ end
+
+ def test_entering_with_blocking
+ load_interlock_latch = Concurrent::CountDownLatch.new
+ monitor_latch = Concurrent::CountDownLatch.new
+
+ able_to_use_monitor = false
+ able_to_load = false
+
+ thread_with_load_interlock = Thread.new do
+ ActiveSupport::Dependencies.interlock.running do
+ load_interlock_latch.count_down
+ monitor_latch.wait
+
+ @monitor.synchronize do
+ able_to_use_monitor = true
+ end
+ end
+ end
+
+ thread_with_monitor_lock = Thread.new do
+ @monitor.synchronize do
+ monitor_latch.count_down
+ load_interlock_latch.wait
+
+ ActiveSupport::Dependencies.interlock.loading do
+ able_to_load = true
+ end
+ end
+ end
+
+ thread_with_load_interlock.join
+ thread_with_monitor_lock.join
+
+ assert able_to_use_monitor
+ assert able_to_load
+ end
+ end
+ end
+end
diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb
index 10719596df..1cf40261dc 100644
--- a/activesupport/test/configurable_test.rb
+++ b/activesupport/test/configurable_test.rb
@@ -43,11 +43,11 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase
test "configuration accessors are not available on instance" do
instance = Parent.new
- assert !instance.respond_to?(:bar)
- assert !instance.respond_to?(:bar=)
+ assert_not_respond_to instance, :bar
+ assert_not_respond_to instance, :bar=
- assert !instance.respond_to?(:baz)
- assert !instance.respond_to?(:baz=)
+ assert_not_respond_to instance, :baz
+ assert_not_respond_to instance, :baz=
end
test "configuration accessors can take a default value" do
diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb
index 0f919efcb0..0a7c43d421 100644
--- a/activesupport/test/core_ext/array/conversions_test.rb
+++ b/activesupport/test/core_ext/array/conversions_test.rb
@@ -90,7 +90,7 @@ class ToXmlTest < ActiveSupport::TestCase
def test_to_xml_with_hash_elements
xml = [
{ name: "David", age: 26, age_in_millis: 820497600000 },
- { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") }
+ { name: "Jason", age: 31, age_in_millis: BigDecimal("1.0") }
].to_xml(skip_instruct: true, indent: 0)
assert_equal '<objects type="array"><object>', xml.first(30)
@@ -173,7 +173,7 @@ class ToXmlTest < ActiveSupport::TestCase
def test_to_xml_with_instruct
xml = [
{ name: "David", age: 26, age_in_millis: 820497600000 },
- { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") }
+ { name: "Jason", age: 31, age_in_millis: BigDecimal("1.0") }
].to_xml(skip_instruct: false, indent: 0)
assert_match(/^<\?xml [^>]*/, xml)
@@ -183,7 +183,7 @@ class ToXmlTest < ActiveSupport::TestCase
def test_to_xml_with_block
xml = [
{ name: "David", age: 26, age_in_millis: 820497600000 },
- { name: "Jason", age: 31, age_in_millis: BigDecimal.new("1.0") }
+ { name: "Jason", age: 31, age_in_millis: BigDecimal("1.0") }
].to_xml(skip_instruct: true, indent: 0) do |builder|
builder.count 2
end
diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb
index da9d4963d8..c182b91826 100644
--- a/activesupport/test/core_ext/array/grouping_test.rb
+++ b/activesupport/test/core_ext/array/grouping_test.rb
@@ -116,7 +116,7 @@ class SplitTest < ActiveSupport::TestCase
def test_split_with_block
a = (1..10).to_a
assert_equal [[1, 2], [4, 5], [7, 8], [10]], a.split { |i| i % 3 == 0 }
- assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9 , 10], a
+ assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], a
end
def test_split_with_edge_values
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index 66e81f1162..62588be33b 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -5,7 +5,7 @@ require "active_support/core_ext/big_decimal"
class BigDecimalTest < ActiveSupport::TestCase
def test_to_s
- bd = BigDecimal.new "0.01"
+ bd = BigDecimal "0.01"
assert_equal "0.01", bd.to_s
assert_equal "+0.01", bd.to_s("+F")
assert_equal "+0.0 1", bd.to_s("+1F")
diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb
index 9cc006fc63..5ea288738e 100644
--- a/activesupport/test/core_ext/class_test.rb
+++ b/activesupport/test/core_ext/class_test.rb
@@ -30,11 +30,11 @@ class ClassTest < ActiveSupport::TestCase
def test_descendants_excludes_singleton_classes
klass = Parent.new.singleton_class
- refute Parent.descendants.include?(klass), "descendants should not include singleton classes"
+ assert_not Parent.descendants.include?(klass), "descendants should not include singleton classes"
end
def test_subclasses_excludes_singleton_classes
klass = Parent.new.singleton_class
- refute Parent.subclasses.include?(klass), "subclasses should not include singleton classes"
+ assert_not Parent.subclasses.include?(klass), "subclasses should not include singleton classes"
end
end
diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb
index 42da6f6cd0..1422f135a8 100644
--- a/activesupport/test/core_ext/date_and_time_behavior.rb
+++ b/activesupport/test/core_ext/date_and_time_behavior.rb
@@ -297,24 +297,24 @@ module DateAndTimeBehavior
def test_beginning_of_week
assert_equal date_time_init(2005, 1, 31, 0, 0, 0), date_time_init(2005, 2, 4, 10, 10, 10).beginning_of_week
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 28, 0, 0, 0).beginning_of_week #monday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 29, 0, 0, 0).beginning_of_week #tuesday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 30, 0, 0, 0).beginning_of_week #wednesday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 01, 0, 0, 0).beginning_of_week #thursday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 02, 0, 0, 0).beginning_of_week #friday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 03, 0, 0, 0).beginning_of_week #saturday
- assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 04, 0, 0, 0).beginning_of_week #sunday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 28, 0, 0, 0).beginning_of_week # monday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 29, 0, 0, 0).beginning_of_week # tuesday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 11, 30, 0, 0, 0).beginning_of_week # wednesday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 01, 0, 0, 0).beginning_of_week # thursday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 02, 0, 0, 0).beginning_of_week # friday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 03, 0, 0, 0).beginning_of_week # saturday
+ assert_equal date_time_init(2005, 11, 28, 0, 0, 0), date_time_init(2005, 12, 04, 0, 0, 0).beginning_of_week # sunday
end
def test_end_of_week
assert_equal date_time_init(2008, 1, 6, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 12, 31, 10, 10, 10).end_of_week
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 27, 0, 0, 0).end_of_week #monday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 28, 0, 0, 0).end_of_week #tuesday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 29, 0, 0, 0).end_of_week #wednesday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 30, 0, 0, 0).end_of_week #thursday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 31, 0, 0, 0).end_of_week #friday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 01, 0, 0, 0).end_of_week #saturday
- assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 02, 0, 0, 0).end_of_week #sunday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 27, 0, 0, 0).end_of_week # monday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 28, 0, 0, 0).end_of_week # tuesday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 29, 0, 0, 0).end_of_week # wednesday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 30, 0, 0, 0).end_of_week # thursday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 8, 31, 0, 0, 0).end_of_week # friday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 01, 0, 0, 0).end_of_week # saturday
+ assert_equal date_time_init(2007, 9, 2, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 9, 02, 0, 0, 0).end_of_week # sunday
end
def test_end_of_month
@@ -328,6 +328,26 @@ module DateAndTimeBehavior
assert_equal date_time_init(2007, 12, 31, 23, 59, 59, Rational(999999999, 1000)), date_time_init(2007, 12, 31, 10, 10, 10).end_of_year
end
+ def test_next_occurring
+ assert_equal date_time_init(2017, 12, 18, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:monday)
+ assert_equal date_time_init(2017, 12, 19, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:tuesday)
+ assert_equal date_time_init(2017, 12, 20, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:wednesday)
+ assert_equal date_time_init(2017, 12, 21, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:thursday)
+ assert_equal date_time_init(2017, 12, 15, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:friday)
+ assert_equal date_time_init(2017, 12, 16, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:saturday)
+ assert_equal date_time_init(2017, 12, 17, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).next_occurring(:sunday)
+ end
+
+ def test_prev_occurring
+ assert_equal date_time_init(2017, 12, 11, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:monday)
+ assert_equal date_time_init(2017, 12, 12, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:tuesday)
+ assert_equal date_time_init(2017, 12, 13, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:wednesday)
+ assert_equal date_time_init(2017, 12, 7, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:thursday)
+ assert_equal date_time_init(2017, 12, 8, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:friday)
+ assert_equal date_time_init(2017, 12, 9, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:saturday)
+ assert_equal date_time_init(2017, 12, 10, 3, 14, 15), date_time_init(2017, 12, 14, 3, 14, 15).prev_occurring(:sunday)
+ end
+
def test_monday_with_default_beginning_of_week_set
with_bw_default(:saturday) do
assert_equal date_time_init(2012, 9, 17, 0, 0, 0), date_time_init(2012, 9, 18, 0, 0, 0).monday
@@ -341,28 +361,28 @@ module DateAndTimeBehavior
end
def test_on_weekend_on_saturday
- assert date_time_init(2015, 1, 3, 0, 0, 0).on_weekend?
- assert date_time_init(2015, 1, 3, 15, 15, 10).on_weekend?
+ assert_predicate date_time_init(2015, 1, 3, 0, 0, 0), :on_weekend?
+ assert_predicate date_time_init(2015, 1, 3, 15, 15, 10), :on_weekend?
end
def test_on_weekend_on_sunday
- assert date_time_init(2015, 1, 4, 0, 0, 0).on_weekend?
- assert date_time_init(2015, 1, 4, 15, 15, 10).on_weekend?
+ assert_predicate date_time_init(2015, 1, 4, 0, 0, 0), :on_weekend?
+ assert_predicate date_time_init(2015, 1, 4, 15, 15, 10), :on_weekend?
end
def test_on_weekend_on_monday
- assert_not date_time_init(2015, 1, 5, 0, 0, 0).on_weekend?
- assert_not date_time_init(2015, 1, 5, 15, 15, 10).on_weekend?
+ assert_not_predicate date_time_init(2015, 1, 5, 0, 0, 0), :on_weekend?
+ assert_not_predicate date_time_init(2015, 1, 5, 15, 15, 10), :on_weekend?
end
def test_on_weekday_on_sunday
- assert_not date_time_init(2015, 1, 4, 0, 0, 0).on_weekday?
- assert_not date_time_init(2015, 1, 4, 15, 15, 10).on_weekday?
+ assert_not_predicate date_time_init(2015, 1, 4, 0, 0, 0), :on_weekday?
+ assert_not_predicate date_time_init(2015, 1, 4, 15, 15, 10), :on_weekday?
end
def test_on_weekday_on_monday
- assert date_time_init(2015, 1, 5, 0, 0, 0).on_weekday?
- assert date_time_init(2015, 1, 5, 15, 15, 10).on_weekday?
+ assert_predicate date_time_init(2015, 1, 5, 0, 0, 0), :on_weekday?
+ assert_predicate date_time_init(2015, 1, 5, 15, 15, 10), :on_weekday?
end
def with_bw_default(bw = :monday)
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 0c6f3f595a..b8652884ce 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -95,11 +95,11 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
def test_beginning_of_week_in_calendar_reform
- assert_equal Date.new(1582, 10, 1), Date.new(1582, 10, 15).beginning_of_week #friday
+ assert_equal Date.new(1582, 10, 1), Date.new(1582, 10, 15).beginning_of_week # friday
end
def test_end_of_week_in_calendar_reform
- assert_equal Date.new(1582, 10, 17), Date.new(1582, 10, 4).end_of_week #thursday
+ assert_equal Date.new(1582, 10, 17), Date.new(1582, 10, 4).end_of_week # thursday
end
def test_end_of_year
@@ -144,7 +144,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(2012, 9, 28), Date.new(2005, 2, 28).advance(years: 7, months: 7)
assert_equal Date.new(2013, 10, 3), Date.new(2005, 2, 28).advance(years: 7, months: 19, days: 5)
assert_equal Date.new(2013, 10, 17), Date.new(2005, 2, 28).advance(years: 7, months: 19, weeks: 2, days: 5)
- assert_equal Date.new(2005, 2, 28), Date.new(2004, 2, 29).advance(years: 1) #leap day plus one year
+ assert_equal Date.new(2005, 2, 28), Date.new(2004, 2, 29).advance(years: 1) # leap day plus one year
end
def test_advance_does_first_years_and_then_days
@@ -386,11 +386,11 @@ end
class DateExtBehaviorTest < ActiveSupport::TestCase
def test_date_acts_like_date
- assert Date.new.acts_like_date?
+ assert_predicate Date.new, :acts_like_date?
end
def test_blank?
- assert_not Date.new.blank?
+ assert_not_predicate Date.new, :blank?
end
def test_freeze_doesnt_clobber_memoized_instance_methods
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index d942cddb2a..894fb80cba 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -30,28 +30,6 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
end
- def test_next_occur
- datetime = DateTime.new(2016, 9, 24, 0, 0) # saturday
- assert_equal datetime.next_occurring(:monday), datetime.since(2.days)
- assert_equal datetime.next_occurring(:tuesday), datetime.since(3.days)
- assert_equal datetime.next_occurring(:wednesday), datetime.since(4.days)
- assert_equal datetime.next_occurring(:thursday), datetime.since(5.days)
- assert_equal datetime.next_occurring(:friday), datetime.since(6.days)
- assert_equal datetime.next_occurring(:saturday), datetime.since(1.week)
- assert_equal datetime.next_occurring(:sunday), datetime.since(1.day)
- end
-
- def test_prev_occur
- datetime = DateTime.new(2016, 9, 24, 0, 0) # saturday
- assert_equal datetime.prev_occurring(:monday), datetime.ago(5.days)
- assert_equal datetime.prev_occurring(:tuesday), datetime.ago(4.days)
- assert_equal datetime.prev_occurring(:wednesday), datetime.ago(3.days)
- assert_equal datetime.prev_occurring(:thursday), datetime.ago(2.days)
- assert_equal datetime.prev_occurring(:friday), datetime.ago(1.day)
- assert_equal datetime.prev_occurring(:saturday), datetime.ago(1.week)
- assert_equal datetime.prev_occurring(:sunday), datetime.ago(6.days)
- end
-
def test_readable_inspect
datetime = DateTime.new(2005, 2, 21, 14, 30, 0)
assert_equal "Mon, 21 Feb 2005 14:30:00 +0000", datetime.readable_inspect
@@ -209,7 +187,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2013, 10, 3, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, days: 5)
assert_equal DateTime.civil(2013, 10, 17, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5)
assert_equal DateTime.civil(2001, 12, 27, 15, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1)
- assert_equal DateTime.civil(2005, 2, 28, 15, 15, 10), DateTime.civil(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year
+ assert_equal DateTime.civil(2005, 2, 28, 15, 15, 10), DateTime.civil(2004, 2, 29, 15, 15, 10).advance(years: 1) # leap day plus one year
assert_equal DateTime.civil(2005, 2, 28, 20, 15, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(hours: 5)
assert_equal DateTime.civil(2005, 2, 28, 15, 22, 10), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(minutes: 7)
assert_equal DateTime.civil(2005, 2, 28, 15, 15, 19), DateTime.civil(2005, 2, 28, 15, 15, 10).advance(seconds: 9)
@@ -337,15 +315,15 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_acts_like_date
- assert DateTime.new.acts_like_date?
+ assert_predicate DateTime.new, :acts_like_date?
end
def test_acts_like_time
- assert DateTime.new.acts_like_time?
+ assert_predicate DateTime.new, :acts_like_time?
end
def test_blank?
- assert_not DateTime.new.blank?
+ assert_not_predicate DateTime.new, :blank?
end
def test_utc?
@@ -385,45 +363,45 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_compare_with_time
- assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59)
- assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0)
+ assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59)
+ assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0)
assert_equal(-1, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1))
end
def test_compare_with_datetime
- assert_equal 1, DateTime.civil(2000) <=> DateTime.civil(1999, 12, 31, 23, 59, 59)
- assert_equal 0, DateTime.civil(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 0)
+ assert_equal 1, DateTime.civil(2000) <=> DateTime.civil(1999, 12, 31, 23, 59, 59)
+ assert_equal 0, DateTime.civil(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 0)
assert_equal(-1, DateTime.civil(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 1))
end
def test_compare_with_time_with_zone
- assert_equal 1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"])
- assert_equal 0, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"])
+ assert_equal 1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"])
+ assert_equal 0, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"])
assert_equal(-1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"]))
end
def test_compare_with_string
- assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59).to_s
- assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s
+ assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59).to_s
+ assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s
assert_equal(-1, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1).to_s)
assert_nil DateTime.civil(2000) <=> "Invalid as Time"
end
def test_compare_with_integer
- assert_equal 1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440587
- assert_equal 0, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440588
+ assert_equal 1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440587
+ assert_equal 0, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440588
assert_equal(-1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440589)
end
def test_compare_with_float
- assert_equal 1, DateTime.civil(1970) <=> 2440586.5
- assert_equal 0, DateTime.civil(1970) <=> 2440587.5
+ assert_equal 1, DateTime.civil(1970) <=> 2440586.5
+ assert_equal 0, DateTime.civil(1970) <=> 2440587.5
assert_equal(-1, DateTime.civil(1970) <=> 2440588.5)
end
def test_compare_with_rational
- assert_equal 1, DateTime.civil(1970) <=> Rational(4881173, 2)
- assert_equal 0, DateTime.civil(1970) <=> Rational(4881175, 2)
+ assert_equal 1, DateTime.civil(1970) <=> Rational(4881173, 2)
+ assert_equal 0, DateTime.civil(1970) <=> Rational(4881175, 2)
assert_equal(-1, DateTime.civil(1970) <=> Rational(4881177, 2))
end
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index b3e0cd8bd0..c5022cf5b1 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -5,6 +5,7 @@ require "active_support/inflector"
require "active_support/time"
require "active_support/json"
require "time_zone_test_helpers"
+require "yaml"
class DurationTest < ActiveSupport::TestCase
include TimeZoneTestHelpers
@@ -71,6 +72,8 @@ class DurationTest < ActiveSupport::TestCase
assert_equal "7 days", 7.days.inspect
assert_equal "1 week", 1.week.inspect
assert_equal "2 weeks", 1.fortnight.inspect
+ assert_equal "0 seconds", (10 % 5.seconds).inspect
+ assert_equal "10 minutes", (10.minutes + 0.seconds).inspect
end
def test_inspect_locale
@@ -160,7 +163,7 @@ class DurationTest < ActiveSupport::TestCase
end
def test_time_plus_duration_returns_same_time_datatype
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"] , Time.utc(2016, 4, 28, 00, 45))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"], Time.utc(2016, 4, 28, 00, 45))
now = Time.now.utc
%w( second minute hour day week month year ).each do |unit|
assert_equal((now + 1.send(unit)).class, Time, "Time + 1.#{unit} must be Time")
@@ -640,6 +643,12 @@ class DurationTest < ActiveSupport::TestCase
assert_equal time + d1, time + d2
end
+ def test_durations_survive_yaml_serialization
+ d1 = YAML.load(YAML.dump(10.minutes))
+ assert_equal 600, d1.to_i
+ assert_equal 660, (d1 + 60).to_i
+ end
+
private
def eastern_time_zone
if Gem.win_platform?
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 746d7ad416..50b811e79f 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -446,7 +446,7 @@ class HashExtTest < ActiveSupport::TestCase
original.freeze
assert_nothing_raised { original.except(:a) }
- assert_raise(RuntimeError) { original.except!(:a) }
+ assert_raise(frozen_error_class) { original.except!(:a) }
end
def test_except_does_not_delete_values_in_original
@@ -1061,7 +1061,7 @@ class HashToXmlTest < ActiveSupport::TestCase
</alert>
XML
alert_at = Hash.from_xml(alert_xml)["alert"]["alert_at"]
- assert alert_at.utc?
+ assert_predicate alert_at, :utc?
assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
end
@@ -1072,7 +1072,7 @@ class HashToXmlTest < ActiveSupport::TestCase
</alert>
XML
alert_at = Hash.from_xml(alert_xml)["alert"]["alert_at"]
- assert alert_at.utc?
+ assert_predicate alert_at, :utc?
assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
end
@@ -1083,7 +1083,7 @@ class HashToXmlTest < ActiveSupport::TestCase
</alert>
XML
alert_at = Hash.from_xml(alert_xml)["alert"]["alert_at"]
- assert alert_at.utc?
+ assert_predicate alert_at, :utc?
assert_equal 2050, alert_at.year
assert_equal 2, alert_at.month
assert_equal 10, alert_at.day
diff --git a/activesupport/test/core_ext/module/anonymous_test.rb b/activesupport/test/core_ext/module/anonymous_test.rb
index 606f22c9b5..e03c217015 100644
--- a/activesupport/test/core_ext/module/anonymous_test.rb
+++ b/activesupport/test/core_ext/module/anonymous_test.rb
@@ -5,12 +5,12 @@ require "active_support/core_ext/module/anonymous"
class AnonymousTest < ActiveSupport::TestCase
test "an anonymous class or module are anonymous" do
- assert Module.new.anonymous?
- assert Class.new.anonymous?
+ assert_predicate Module.new, :anonymous?
+ assert_predicate Class.new, :anonymous?
end
test "a named class or module are not anonymous" do
- assert !Kernel.anonymous?
- assert !Object.anonymous?
+ assert_not_predicate Kernel, :anonymous?
+ assert_not_predicate Object, :anonymous?
end
end
diff --git a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
index e0fbd1002c..e0e331fc91 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
@@ -43,22 +43,22 @@ class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase
assert_respond_to @class, :foo
assert_respond_to @class, :foo=
assert_respond_to @object, :bar
- assert !@object.respond_to?(:bar=)
+ assert_not_respond_to @object, :bar=
end.join
end
def test_should_not_create_instance_reader
Thread.new do
assert_respond_to @class, :shaq
- assert !@object.respond_to?(:shaq)
+ assert_not_respond_to @object, :shaq
end.join
end
def test_should_not_create_instance_accessors
Thread.new do
assert_respond_to @class, :camp
- assert !@object.respond_to?(:camp)
- assert !@object.respond_to?(:camp=)
+ assert_not_respond_to @object, :camp
+ assert_not_respond_to @object, :camp=
end.join
end
diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb
index f1d6859a88..33c583947a 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -65,18 +65,18 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_respond_to @module, :foo
assert_respond_to @module, :foo=
assert_respond_to @object, :bar
- assert !@object.respond_to?(:bar=)
+ assert_not_respond_to @object, :bar=
end
def test_should_not_create_instance_reader
assert_respond_to @module, :shaq
- assert !@object.respond_to?(:shaq)
+ assert_not_respond_to @object, :shaq
end
def test_should_not_create_instance_accessors
assert_respond_to @module, :camp
- assert !@object.respond_to?(:camp)
- assert !@object.respond_to?(:camp=)
+ assert_not_respond_to @object, :camp
+ assert_not_respond_to @object, :camp=
end
def test_should_raise_name_error_if_attribute_name_is_invalid
diff --git a/activesupport/test/core_ext/module/attribute_aliasing_test.rb b/activesupport/test/core_ext/module/attribute_aliasing_test.rb
index 187a0f4da2..81aac224f9 100644
--- a/activesupport/test/core_ext/module/attribute_aliasing_test.rb
+++ b/activesupport/test/core_ext/module/attribute_aliasing_test.rb
@@ -30,15 +30,15 @@ class AttributeAliasingTest < ActiveSupport::TestCase
def test_attribute_alias
e = AttributeAliasing::Email.new
- assert !e.subject?
+ assert_not_predicate e, :subject?
e.title = "Upgrade computer"
assert_equal "Upgrade computer", e.subject
- assert e.subject?
+ assert_predicate e, :subject?
e.subject = "We got a long way to go"
assert_equal "We got a long way to go", e.title
- assert e.title?
+ assert_predicate e, :title?
end
def test_aliasing_to_uppercase_attributes
@@ -47,15 +47,15 @@ class AttributeAliasingTest < ActiveSupport::TestCase
# to more sensible ones, everything goes *foof*.
e = AttributeAliasing::Email.new
- assert !e.body?
- assert !e.Data?
+ assert_not_predicate e, :body?
+ assert_not_predicate e, :Data?
e.body = "No, really, this is not a joke."
assert_equal "No, really, this is not a joke.", e.Data
- assert e.Data?
+ assert_predicate e, :Data?
e.Data = "Uppercased methods are the suck"
assert_equal "Uppercased methods are the suck", e.body
- assert e.body?
+ assert_predicate e, :body?
end
end
diff --git a/activesupport/test/core_ext/module/concerning_test.rb b/activesupport/test/core_ext/module/concerning_test.rb
index 192c3d5a9c..969434766f 100644
--- a/activesupport/test/core_ext/module/concerning_test.rb
+++ b/activesupport/test/core_ext/module/concerning_test.rb
@@ -55,10 +55,10 @@ class ModuleConcernTest < ActiveSupport::TestCase
end
def test_using_class_methods_blocks_instead_of_ClassMethods_module
- assert !Foo.respond_to?(:will_be_orphaned)
- assert Foo.respond_to?(:hacked_on)
- assert Foo.respond_to?(:nicer_dsl)
- assert Foo.respond_to?(:doesnt_clobber)
+ assert_not_respond_to Foo, :will_be_orphaned
+ assert_respond_to Foo, :hacked_on
+ assert_respond_to Foo, :nicer_dsl
+ assert_respond_to Foo, :doesnt_clobber
# Orphan in Foo::ClassMethods, not Bar::ClassMethods.
assert Foo.const_defined?(:ClassMethods)
diff --git a/activesupport/test/core_ext/module/reachable_test.rb b/activesupport/test/core_ext/module/reachable_test.rb
index 097a72fa5b..f356d46957 100644
--- a/activesupport/test/core_ext/module/reachable_test.rb
+++ b/activesupport/test/core_ext/module/reachable_test.rb
@@ -6,15 +6,15 @@ require "active_support/core_ext/module/reachable"
class AnonymousTest < ActiveSupport::TestCase
test "an anonymous class or module is not reachable" do
assert_deprecated do
- assert !Module.new.reachable?
- assert !Class.new.reachable?
+ assert_not_predicate Module.new, :reachable?
+ assert_not_predicate Class.new, :reachable?
end
end
test "ordinary named classes or modules are reachable" do
assert_deprecated do
- assert Kernel.reachable?
- assert Object.reachable?
+ assert_predicate Kernel, :reachable?
+ assert_predicate Object, :reachable?
end
end
@@ -26,8 +26,8 @@ class AnonymousTest < ActiveSupport::TestCase
self.class.send(:remove_const, :M)
assert_deprecated do
- assert !c.reachable?
- assert !m.reachable?
+ assert_not_predicate c, :reachable?
+ assert_not_predicate m, :reachable?
end
end
@@ -42,10 +42,10 @@ class AnonymousTest < ActiveSupport::TestCase
eval "module M; end"
assert_deprecated do
- assert C.reachable?
- assert M.reachable?
- assert !c.reachable?
- assert !m.reachable?
+ assert_predicate C, :reachable?
+ assert_predicate M, :reachable?
+ assert_not_predicate c, :reachable?
+ assert_not_predicate m, :reachable?
end
end
end
diff --git a/activesupport/test/core_ext/module/remove_method_test.rb b/activesupport/test/core_ext/module/remove_method_test.rb
index 8493be8d08..a18fc0a5e4 100644
--- a/activesupport/test/core_ext/module/remove_method_test.rb
+++ b/activesupport/test/core_ext/module/remove_method_test.rb
@@ -32,14 +32,14 @@ class RemoveMethodTest < ActiveSupport::TestCase
RemoveMethodTests::A.class_eval {
remove_possible_method(:do_something)
}
- assert !RemoveMethodTests::A.new.respond_to?(:do_something)
+ assert_not_respond_to RemoveMethodTests::A.new, :do_something
end
def test_remove_singleton_method_from_an_object
RemoveMethodTests::A.class_eval {
remove_possible_singleton_method(:do_something_else)
}
- assert !RemoveMethodTests::A.respond_to?(:do_something_else)
+ assert_not_respond_to RemoveMethodTests::A, :do_something_else
end
def test_redefine_method_in_an_object
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index e918823074..ebbe9c304c 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -347,7 +347,7 @@ class ModuleTest < ActiveSupport::TestCase
def test_delegation_with_method_arguments
has_block = HasBlock.new(Block.new)
- assert has_block.hello?
+ assert_predicate has_block, :hello?
end
def test_delegate_missing_to_with_method
@@ -383,9 +383,9 @@ class ModuleTest < ActiveSupport::TestCase
end
def test_delegate_missing_to_affects_respond_to
- assert DecoratedTester.new(@david).respond_to?(:name)
- assert_not DecoratedTester.new(@david).respond_to?(:private_name)
- assert_not DecoratedTester.new(@david).respond_to?(:my_fake_method)
+ assert_respond_to DecoratedTester.new(@david), :name
+ assert_not_respond_to DecoratedTester.new(@david), :private_name
+ assert_not_respond_to DecoratedTester.new(@david), :my_fake_method
assert DecoratedTester.new(@david).respond_to?(:name, true)
assert_not DecoratedTester.new(@david).respond_to?(:private_name, true)
@@ -414,8 +414,8 @@ class ModuleTest < ActiveSupport::TestCase
place = location.new(Somewhere.new("Such street", "Sad city"))
- assert_not place.respond_to?(:street)
- assert_not place.respond_to?(:city)
+ assert_not_respond_to place, :street
+ assert_not_respond_to place, :city
assert place.respond_to?(:street, true) # Asking for private method
assert place.respond_to?(:city, true)
@@ -432,12 +432,12 @@ class ModuleTest < ActiveSupport::TestCase
place = location.new(Somewhere.new("Such street", "Sad city"))
- assert_not place.respond_to?(:street)
- assert_not place.respond_to?(:city)
+ assert_not_respond_to place, :street
+ assert_not_respond_to place, :city
- assert_not place.respond_to?(:the_street)
+ assert_not_respond_to place, :the_street
assert place.respond_to?(:the_street, true)
- assert_not place.respond_to?(:the_city)
+ assert_not_respond_to place, :the_city
assert place.respond_to?(:the_city, true)
end
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index d38124b214..4b9073da54 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -302,7 +302,7 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal "40 KB", 41100.to_s(:human_size, precision: 2)
assert_equal "1.0 KB", kilobytes(1.0123).to_s(:human_size, precision: 2, strip_insignificant_zeros: false)
assert_equal "1.012 KB", kilobytes(1.0123).to_s(:human_size, precision: 3, significant: false)
- assert_equal "1 KB", kilobytes(1.0123).to_s(:human_size, precision: 0, significant: true) #ignores significant it precision is 0
+ assert_equal "1 KB", kilobytes(1.0123).to_s(:human_size, precision: 0, significant: true) # ignores significant it precision is 0
end
def test_to_s__human_size_with_custom_delimiter_and_separator
@@ -330,17 +330,17 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal "489.0 Thousand", 489000.to_s(:human, precision: 4, strip_insignificant_zeros: false)
assert_equal "1.2346 Million", 1234567.to_s(:human, precision: 4, significant: false)
assert_equal "1,2 Million", 1234567.to_s(:human, precision: 1, significant: false, separator: ",")
- assert_equal "1 Million", 1234567.to_s(:human, precision: 0, significant: true, separator: ",") #significant forced to false
+ assert_equal "1 Million", 1234567.to_s(:human, precision: 0, significant: true, separator: ",") # significant forced to false
end
def test_number_to_human_with_custom_units
- #Only integers
+ # Only integers
volume = { unit: "ml", thousand: "lt", million: "m3" }
assert_equal "123 lt", 123456.to_s(:human, units: volume)
assert_equal "12 ml", 12.to_s(:human, units: volume)
assert_equal "1.23 m3", 1234567.to_s(:human, units: volume)
- #Including fractionals
+ # Including fractionals
distance = { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" }
assert_equal "1.23 mm", 0.00123.to_s(:human, units: distance)
assert_equal "1.23 cm", 0.0123.to_s(:human, units: distance)
@@ -353,14 +353,14 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal "1.23 km", 1230.to_s(:human, units: distance)
assert_equal "12.3 km", 12300.to_s(:human, units: distance)
- #The quantifiers don't need to be a continuous sequence
+ # The quantifiers don't need to be a continuous sequence
gangster = { hundred: "hundred bucks", million: "thousand quids" }
assert_equal "1 hundred bucks", 100.to_s(:human, units: gangster)
assert_equal "25 hundred bucks", 2500.to_s(:human, units: gangster)
assert_equal "25 thousand quids", 25000000.to_s(:human, units: gangster)
assert_equal "12300 thousand quids", 12345000000.to_s(:human, units: gangster)
- #Spaces are stripped from the resulting string
+ # Spaces are stripped from the resulting string
assert_equal "4", 4.to_s(:human, units: { unit: "", ten: "tens " })
assert_equal "4.5 tens", 45.to_s(:human, units: { unit: "", ten: " tens " })
end
diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb
index 749e59ec00..954f415383 100644
--- a/activesupport/test/core_ext/object/blank_test.rb
+++ b/activesupport/test/core_ext/object/blank_test.rb
@@ -16,8 +16,8 @@ class BlankTest < ActiveSupport::TestCase
end
end
- BLANK = [ EmptyTrue.new, nil, false, "", " ", " \n\t \r ", " ", "\u00a0", [], {} ]
- NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 }, Time.now ]
+ BLANK = [ EmptyTrue.new, nil, false, "", " ", " \n\t \r ", " ", "\u00a0", [], {}, " ".encode("UTF-16LE") ]
+ NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 }, Time.now, "my value".encode("UTF-16LE") ]
def test_blank
BLANK.each { |v| assert_equal true, v.blank?, "#{v.inspect} should be blank" }
diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
index 93f39b00bb..b984becce3 100644
--- a/activesupport/test/core_ext/object/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -8,16 +8,16 @@ require "active_support/core_ext/numeric/time"
class DuplicableTest < ActiveSupport::TestCase
if RUBY_VERSION >= "2.5.0"
RAISE_DUP = [method(:puts)]
- ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)]
+ ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)]
elsif RUBY_VERSION >= "2.4.1"
RAISE_DUP = [method(:puts), Complex(1), Rational(1)]
- ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3]
+ ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3]
elsif RUBY_VERSION >= "2.4.0" # Due to 2.4.0 bug. This elsif cannot be removed unless we drop 2.4.0 support...
RAISE_DUP = [method(:puts), Complex(1), Rational(1), "symbol_from_string".to_sym]
- ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3]
+ ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3]
else
RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts), Complex(1), Rational(1)]
- ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56")]
+ ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56")]
end
def test_duplicable
diff --git a/activesupport/test/core_ext/object/instance_variables_test.rb b/activesupport/test/core_ext/object/instance_variables_test.rb
index b9ec827954..a3d8daab5b 100644
--- a/activesupport/test/core_ext/object/instance_variables_test.rb
+++ b/activesupport/test/core_ext/object/instance_variables_test.rb
@@ -19,7 +19,7 @@ class ObjectInstanceVariableTest < ActiveSupport::TestCase
end
def test_instance_exec_passes_arguments_to_block
- assert_equal %w(hello goodbye), "hello".instance_exec("goodbye") { |v| [self, v] }
+ assert_equal %w(hello goodbye), "hello".dup.instance_exec("goodbye") { |v| [self, v] }
end
def test_instance_exec_with_frozen_obj
@@ -27,7 +27,7 @@ class ObjectInstanceVariableTest < ActiveSupport::TestCase
end
def test_instance_exec_nested
- assert_equal %w(goodbye olleh bar), "hello".instance_exec("goodbye") { |arg|
+ assert_equal %w(goodbye olleh bar), "hello".dup.instance_exec("goodbye") { |arg|
[arg] + instance_exec("bar") { |v| [reverse, v] } }
end
end
diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb
index fe68b24bf5..40d6cdd28e 100644
--- a/activesupport/test/core_ext/object/try_test.rb
+++ b/activesupport/test/core_ext/object/try_test.rb
@@ -10,25 +10,25 @@ class ObjectTryTest < ActiveSupport::TestCase
def test_nonexisting_method
method = :undefined_method
- assert !@string.respond_to?(method)
+ assert_not_respond_to @string, method
assert_nil @string.try(method)
end
def test_nonexisting_method_with_arguments
method = :undefined_method
- assert !@string.respond_to?(method)
+ assert_not_respond_to @string, method
assert_nil @string.try(method, "llo", "y")
end
def test_nonexisting_method_bang
method = :undefined_method
- assert !@string.respond_to?(method)
+ assert_not_respond_to @string, method
assert_raise(NoMethodError) { @string.try!(method) }
end
def test_nonexisting_method_with_arguments_bang
method = :undefined_method
- assert !@string.respond_to?(method)
+ assert_not_respond_to @string, method
assert_raise(NoMethodError) { @string.try!(method, "llo", "y") }
end
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 049fac8fd4..903c173e59 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -106,26 +106,26 @@ class RangeTest < ActiveSupport::TestCase
end
def test_each_on_time_with_zone
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30))
assert_raises TypeError do
((twz - 1.hour)..twz).each {}
end
end
def test_step_on_time_with_zone
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30))
assert_raises TypeError do
((twz - 1.hour)..twz).step(1) {}
end
end
def test_include_on_time_with_zone
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30))
assert ((twz - 1.hour)..twz).include?(twz)
end
def test_case_equals_on_time_with_zone
- twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"] , Time.utc(2006, 11, 28, 10, 30))
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Eastern Time (US & Canada)"], Time.utc(2006, 11, 28, 10, 30))
assert ((twz - 1.hour)..twz) === twz
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 5c5abe9fd1..472190277e 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -259,8 +259,8 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_string_inquiry
- assert "production".inquiry.production?
- assert !"production".inquiry.development?
+ assert_predicate "production".inquiry, :production?
+ assert_not_predicate "production".inquiry, :development?
end
def test_truncate
@@ -317,7 +317,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
def test_truncate_should_not_be_html_safe
- assert !"Hello World!".truncate(12).html_safe?
+ assert_not_predicate "Hello World!".truncate(12), :html_safe?
end
def test_remove
@@ -639,7 +639,7 @@ end
class StringBehaviourTest < ActiveSupport::TestCase
def test_acts_like_string
- assert "Bambi".acts_like_string?
+ assert_predicate "Bambi", :acts_like_string?
end
end
@@ -654,10 +654,10 @@ class CoreExtStringMultibyteTest < ActiveSupport::TestCase
end
def test_string_should_recognize_utf8_strings
- assert UTF8_STRING.is_utf8?
- assert ASCII_STRING.is_utf8?
- assert !EUC_JP_STRING.is_utf8?
- assert !INVALID_UTF8_STRING.is_utf8?
+ assert_predicate UTF8_STRING, :is_utf8?
+ assert_predicate ASCII_STRING, :is_utf8?
+ assert_not_predicate EUC_JP_STRING, :is_utf8?
+ assert_not_predicate INVALID_UTF8_STRING, :is_utf8?
end
def test_mb_chars_returns_instance_of_proxy_class
@@ -676,12 +676,12 @@ class OutputSafetyTest < ActiveSupport::TestCase
end
test "A string is unsafe by default" do
- assert !@string.html_safe?
+ assert_not_predicate @string, :html_safe?
end
test "A string can be marked safe" do
string = @string.html_safe
- assert string.html_safe?
+ assert_predicate string, :html_safe?
end
test "Marking a string safe returns the string" do
@@ -689,15 +689,15 @@ class OutputSafetyTest < ActiveSupport::TestCase
end
test "An integer is safe by default" do
- assert 5.html_safe?
+ assert_predicate 5, :html_safe?
end
test "a float is safe by default" do
- assert 5.7.html_safe?
+ assert_predicate 5.7, :html_safe?
end
test "An object is unsafe by default" do
- assert !@object.html_safe?
+ assert_not_predicate @object, :html_safe?
end
test "Adding an object to a safe string returns a safe string" do
@@ -705,7 +705,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
string << @object
assert_equal "helloother", string
- assert string.html_safe?
+ assert_predicate string, :html_safe?
end
test "Adding a safe string to another safe string returns a safe string" do
@@ -714,7 +714,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
@combination = @other_string + string
assert_equal "otherhello", @combination
- assert @combination.html_safe?
+ assert_predicate @combination, :html_safe?
end
test "Adding an unsafe string to a safe string escapes it and returns a safe string" do
@@ -725,20 +725,20 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_equal "other&lt;foo&gt;", @combination
assert_equal "hello<foo>", @other_combination
- assert @combination.html_safe?
- assert !@other_combination.html_safe?
+ assert_predicate @combination, :html_safe?
+ assert_not_predicate @other_combination, :html_safe?
end
test "Prepending safe onto unsafe yields unsafe" do
@string.prepend "other".html_safe
- assert !@string.html_safe?
+ assert_not_predicate @string, :html_safe?
assert_equal "otherhello", @string
end
test "Prepending unsafe onto safe yields escaped safe" do
other = "other".html_safe
other.prepend "<foo>"
- assert other.html_safe?
+ assert_predicate other, :html_safe?
assert_equal "&lt;foo&gt;other", other
end
@@ -747,14 +747,14 @@ class OutputSafetyTest < ActiveSupport::TestCase
string = @string.html_safe
@other_string.concat(string)
- assert !@other_string.html_safe?
+ assert_not_predicate @other_string, :html_safe?
end
test "Concatting unsafe onto safe yields escaped safe" do
@other_string = "other".html_safe
string = @other_string.concat("<foo>")
assert_equal "other&lt;foo&gt;", string
- assert string.html_safe?
+ assert_predicate string, :html_safe?
end
test "Concatting safe onto safe yields safe" do
@@ -762,7 +762,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
string = @string.html_safe
@other_string.concat(string)
- assert @other_string.html_safe?
+ assert_predicate @other_string, :html_safe?
end
test "Concatting safe onto unsafe with << yields unsafe" do
@@ -770,14 +770,14 @@ class OutputSafetyTest < ActiveSupport::TestCase
string = @string.html_safe
@other_string << string
- assert !@other_string.html_safe?
+ assert_not_predicate @other_string, :html_safe?
end
test "Concatting unsafe onto safe with << yields escaped safe" do
@other_string = "other".html_safe
string = @other_string << "<foo>"
assert_equal "other&lt;foo&gt;", string
- assert string.html_safe?
+ assert_predicate string, :html_safe?
end
test "Concatting safe onto safe with << yields safe" do
@@ -785,7 +785,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
string = @string.html_safe
@other_string << string
- assert @other_string.html_safe?
+ assert_predicate @other_string, :html_safe?
end
test "Concatting safe onto unsafe with % yields unsafe" do
@@ -793,7 +793,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
string = @string.html_safe
@other_string = @other_string % string
- assert !@other_string.html_safe?
+ assert_not_predicate @other_string, :html_safe?
end
test "Concatting unsafe onto safe with % yields escaped safe" do
@@ -801,7 +801,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
string = @other_string % "<foo>"
assert_equal "other&lt;foo&gt;", string
- assert string.html_safe?
+ assert_predicate string, :html_safe?
end
test "Concatting safe onto safe with % yields safe" do
@@ -809,7 +809,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
string = @string.html_safe
@other_string = @other_string % string
- assert @other_string.html_safe?
+ assert_predicate @other_string, :html_safe?
end
test "Concatting with % doesn't modify a string" do
@@ -823,7 +823,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
string = @string.html_safe
string = string.concat(13)
assert_equal "hello".dup.concat(13), string
- assert string.html_safe?
+ assert_predicate string, :html_safe?
end
test "emits normal string yaml" do
@@ -832,8 +832,8 @@ class OutputSafetyTest < ActiveSupport::TestCase
test "call to_param returns a normal string" do
string = @string.html_safe
- assert string.html_safe?
- assert !string.to_param.html_safe?
+ assert_predicate string, :html_safe?
+ assert_not_predicate string.to_param, :html_safe?
end
test "ERB::Util.html_escape should escape unsafe characters" do
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 8cb17df01b..e1cb22fda8 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -451,7 +451,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2013, 10, 3, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, days: 5)
assert_equal Time.local(2013, 10, 17, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5)
assert_equal Time.local(2001, 12, 27, 15, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1)
- assert_equal Time.local(2005, 2, 28, 15, 15, 10), Time.local(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year
+ assert_equal Time.local(2005, 2, 28, 15, 15, 10), Time.local(2004, 2, 29, 15, 15, 10).advance(years: 1) # leap day plus one year
assert_equal Time.local(2005, 2, 28, 20, 15, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(hours: 5)
assert_equal Time.local(2005, 2, 28, 15, 22, 10), Time.local(2005, 2, 28, 15, 15, 10).advance(minutes: 7)
assert_equal Time.local(2005, 2, 28, 15, 15, 19), Time.local(2005, 2, 28, 15, 15, 10).advance(seconds: 9)
@@ -473,7 +473,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.utc(2013, 10, 3, 15, 15, 10), Time.utc(2005, 2, 22, 15, 15, 10).advance(years: 7, months: 19, days: 11)
assert_equal Time.utc(2013, 10, 17, 15, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(years: 7, months: 19, weeks: 2, days: 5)
assert_equal Time.utc(2001, 12, 27, 15, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(years: -3, months: -2, days: -1)
- assert_equal Time.utc(2005, 2, 28, 15, 15, 10), Time.utc(2004, 2, 29, 15, 15, 10).advance(years: 1) #leap day plus one year
+ assert_equal Time.utc(2005, 2, 28, 15, 15, 10), Time.utc(2004, 2, 29, 15, 15, 10).advance(years: 1) # leap day plus one year
assert_equal Time.utc(2005, 2, 28, 20, 15, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(hours: 5)
assert_equal Time.utc(2005, 2, 28, 15, 22, 10), Time.utc(2005, 2, 28, 15, 15, 10).advance(minutes: 7)
assert_equal Time.utc(2005, 2, 28, 15, 15, 19), Time.utc(2005, 2, 28, 15, 15, 10).advance(seconds: 9)
@@ -495,7 +495,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.new(2013, 10, 3, 15, 15, 10, "-08:00"), Time.new(2005, 2, 22, 15, 15, 10, "-08:00").advance(years: 7, months: 19, days: 11)
assert_equal Time.new(2013, 10, 17, 15, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(years: 7, months: 19, weeks: 2, days: 5)
assert_equal Time.new(2001, 12, 27, 15, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(years: -3, months: -2, days: -1)
- assert_equal Time.new(2005, 2, 28, 15, 15, 10, "-08:00"), Time.new(2004, 2, 29, 15, 15, 10, "-08:00").advance(years: 1) #leap day plus one year
+ assert_equal Time.new(2005, 2, 28, 15, 15, 10, "-08:00"), Time.new(2004, 2, 29, 15, 15, 10, "-08:00").advance(years: 1) # leap day plus one year
assert_equal Time.new(2005, 2, 28, 20, 15, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(hours: 5)
assert_equal Time.new(2005, 2, 28, 15, 22, 10, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(minutes: 7)
assert_equal Time.new(2005, 2, 28, 15, 15, 19, "-08:00"), Time.new(2005, 2, 28, 15, 15, 10, "-08:00").advance(seconds: 9)
@@ -737,7 +737,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_acts_like_time
- assert Time.new.acts_like_time?
+ assert_predicate Time.new, :acts_like_time?
end
def test_formatted_offset_with_utc
@@ -756,26 +756,26 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_compare_with_time
- assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999)
- assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0)
+ assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999)
+ assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0)
assert_equal(-1, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0, 001))
end
def test_compare_with_datetime
- assert_equal 1, Time.utc(2000) <=> DateTime.civil(1999, 12, 31, 23, 59, 59)
- assert_equal 0, Time.utc(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 0)
+ assert_equal 1, Time.utc(2000) <=> DateTime.civil(1999, 12, 31, 23, 59, 59)
+ assert_equal 0, Time.utc(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 0)
assert_equal(-1, Time.utc(2000) <=> DateTime.civil(2000, 1, 1, 0, 0, 1))
end
def test_compare_with_time_with_zone
- assert_equal 1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"])
- assert_equal 0, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"])
+ assert_equal 1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"])
+ assert_equal 0, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"])
assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"]))
end
def test_compare_with_string
- assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999).to_s
- assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s
+ assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999).to_s
+ assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s
assert_equal(-1, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1, 0).to_s)
assert_nil Time.utc(2000) <=> "Invalid as Time"
end
@@ -863,11 +863,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_minus_with_time_with_zone
- assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone["UTC"])
+ assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1), ActiveSupport::TimeZone["UTC"])
end
def test_minus_with_datetime
- assert_equal 86_400.0, Time.utc(2000, 1, 2) - DateTime.civil(2000, 1, 1)
+ assert_equal 86_400.0, Time.utc(2000, 1, 2) - DateTime.civil(2000, 1, 1)
end
def test_time_created_with_local_constructor_cannot_represent_times_during_hour_skipped_by_dst
@@ -875,7 +875,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
# On Apr 2 2006 at 2:00AM in US, clocks were moved forward to 3:00AM.
# Therefore, 2AM EST doesn't exist for this date; Time.local fails over to 3:00AM EDT
assert_equal Time.local(2006, 4, 2, 3), Time.local(2006, 4, 2, 2)
- assert Time.local(2006, 4, 2, 2).dst?
+ assert_predicate Time.local(2006, 4, 2, 2), :dst?
end
end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 0f80a24758..be3b8a49a9 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -50,6 +50,12 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_raise(ArgumentError) { @twz.in_time_zone(Object.new) }
end
+ def test_in_time_zone_with_ambiguous_time
+ with_env_tz "Europe/Moscow" do
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), Time.local(2014, 10, 26, 1, 0, 0).in_time_zone("Moscow")
+ end
+ end
+
def test_localtime
assert_equal @twz.localtime, @twz.utc.getlocal
assert_instance_of Time, @twz.localtime
@@ -78,7 +84,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_formatted_offset
assert_equal "-05:00", @twz.formatted_offset
- assert_equal "-04:00", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).formatted_offset #dst
+ assert_equal "-04:00", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).formatted_offset # dst
end
def test_dst?
@@ -88,7 +94,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_zone
assert_equal "EST", @twz.zone
- assert_equal "EDT", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone #dst
+ assert_equal "EDT", ActiveSupport::TimeWithZone.new(Time.utc(2000, 6), @time_zone).zone # dst
end
def test_nsec
@@ -215,20 +221,20 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_compare_with_time
- assert_equal 1, @twz <=> Time.utc(1999, 12, 31, 23, 59, 59)
- assert_equal 0, @twz <=> Time.utc(2000, 1, 1, 0, 0, 0)
+ assert_equal 1, @twz <=> Time.utc(1999, 12, 31, 23, 59, 59)
+ assert_equal 0, @twz <=> Time.utc(2000, 1, 1, 0, 0, 0)
assert_equal(-1, @twz <=> Time.utc(2000, 1, 1, 0, 0, 1))
end
def test_compare_with_datetime
- assert_equal 1, @twz <=> DateTime.civil(1999, 12, 31, 23, 59, 59)
- assert_equal 0, @twz <=> DateTime.civil(2000, 1, 1, 0, 0, 0)
+ assert_equal 1, @twz <=> DateTime.civil(1999, 12, 31, 23, 59, 59)
+ assert_equal 0, @twz <=> DateTime.civil(2000, 1, 1, 0, 0, 0)
assert_equal(-1, @twz <=> DateTime.civil(2000, 1, 1, 0, 0, 1))
end
def test_compare_with_time_with_zone
- assert_equal 1, @twz <=> ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"])
- assert_equal 0, @twz <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"])
+ assert_equal 1, @twz <=> ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"])
+ assert_equal 0, @twz <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone["UTC"])
assert_equal(-1, @twz <=> ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone["UTC"]))
end
@@ -301,13 +307,13 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_plus_with_integer
- assert_equal Time.utc(1999, 12, 31, 19, 0 , 5), (@twz + 5).time
+ assert_equal Time.utc(1999, 12, 31, 19, 0, 5), (@twz + 5).time
end
def test_plus_with_integer_when_self_wraps_datetime
datetime = DateTime.civil(2000, 1, 1, 0)
twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone)
- assert_equal DateTime.civil(1999, 12, 31, 19, 0 , 5), (twz + 5).time
+ assert_equal DateTime.civil(1999, 12, 31, 19, 0, 5), (twz + 5).time
end
def test_plus_when_crossing_time_class_limit
@@ -316,31 +322,31 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_plus_with_duration
- assert_equal Time.utc(2000, 1, 5, 19, 0 , 0), (@twz + 5.days).time
+ assert_equal Time.utc(2000, 1, 5, 19, 0, 0), (@twz + 5.days).time
end
def test_minus_with_integer
- assert_equal Time.utc(1999, 12, 31, 18, 59 , 55), (@twz - 5).time
+ assert_equal Time.utc(1999, 12, 31, 18, 59, 55), (@twz - 5).time
end
def test_minus_with_integer_when_self_wraps_datetime
datetime = DateTime.civil(2000, 1, 1, 0)
twz = ActiveSupport::TimeWithZone.new(datetime, @time_zone)
- assert_equal DateTime.civil(1999, 12, 31, 18, 59 , 55), (twz - 5).time
+ assert_equal DateTime.civil(1999, 12, 31, 18, 59, 55), (twz - 5).time
end
def test_minus_with_duration
- assert_equal Time.utc(1999, 12, 26, 19, 0 , 0), (@twz - 5.days).time
+ assert_equal Time.utc(1999, 12, 26, 19, 0, 0), (@twz - 5.days).time
end
def test_minus_with_time
- assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - Time.utc(2000, 1, 1)
- assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2), ActiveSupport::TimeZone["Hawaii"]) - Time.utc(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - Time.utc(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2), ActiveSupport::TimeZone["Hawaii"]) - Time.utc(2000, 1, 1)
end
def test_minus_with_time_precision
- assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"]) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
- assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["Hawaii"]) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
+ assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"]) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
+ assert_equal 86_399.999999998, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["Hawaii"]) - Time.utc(2000, 1, 2, 0, 0, 0, Rational(1, 1000))
end
def test_minus_with_time_with_zone
@@ -352,20 +358,20 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_minus_with_time_with_zone_precision
twz1 = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0, Rational(1, 1000)), ActiveSupport::TimeZone["UTC"])
twz2 = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"])
- assert_equal 86_399.999999998, twz2 - twz1
+ assert_equal 86_399.999999998, twz2 - twz1
end
def test_minus_with_datetime
- assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - DateTime.civil(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - DateTime.civil(2000, 1, 1)
end
def test_minus_with_datetime_precision
- assert_equal 86_399.999999999, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"]) - DateTime.civil(2000, 1, 1)
+ assert_equal 86_399.999999999, ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 23, 59, 59, Rational(999999999, 1000)), ActiveSupport::TimeZone["UTC"]) - DateTime.civil(2000, 1, 1)
end
def test_minus_with_wrapped_datetime
- assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - Time.utc(2000, 1, 1)
- assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - DateTime.civil(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - Time.utc(2000, 1, 1)
+ assert_equal 86_400.0, ActiveSupport::TimeWithZone.new(DateTime.civil(2000, 1, 2), ActiveSupport::TimeZone["UTC"]) - DateTime.civil(2000, 1, 1)
end
def test_plus_and_minus_enforce_spring_dst_rules
@@ -475,7 +481,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_acts_like_time
- assert @twz.acts_like_time?
+ assert_predicate @twz, :acts_like_time?
assert @twz.acts_like?(:time)
assert ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone).acts_like?(:time)
end
@@ -486,7 +492,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_blank?
- assert_not @twz.blank?
+ assert_not_predicate @twz, :blank?
end
def test_is_a
@@ -501,17 +507,17 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_method_missing_with_time_return_value
assert_instance_of ActiveSupport::TimeWithZone, @twz.months_since(1)
- assert_equal Time.utc(2000, 1, 31, 19, 0 , 0), @twz.months_since(1).time
+ assert_equal Time.utc(2000, 1, 31, 19, 0, 0), @twz.months_since(1).time
end
def test_marshal_dump_and_load
marshal_str = Marshal.dump(@twz)
mtime = Marshal.load(marshal_str)
assert_equal Time.utc(2000, 1, 1, 0), mtime.utc
- assert mtime.utc.utc?
+ assert_predicate mtime.utc, :utc?
assert_equal ActiveSupport::TimeZone["Eastern Time (US & Canada)"], mtime.time_zone
assert_equal Time.utc(1999, 12, 31, 19), mtime.time
- assert mtime.time.utc?
+ assert_predicate mtime.time, :utc?
assert_equal @twz.inspect, mtime.inspect
end
@@ -520,16 +526,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
marshal_str = Marshal.dump(twz)
mtime = Marshal.load(marshal_str)
assert_equal Time.utc(2000, 1, 1, 0), mtime.utc
- assert mtime.utc.utc?
+ assert_predicate mtime.utc, :utc?
assert_equal "America/New_York", mtime.time_zone.name
assert_equal Time.utc(1999, 12, 31, 19), mtime.time
- assert mtime.time.utc?
+ assert_predicate mtime.time, :utc?
assert_equal @twz.inspect, mtime.inspect
end
def test_freeze
@twz.freeze
- assert @twz.frozen?
+ assert_predicate @twz, :frozen?
end
def test_freeze_preloads_instance_variables
@@ -1029,8 +1035,8 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
def test_nil_time_zone
Time.use_zone nil do
- assert !@t.in_time_zone.respond_to?(:period), "no period method"
- assert !@dt.in_time_zone.respond_to?(:period), "no period method"
+ assert_not_respond_to @t.in_time_zone, :period, "no period method"
+ assert_not_respond_to @dt.in_time_zone, :period, "no period method"
end
end
@@ -1218,7 +1224,7 @@ class TimeWithZoneMethodsForDate < ActiveSupport::TestCase
def test_nil_time_zone
with_tz_default nil do
- assert !@d.in_time_zone.respond_to?(:period), "no period method"
+ assert_not_respond_to @d.in_time_zone, :period, "no period method"
end
end
@@ -1267,9 +1273,9 @@ class TimeWithZoneMethodsForString < ActiveSupport::TestCase
def test_nil_time_zone
with_tz_default nil do
- assert !@s.in_time_zone.respond_to?(:period), "no period method"
- assert !@u.in_time_zone.respond_to?(:period), "no period method"
- assert !@z.in_time_zone.respond_to?(:period), "no period method"
+ assert_not_respond_to @s.in_time_zone, :period, "no period method"
+ assert_not_respond_to @u.in_time_zone, :period, "no period method"
+ assert_not_respond_to @z.in_time_zone, :period, "no period method"
end
end
@@ -1301,4 +1307,10 @@ class TimeWithZoneMethodsForString < ActiveSupport::TestCase
assert_raise(ArgumentError) { @u.in_time_zone(Object.new) }
assert_raise(ArgumentError) { @z.in_time_zone(Object.new) }
end
+
+ def test_in_time_zone_with_ambiguous_time
+ with_tz_default "Moscow" do
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), "2014-10-26 01:00:00".in_time_zone
+ end
+ end
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index d636da46d2..c0c4c4cac5 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -185,6 +185,61 @@ class DependenciesTest < ActiveSupport::TestCase
end
end
+ # Regression see https://github.com/rails/rails/issues/31694
+ def test_included_constant_that_changes_to_have_exception_then_back_does_not_loop_forever
+ # This constant references a nested constant whose namespace will be auto-generated
+ parent_constant = <<-RUBY
+ class ConstantReloadError
+ AnotherConstant::ReloadError
+ end
+ RUBY
+
+ # This constant's namespace will be auto-generated,
+ # also, we'll edit it to contain an error at load-time
+ child_constant = <<-RUBY
+ class AnotherConstant::ReloadError
+ # no_such_method_as_this
+ end
+ RUBY
+
+ # Create a version which contains an error during loading
+ child_constant_with_error = child_constant.sub("# no_such_method_as_this", "no_such_method_as_this")
+
+ fixtures_path = File.join(__dir__, "autoloading_fixtures")
+ Dir.mktmpdir(nil, fixtures_path) do |tmpdir|
+ # Set up the file structure where constants will be loaded from
+ child_constant_path = "#{tmpdir}/another_constant/reload_error.rb"
+ File.write("#{tmpdir}/constant_reload_error.rb", parent_constant)
+ Dir.mkdir("#{tmpdir}/another_constant")
+ File.write(child_constant_path, child_constant_with_error)
+
+ tmpdir_name = tmpdir.split("/").last
+ with_loading("autoloading_fixtures/#{tmpdir_name}") do
+ # Load the file, with the error:
+ assert_raises(NameError) {
+ ConstantReloadError
+ }
+
+ Timeout.timeout(0.1) do
+ # Remove the constant, as if Rails development middleware is reloading changed files:
+ ActiveSupport::Dependencies.remove_unloadable_constants!
+ refute defined?(AnotherConstant::ReloadError)
+ end
+
+ # Change the file, so that it is **correct** this time:
+ File.write(child_constant_path, child_constant)
+
+ # Again: Remove the constant, as if Rails development middleware is reloading changed files:
+ ActiveSupport::Dependencies.remove_unloadable_constants!
+ refute defined?(AnotherConstant::ReloadError)
+
+ # Now, reload the _fixed_ constant:
+ assert ConstantReloadError
+ assert AnotherConstant::ReloadError
+ end
+ end
+ end
+
def test_module_loading
with_autoloading_fixtures do
assert_kind_of Module, A
@@ -752,7 +807,7 @@ class DependenciesTest < ActiveSupport::TestCase
Object.const_set :C, Class.new { def self.before_remove_const; end }
C.unloadable
assert_called(C, :before_remove_const, times: 1) do
- assert C.respond_to?(:before_remove_const)
+ assert_respond_to C, :before_remove_const
ActiveSupport::Dependencies.clear
assert !defined?(C)
end
diff --git a/activesupport/test/deprecation/method_wrappers_test.rb b/activesupport/test/deprecation/method_wrappers_test.rb
index 04e2325754..439e117c1d 100644
--- a/activesupport/test/deprecation/method_wrappers_test.rb
+++ b/activesupport/test/deprecation/method_wrappers_test.rb
@@ -8,6 +8,16 @@ class MethodWrappersTest < ActiveSupport::TestCase
@klass = Class.new do
def new_method; "abc" end
alias_method :old_method, :new_method
+
+ protected
+
+ def new_protected_method; "abc" end
+ alias_method :old_protected_method, :new_protected_method
+
+ private
+
+ def new_private_method; "abc" end
+ alias_method :old_private_method, :new_private_method
end
end
@@ -33,4 +43,16 @@ class MethodWrappersTest < ActiveSupport::TestCase
assert_deprecated(warning, deprecator) { assert_equal "abc", @klass.new.old_method }
end
+
+ def test_deprecate_methods_protected_method
+ ActiveSupport::Deprecation.deprecate_methods(@klass, old_protected_method: :new_protected_method)
+
+ assert(@klass.protected_method_defined?(:old_protected_method))
+ end
+
+ def test_deprecate_methods_private_method
+ ActiveSupport::Deprecation.deprecate_methods(@klass, old_private_method: :new_private_method)
+
+ assert(@klass.private_method_defined?(:old_private_method))
+ end
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index f2267a822f..60673c032b 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -158,7 +158,7 @@ class DeprecationTest < ActiveSupport::TestCase
stderr_output = capture(:stderr) {
assert_nil behavior.call("Some error!", ["call stack!"], "horizon", "gem")
}
- assert stderr_output.empty?
+ assert_empty stderr_output
end
def test_default_notify_behavior
diff --git a/activesupport/test/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb
index 1f8b4a8605..2c94c3c56c 100644
--- a/activesupport/test/descendants_tracker_test_cases.rb
+++ b/activesupport/test/descendants_tracker_test_cases.rb
@@ -37,7 +37,7 @@ module DescendantsTrackerTestCases
mark_as_autoloaded(*ALL) do
ActiveSupport::DescendantsTracker.clear
ALL.each do |k|
- assert ActiveSupport::DescendantsTracker.descendants(k).empty?
+ assert_empty ActiveSupport::DescendantsTracker.descendants(k)
end
end
end
diff --git a/activesupport/test/descendants_tracker_with_autoloading_test.rb b/activesupport/test/descendants_tracker_with_autoloading_test.rb
index 7c396b7c8e..d4fedb5a67 100644
--- a/activesupport/test/descendants_tracker_with_autoloading_test.rb
+++ b/activesupport/test/descendants_tracker_with_autoloading_test.rb
@@ -12,7 +12,7 @@ class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase
mark_as_autoloaded(*ALL) do
ActiveSupport::DescendantsTracker.clear
ALL.each do |k|
- assert ActiveSupport::DescendantsTracker.descendants(k).empty?
+ assert_empty ActiveSupport::DescendantsTracker.descendants(k)
end
end
end
diff --git a/activesupport/test/digest_test.rb b/activesupport/test/digest_test.rb
new file mode 100644
index 0000000000..83ff2a8d83
--- /dev/null
+++ b/activesupport/test/digest_test.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "openssl"
+
+class DigestTest < ActiveSupport::TestCase
+ class InvalidDigest; end
+ def test_with_default_hash_digest_class
+ assert_equal ::Digest::MD5.hexdigest("hello friend"), ActiveSupport::Digest.hexdigest("hello friend")
+ end
+
+ def test_with_custom_hash_digest_class
+ original_hash_digest_class = ActiveSupport::Digest.hash_digest_class
+
+ ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1
+ digest = ActiveSupport::Digest.hexdigest("hello friend")
+
+ assert_equal 32, digest.length
+ assert_equal ::Digest::SHA1.hexdigest("hello friend")[0...32], digest
+ ensure
+ ActiveSupport::Digest.hash_digest_class = original_hash_digest_class
+ end
+
+ def test_should_raise_argument_error_if_custom_digest_is_missing_hexdigest_method
+ assert_raises(ArgumentError) { ActiveSupport::Digest.hash_digest_class = InvalidDigest }
+ end
+end
diff --git a/activesupport/test/encrypted_configuration_test.rb b/activesupport/test/encrypted_configuration_test.rb
index 471faa8c12..93ccf457de 100644
--- a/activesupport/test/encrypted_configuration_test.rb
+++ b/activesupport/test/encrypted_configuration_test.rb
@@ -10,8 +10,10 @@ class EncryptedConfigurationTest < ActiveSupport::TestCase
@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"
+ @credentials = ActiveSupport::EncryptedConfiguration.new(
+ config_path: @credentials_config_path, key_path: @credentials_key_path,
+ env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true
+ )
end
teardown do
@@ -51,6 +53,14 @@ class EncryptedConfigurationTest < ActiveSupport::TestCase
assert_equal "things", @credentials[:new]
end
+ test "raise error when writing an invalid format value" do
+ assert_raise(Psych::SyntaxError) do
+ @credentials.change do |config_file|
+ config_file.write "login: *login\n username: dummy"
+ end
+ end
+ end
+
test "raises key error when accessing config via bang method" do
assert_raise(KeyError) { @credentials.something! }
end
diff --git a/activesupport/test/encrypted_file_test.rb b/activesupport/test/encrypted_file_test.rb
index 7259726d08..ba3bbef903 100644
--- a/activesupport/test/encrypted_file_test.rb
+++ b/activesupport/test/encrypted_file_test.rb
@@ -12,8 +12,9 @@ class EncryptedFileTest < ActiveSupport::TestCase
@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"
+ @encrypted_file = ActiveSupport::EncryptedFile.new(
+ content_path: @content_path, key_path: @key_path, env_key: "CONTENT_KEY", raise_if_missing_key: true
+ )
end
teardown do
@@ -47,4 +48,12 @@ class EncryptedFileTest < ActiveSupport::TestCase
assert_equal "#{@content} and went by the lake", @encrypted_file.read
end
+
+ test "raise MissingKeyError when key is missing" do
+ assert_raise(ActiveSupport::EncryptedFile::MissingKeyError) do
+ ActiveSupport::EncryptedFile.new(
+ content_path: @content_path, key_path: "", env_key: "", raise_if_missing_key: true
+ ).read
+ end
+ end
end
diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb
index 9b560f7f42..d3af0dbef3 100644
--- a/activesupport/test/evented_file_update_checker_test.rb
+++ b/activesupport/test/evented_file_update_checker_test.rb
@@ -39,18 +39,18 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
FileUtils.touch(tmpfiles)
checker = new_checker(tmpfiles) {}
- assert !checker.updated?
+ assert_not_predicate checker, :updated?
# Pipes used for flow control across fork.
boot_reader, boot_writer = IO.pipe
touch_reader, touch_writer = IO.pipe
pid = fork do
- assert checker.updated?
+ assert_predicate checker, :updated?
# Clear previous check value.
checker.execute
- assert !checker.updated?
+ assert_not_predicate checker, :updated?
# Fork is booted, ready for file to be touched
# notify parent process.
@@ -60,7 +60,7 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
# has been touched.
IO.select([touch_reader])
- assert checker.updated?
+ assert_predicate checker, :updated?
end
assert pid
@@ -72,7 +72,7 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
# Notify fork that files have been touched.
touch_writer.write("touched")
- assert checker.updated?
+ assert_predicate checker, :updated?
Process.wait(pid)
end
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
index f8266dac06..daf7f9d139 100644
--- a/activesupport/test/file_update_checker_shared_tests.rb
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -89,12 +89,12 @@ module FileUpdateCheckerSharedTests
i = 0
checker = new_checker(tmpfiles) { i += 1 }
- assert !checker.updated?
+ assert_not_predicate checker, :updated?
touch(tmpfiles)
wait
- assert checker.updated?
+ assert_predicate checker, :updated?
end
test "updated should become true when watched files are modified" do
@@ -103,12 +103,12 @@ module FileUpdateCheckerSharedTests
FileUtils.touch(tmpfiles)
checker = new_checker(tmpfiles) { i += 1 }
- assert !checker.updated?
+ assert_not_predicate checker, :updated?
touch(tmpfiles)
wait
- assert checker.updated?
+ assert_predicate checker, :updated?
end
test "updated should become true when watched files are deleted" do
@@ -117,12 +117,12 @@ module FileUpdateCheckerSharedTests
FileUtils.touch(tmpfiles)
checker = new_checker(tmpfiles) { i += 1 }
- assert !checker.updated?
+ assert_not_predicate checker, :updated?
rm_f(tmpfiles)
wait
- assert checker.updated?
+ assert_predicate checker, :updated?
end
test "should be robust to handle files with wrong modified time" do
@@ -164,14 +164,14 @@ module FileUpdateCheckerSharedTests
i = 0
checker = new_checker(tmpfiles) { i += 1 }
- assert !checker.updated?
+ assert_not_predicate checker, :updated?
touch(tmpfiles)
wait
- assert checker.updated?
+ assert_predicate checker, :updated?
checker.execute
- assert !checker.updated?
+ assert_not_predicate checker, :updated?
end
test "should execute the block if files change in a watched directory one extension" do
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 6cc1039d8e..5e50acf5db 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -224,6 +224,12 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal("json_html_api", ActiveSupport::Inflector.underscore("JSONHTMLAPI"))
end
+ def test_acronym_regexp_is_deprecated
+ assert_deprecated do
+ ActiveSupport::Inflector.inflections.acronym_regex
+ end
+ end
+
def test_underscore
CamelToUnderscore.each do |camel, underscore|
assert_equal(underscore, ActiveSupport::Inflector.underscore(camel))
@@ -454,12 +460,12 @@ class InflectorTest < ActiveSupport::TestCase
ActiveSupport::Inflector.inflections(:es) { |inflect| inflect.clear }
- assert ActiveSupport::Inflector.inflections(:es).plurals.empty?
- assert ActiveSupport::Inflector.inflections(:es).singulars.empty?
- assert ActiveSupport::Inflector.inflections(:es).uncountables.empty?
- assert !ActiveSupport::Inflector.inflections.plurals.empty?
- assert !ActiveSupport::Inflector.inflections.singulars.empty?
- assert !ActiveSupport::Inflector.inflections.uncountables.empty?
+ assert_empty ActiveSupport::Inflector.inflections(:es).plurals
+ assert_empty ActiveSupport::Inflector.inflections(:es).singulars
+ assert_empty ActiveSupport::Inflector.inflections(:es).uncountables
+ assert_not_empty ActiveSupport::Inflector.inflections.plurals
+ assert_not_empty ActiveSupport::Inflector.inflections.singulars
+ assert_not_empty ActiveSupport::Inflector.inflections.uncountables
end
def test_clear_all
@@ -472,10 +478,10 @@ class InflectorTest < ActiveSupport::TestCase
inflect.clear :all
- assert inflect.plurals.empty?
- assert inflect.singulars.empty?
- assert inflect.uncountables.empty?
- assert inflect.humans.empty?
+ assert_empty inflect.plurals
+ assert_empty inflect.singulars
+ assert_empty inflect.uncountables
+ assert_empty inflect.humans
end
end
@@ -489,10 +495,10 @@ class InflectorTest < ActiveSupport::TestCase
inflect.clear
- assert inflect.plurals.empty?
- assert inflect.singulars.empty?
- assert inflect.uncountables.empty?
- assert inflect.humans.empty?
+ assert_empty inflect.plurals
+ assert_empty inflect.singulars
+ assert_empty inflect.uncountables
+ assert_empty inflect.humans
end
end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 96ad8dfbdb..340a2abf75 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -187,7 +187,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_array_should_pass_encoding_options_to_children_in_as_json
people = [
{ name: "John", address: { city: "London", country: "UK" } },
- { name: "Jean", address: { city: "Paris" , country: "France" } }
+ { name: "Jean", address: { city: "Paris", country: "France" } }
]
json = people.as_json only: [:address, :city]
expected = [
@@ -201,7 +201,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
def test_array_should_pass_encoding_options_to_children_in_to_json
people = [
{ name: "John", address: { city: "London", country: "UK" } },
- { name: "Jean", address: { city: "Paris" , country: "France" } }
+ { name: "Jean", address: { city: "Paris", country: "France" } }
]
json = people.to_json only: [:address, :city]
@@ -210,10 +210,10 @@ class TestJSONEncoding < ActiveSupport::TestCase
People = Class.new(BasicObject) do
include Enumerable
- def initialize()
+ def initialize
@people = [
{ name: "John", address: { city: "London", country: "UK" } },
- { name: "Jean", address: { city: "Paris" , country: "France" } }
+ { name: "Jean", address: { city: "Paris", country: "France" } }
]
end
def each(*, &blk)
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index f51fbe2671..560b86b1a3 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -469,10 +469,10 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_respond_to_knows_which_methods_the_proxy_responds_to
- assert "".mb_chars.respond_to?(:slice) # Defined on Chars
- assert "".mb_chars.respond_to?(:capitalize!) # Defined on Chars
- assert "".mb_chars.respond_to?(:gsub) # Defined on String
- assert !"".mb_chars.respond_to?(:undefined_method) # Not defined
+ assert_respond_to "".mb_chars, :slice # Defined on Chars
+ assert_respond_to "".mb_chars, :capitalize! # Defined on Chars
+ assert_respond_to "".mb_chars, :gsub # Defined on String
+ assert_not_respond_to "".mb_chars, :undefined_method # Not defined
end
def test_method_works_for_proxyed_methods
@@ -485,7 +485,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
end
def test_acts_like_string
- assert "Bambi".mb_chars.acts_like_string?
+ assert_predicate "Bambi".mb_chars, :acts_like_string?
end
end
diff --git a/activesupport/test/notifications/instrumenter_test.rb b/activesupport/test/notifications/instrumenter_test.rb
index 1d76c91d30..d5c9e82e9f 100644
--- a/activesupport/test/notifications/instrumenter_test.rb
+++ b/activesupport/test/notifications/instrumenter_test.rb
@@ -47,13 +47,13 @@ module ActiveSupport
def test_start
instrumenter.start("foo", payload)
assert_equal [["foo", instrumenter.id, payload]], notifier.starts
- assert_predicate notifier.finishes, :empty?
+ assert_empty notifier.finishes
end
def test_finish
instrumenter.finish("foo", payload)
assert_equal [["foo", instrumenter.id, payload]], notifier.finishes
- assert_predicate notifier.starts, :empty?
+ assert_empty notifier.starts
end
end
end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index 5cfbd60131..28a02aafc8 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -250,8 +250,8 @@ module Notifications
time = Time.now
event = event(:foo, time, time + 0.01, random_id, {})
- assert_equal :foo, event.name
- assert_equal time, event.time
+ assert_equal :foo, event.name
+ assert_equal time, event.time
assert_in_delta 10.0, event.duration, 0.00001
end
diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb
index 6d6958be49..365fa96f4d 100644
--- a/activesupport/test/number_helper_i18n_test.rb
+++ b/activesupport/test/number_helper_i18n_test.rb
@@ -35,7 +35,7 @@ module ActiveSupport
thousand: "t",
million: "m",
billion: "b",
- trillion: "t" ,
+ trillion: "t",
quadrillion: "q"
}
}
@@ -77,10 +77,10 @@ module ActiveSupport
end
def test_number_with_i18n_precision
- #Delimiter was set to ""
+ # Delimiter was set to ""
assert_equal("10000", number_to_rounded(10000, locale: "ts"))
- #Precision inherited and significant was set
+ # Precision inherited and significant was set
assert_equal("1.00", number_to_rounded(1.0, locale: "ts"))
end
@@ -90,7 +90,7 @@ module ActiveSupport
end
def test_number_with_i18n_delimiter
- #Delimiter "," and separator "."
+ # Delimiter "," and separator "."
assert_equal("1,000,000.234", number_to_delimited(1000000.234, locale: "ts"))
end
@@ -114,7 +114,7 @@ module ActiveSupport
end
def test_number_to_i18n_human_size
- #b for bytes and k for kbytes
+ # b for bytes and k for kbytes
assert_equal("2 k", number_to_human_size(2048, locale: "ts"))
assert_equal("42 b", number_to_human_size(42, locale: "ts"))
end
@@ -125,11 +125,11 @@ module ActiveSupport
end
def test_number_to_human_with_default_translation_scope
- #Using t for thousand
+ # Using t for thousand
assert_equal "2 t", number_to_human(2000, locale: "ts")
- #Significant was set to true with precision 2, using b for billion
+ # Significant was set to true with precision 2, using b for billion
assert_equal "1.2 b", number_to_human(1234567890, locale: "ts")
- #Using pluralization (Ten/Tens and Tenth/Tenths)
+ # Using pluralization (Ten/Tens and Tenth/Tenths)
assert_equal "1 Tenth", number_to_human(0.1, locale: "ts")
assert_equal "1.3 Tenth", number_to_human(0.134, locale: "ts")
assert_equal "2 Tenths", number_to_human(0.2, locale: "ts")
@@ -144,7 +144,7 @@ module ActiveSupport
end
def test_number_to_human_with_custom_translation_scope
- #Significant was set to true with precision 2, with custom translated units
+ # Significant was set to true with precision 2, with custom translated units
assert_equal "4.3 cm", number_to_human(0.0432, locale: "ts", units: :custom_units_for_number_to_human)
end
end
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index b4795b6ee1..16ccc5572c 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -260,7 +260,7 @@ module ActiveSupport
assert_equal "40 KB", number_helper.number_to_human_size(41100, precision: 2)
assert_equal "1.0 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 2, strip_insignificant_zeros: false)
assert_equal "1.012 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 3, significant: false)
- assert_equal "1 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 0, significant: true) #ignores significant it precision is 0
+ assert_equal "1 KB", number_helper.number_to_human_size(kilobytes(1.0123), precision: 0, significant: true) # ignores significant it precision is 0
end
end
@@ -292,7 +292,7 @@ module ActiveSupport
assert_equal "489.0 Thousand", number_helper.number_to_human(489000, precision: 4, strip_insignificant_zeros: false)
assert_equal "1.2346 Million", number_helper.number_to_human(1234567, precision: 4, significant: false)
assert_equal "1,2 Million", number_helper.number_to_human(1234567, precision: 1, significant: false, separator: ",")
- assert_equal "1 Million", number_helper.number_to_human(1234567, precision: 0, significant: true, separator: ",") #significant forced to false
+ assert_equal "1 Million", number_helper.number_to_human(1234567, precision: 0, significant: true, separator: ",") # significant forced to false
assert_equal "1 Million", number_helper.number_to_human(999999)
assert_equal "1 Billion", number_helper.number_to_human(999999999)
end
@@ -300,13 +300,13 @@ module ActiveSupport
def test_number_to_human_with_custom_units
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
- #Only integers
+ # Only integers
volume = { unit: "ml", thousand: "lt", million: "m3" }
assert_equal "123 lt", number_helper.number_to_human(123456, units: volume)
assert_equal "12 ml", number_helper.number_to_human(12, units: volume)
assert_equal "1.23 m3", number_helper.number_to_human(1234567, units: volume)
- #Including fractionals
+ # Including fractionals
distance = { mili: "mm", centi: "cm", deci: "dm", unit: "m", ten: "dam", hundred: "hm", thousand: "km" }
assert_equal "1.23 mm", number_helper.number_to_human(0.00123, units: distance)
assert_equal "1.23 cm", number_helper.number_to_human(0.0123, units: distance)
@@ -319,7 +319,7 @@ module ActiveSupport
assert_equal "1.23 km", number_helper.number_to_human(1230, units: distance)
assert_equal "12.3 km", number_helper.number_to_human(12300, units: distance)
- #The quantifiers don't need to be a continuous sequence
+ # The quantifiers don't need to be a continuous sequence
gangster = { hundred: "hundred bucks", million: "thousand quids" }
assert_equal "1 hundred bucks", number_helper.number_to_human(100, units: gangster)
assert_equal "25 hundred bucks", number_helper.number_to_human(2500, units: gangster)
@@ -329,11 +329,11 @@ module ActiveSupport
assert_equal "25 thousand quids", number_helper.number_to_human(25000000, units: gangster)
assert_equal "12300 thousand quids", number_helper.number_to_human(12345000000, units: gangster)
- #Spaces are stripped from the resulting string
+ # Spaces are stripped from the resulting string
assert_equal "4", number_helper.number_to_human(4, units: { unit: "", ten: "tens " })
assert_equal "4.5 tens", number_helper.number_to_human(45, units: { unit: "", ten: " tens " })
- #Uses only the provided units and does not try to use larger ones
+ # Uses only the provided units and does not try to use larger ones
assert_equal "1000 kilometers", number_helper.number_to_human(1_000_000, units: { unit: "meter", thousand: "kilometers" })
end
end
diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index 2c67bb02ac..cba5b8a8de 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -82,8 +82,8 @@ class OrderedOptionsTest < ActiveSupport::TestCase
def test_introspection
a = ActiveSupport::OrderedOptions.new
- assert a.respond_to?(:blah)
- assert a.respond_to?(:blah=)
+ assert_respond_to a, :blah
+ assert_respond_to a, :blah=
assert_equal 42, a.method(:blah=).call(42)
assert_equal 42, a.method(:blah).call
end
@@ -91,7 +91,7 @@ class OrderedOptionsTest < ActiveSupport::TestCase
def test_raises_with_bang
a = ActiveSupport::OrderedOptions.new
a[:foo] = :bar
- assert a.respond_to?(:foo!)
+ assert_respond_to a, :foo!
assert_nothing_raised { a.foo! }
assert_equal a.foo, a.foo!
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 05c2fb59be..1c19c92bb0 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -39,7 +39,7 @@ class SafeBufferTest < ActiveSupport::TestCase
end
test "Should be considered safe" do
- assert @buffer.html_safe?
+ assert_predicate @buffer, :html_safe?
end
test "Should return a safe buffer when calling to_s" do
@@ -78,13 +78,13 @@ class SafeBufferTest < ActiveSupport::TestCase
test "Should not return safe buffer from gsub" do
altered_buffer = @buffer.gsub("", "asdf")
assert_equal "asdf", altered_buffer
- assert !altered_buffer.html_safe?
+ assert_not_predicate altered_buffer, :html_safe?
end
test "Should not return safe buffer from gsub!" do
@buffer.gsub!("", "asdf")
assert_equal "asdf", @buffer
- assert !@buffer.html_safe?
+ assert_not_predicate @buffer, :html_safe?
end
test "Should escape dirty buffers on add" do
@@ -101,13 +101,13 @@ class SafeBufferTest < ActiveSupport::TestCase
test "Should preserve html_safe? status on copy" do
@buffer.gsub!("", "<>")
- assert !@buffer.dup.html_safe?
+ assert_not_predicate @buffer.dup, :html_safe?
end
test "Should return safe buffer when added with another safe buffer" do
clean = "<script>".html_safe
result_buffer = @buffer + clean
- assert result_buffer.html_safe?
+ assert_predicate result_buffer, :html_safe?
assert_equal "<script>", result_buffer
end
@@ -127,8 +127,8 @@ class SafeBufferTest < ActiveSupport::TestCase
end
test "clone_empty keeps the original dirtyness" do
- assert @buffer.clone_empty.html_safe?
- assert !@buffer.gsub!("", "").clone_empty.html_safe?
+ assert_predicate @buffer.clone_empty, :html_safe?
+ assert_not_predicate @buffer.gsub!("", "").clone_empty, :html_safe?
end
test "Should be safe when sliced if original value was safe" do
diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb
index efd2bcfa0f..0a607594a2 100644
--- a/activesupport/test/security_utils_test.rb
+++ b/activesupport/test/security_utils_test.rb
@@ -9,8 +9,14 @@ class SecurityUtilsTest < ActiveSupport::TestCase
assert_not ActiveSupport::SecurityUtils.secure_compare("a", "b")
end
- def test_variable_size_secure_compare_should_perform_string_comparison
- assert ActiveSupport::SecurityUtils.variable_size_secure_compare("a", "a")
- assert_not ActiveSupport::SecurityUtils.variable_size_secure_compare("a", "b")
+ def test_fixed_length_secure_compare_should_perform_string_comparison
+ assert ActiveSupport::SecurityUtils.fixed_length_secure_compare("a", "a")
+ assert !ActiveSupport::SecurityUtils.fixed_length_secure_compare("a", "b")
+ end
+
+ def test_fixed_length_secure_compare_raise_on_length_mismatch
+ assert_raises(ArgumentError, "string length mismatch.") do
+ ActiveSupport::SecurityUtils.fixed_length_secure_compare("a", "ab")
+ end
end
end
diff --git a/activesupport/test/string_inquirer_test.rb b/activesupport/test/string_inquirer_test.rb
index bf8f878a32..09726b144a 100644
--- a/activesupport/test/string_inquirer_test.rb
+++ b/activesupport/test/string_inquirer_test.rb
@@ -8,11 +8,11 @@ class StringInquirerTest < ActiveSupport::TestCase
end
def test_match
- assert @string_inquirer.production?
+ assert_predicate @string_inquirer, :production?
end
def test_miss
- assert_not @string_inquirer.development?
+ assert_not_predicate @string_inquirer, :development?
end
def test_missing_question_mark
diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb
index 5fd6a6b316..e2b41cf8ee 100644
--- a/activesupport/test/tagged_logging_test.rb
+++ b/activesupport/test/tagged_logging_test.rb
@@ -21,7 +21,7 @@ class TaggedLoggingTest < ActiveSupport::TestCase
assert_nil logger.formatter
ActiveSupport::TaggedLogging.new(logger)
assert_not_nil logger.formatter
- assert logger.formatter.respond_to?(:tagged)
+ assert_respond_to logger.formatter, :tagged
end
test "tagged once" do
@@ -74,11 +74,12 @@ class TaggedLoggingTest < ActiveSupport::TestCase
test "keeps each tag in their own thread" do
@logger.tagged("BCX") do
Thread.new do
+ @logger.info "Dull story"
@logger.tagged("OMG") { @logger.info "Cool story" }
end.join
@logger.info "Funky time"
end
- assert_equal "[OMG] Cool story\n[BCX] Funky time\n", @output.string
+ assert_equal "Dull story\n[OMG] Cool story\n[BCX] Funky time\n", @output.string
end
test "keeps each tag in their own instance" do
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 84e4953fe2..eced622137 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -115,6 +115,35 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
end
+ def test_hash_of_expressions
+ assert_difference "@object.num" => 1, "@object.num + 1" => 1 do
+ @object.increment
+ end
+ end
+
+ def test_hash_of_expressions_with_message
+ error = assert_raises Minitest::Assertion do
+ assert_difference({ "@object.num" => 0 }, "Object Changed") do
+ @object.increment
+ end
+ end
+ assert_equal "Object Changed.\n\"@object.num\" didn't change by 0.\nExpected: 0\n Actual: 1", error.message
+ end
+
+ def test_hash_of_lambda_expressions
+ assert_difference -> { @object.num } => 1, -> { @object.num + 1 } => 1 do
+ @object.increment
+ end
+ end
+
+ def test_hash_of_expressions_identify_failure
+ assert_raises(Minitest::Assertion) do
+ assert_difference "@object.num" => 1, "1 + 1" => 1 do
+ @object.increment
+ end
+ end
+ end
+
def test_assert_changes_pass
assert_changes "@object.num" do
@object.increment
@@ -156,6 +185,16 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
end
+ def test_assert_changes_with_to_option_but_no_change_has_special_message
+ error = assert_raises Minitest::Assertion do
+ assert_changes "@object.num", to: 0 do
+ # no changes
+ end
+ end
+
+ assert_equal "\"@object.num\" didn't change. It was already 0", error.message
+ end
+
def test_assert_changes_with_wrong_to_option
assert_raises Minitest::Assertion do
assert_changes "@object.num", to: 2 do
@@ -218,6 +257,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase
def test_assert_changes_with_message
error = assert_raises Minitest::Assertion do
assert_changes "@object.num", "@object.num should 1", to: 1 do
+ @object.decrement
end
end
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
index 4172c18ead..9c2c635f43 100644
--- a/activesupport/test/time_travel_test.rb
+++ b/activesupport/test/time_travel_test.rb
@@ -98,7 +98,7 @@ class TimeTravelTest < ActiveSupport::TestCase
travel_to outer_expected_time do
e = assert_raises(RuntimeError) do
travel_to(inner_expected_time) do
- #noop
+ # noop
end
end
assert_match(/Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing\./, e.message)
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index acb0ecd226..405c8f315b 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -32,6 +32,12 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_period_for_local_with_ambigiuous_time
+ zone = ActiveSupport::TimeZone["Moscow"]
+ period = zone.period_for_local(Time.utc(2015, 1, 1))
+ assert_equal period, zone.period_for_local(Time.utc(2014, 10, 26, 1, 0, 0))
+ end
+
def test_from_integer_to_map
assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[-28800] # PST
end
@@ -195,6 +201,11 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal "EDT", twz.zone
end
+ def test_local_with_ambiguous_time
+ zone = ActiveSupport::TimeZone["Moscow"]
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.local(2014, 10, 26, 1, 0, 0)
+ end
+
def test_at
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
secs = 946684800.0
@@ -303,6 +314,11 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_iso8601_with_ambiguous_time
+ zone = ActiveSupport::TimeZone["Moscow"]
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.parse("2014-10-26T01:00:00")
+ end
+
def test_parse
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.parse("1999-12-31 19:00:00")
@@ -412,6 +428,11 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal "argument out of range", exception.message
end
+ def test_parse_with_ambiguous_time
+ zone = ActiveSupport::TimeZone["Moscow"]
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.parse("2014-10-26 01:00:00")
+ end
+
def test_rfc3339
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.rfc3339("1999-12-31T14:00:00-10:00")
@@ -604,6 +625,11 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_strptime_with_ambiguous_time
+ zone = ActiveSupport::TimeZone["Moscow"]
+ assert_equal Time.utc(2014, 10, 25, 22, 0, 0), zone.strptime("2014-10-26 01:00:00", "%Y-%m-%d %H:%M:%S")
+ end
+
def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize
tzinfo = TZInfo::Timezone.get("America/New_York")
zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo)
@@ -692,6 +718,13 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_all_uninfluenced_by_time_zone_lookups_delegated_to_tzinfo
+ ActiveSupport::TimeZone.clear
+ galapagos = ActiveSupport::TimeZone["Pacific/Galapagos"]
+ all_zones = ActiveSupport::TimeZone.all
+ assert_not_includes all_zones, galapagos
+ end
+
def test_index
assert_nil ActiveSupport::TimeZone["bogus"]
assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone["Central Time (US & Canada)"]
diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb
index 5c13f493f1..3eef3946a3 100644
--- a/activesupport/test/xml_mini/libxml_engine_test.rb
+++ b/activesupport/test/xml_mini/libxml_engine_test.rb
@@ -6,7 +6,7 @@ XMLMiniEngineTest.run_with_gem("libxml") do
class LibxmlEngineTest < XMLMiniEngineTest
def setup
super
- LibXML::XML::Error.set_handler(&lambda { |error| }) #silence libxml, exceptions will do
+ LibXML::XML::Error.set_handler(&lambda { |error| }) # silence libxml, exceptions will do
end
private
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index 5e46406a7b..18a3f2ca66 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -114,7 +114,7 @@ module XmlMiniTest
end
test "#to_tag accepts decimal types" do
- @xml.to_tag(:b, ::BigDecimal.new("1.2"), @options)
+ @xml.to_tag(:b, BigDecimal("1.2"), @options)
assert_xml("<b type=\"decimal\">1.2</b>")
end
diff --git a/ci/qunit-selenium-runner.rb b/ci/qunit-selenium-runner.rb
index 3a58377d77..05bcab8cdb 100644
--- a/ci/qunit-selenium-runner.rb
+++ b/ci/qunit-selenium-runner.rb
@@ -6,6 +6,7 @@ require "chromedriver/helper"
driver_options = Selenium::WebDriver::Chrome::Options.new
driver_options.add_argument("--headless")
driver_options.add_argument("--disable-gpu")
+driver_options.add_argument("--no-sandbox")
driver = ::Selenium::WebDriver.for(:chrome, options: driver_options)
result = QUnit::Selenium::TestRunner.new(driver).open(ARGV[0], timeout: 60)
diff --git a/ci/travis.rb b/ci/travis.rb
index b124358789..861063afa5 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -135,7 +135,7 @@ class Build
if activesupport? && !isolated?
# There is a known issue with the listen tests that causes files to be
# incorrectly GC'ed even when they are still in-use. The current solution
- # is to only run them in isolation to avoid randomly failing our test suite.
+ # is to only run them in isolation to avoid random failures of our test suite.
{ "LISTEN" => "0" }
else
{}
@@ -159,7 +159,7 @@ results = {}
ENV["GEM"].split(",").each do |gem|
[false, true].each do |isolated|
next if ENV["TRAVIS_PULL_REQUEST"] && ENV["TRAVIS_PULL_REQUEST"] != "false" && isolated
- next if RUBY_VERSION < "2.4" && isolated
+ next if RUBY_VERSION < "2.5" && isolated
next if gem == "railties" && isolated
next if gem == "ac" && isolated
next if gem == "ac:integration" && isolated
diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md
index d8b122d264..64e8b51817 100644
--- a/guides/CHANGELOG.md
+++ b/guides/CHANGELOG.md
@@ -1 +1,3 @@
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/guides/CHANGELOG.md) for previous changes.
+
+
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/guides/CHANGELOG.md) for previous changes.
diff --git a/guides/assets/images/belongs_to.png b/guides/assets/images/belongs_to.png
index 1a9926e578..2b8c1d52ea 100644
--- a/guides/assets/images/belongs_to.png
+++ b/guides/assets/images/belongs_to.png
Binary files differ
diff --git a/guides/assets/images/habtm.png b/guides/assets/images/habtm.png
index 41013b743d..7e508cc1a6 100644
--- a/guides/assets/images/habtm.png
+++ b/guides/assets/images/habtm.png
Binary files differ
diff --git a/guides/assets/images/has_many.png b/guides/assets/images/has_many.png
index 0d67bea38b..36ccf9f0f6 100644
--- a/guides/assets/images/has_many.png
+++ b/guides/assets/images/has_many.png
Binary files differ
diff --git a/guides/assets/images/has_many_through.png b/guides/assets/images/has_many_through.png
index b4da60e1fb..9e9caabd73 100644
--- a/guides/assets/images/has_many_through.png
+++ b/guides/assets/images/has_many_through.png
Binary files differ
diff --git a/guides/assets/images/has_one.png b/guides/assets/images/has_one.png
index c70763856a..c29c6b9c59 100644
--- a/guides/assets/images/has_one.png
+++ b/guides/assets/images/has_one.png
Binary files differ
diff --git a/guides/assets/images/has_one_through.png b/guides/assets/images/has_one_through.png
index 888a02b775..fdf13286c4 100644
--- a/guides/assets/images/has_one_through.png
+++ b/guides/assets/images/has_one_through.png
Binary files differ
diff --git a/guides/assets/images/polymorphic.png b/guides/assets/images/polymorphic.png
index e0a7f6d64a..d630db9e01 100644
--- a/guides/assets/images/polymorphic.png
+++ b/guides/assets/images/polymorphic.png
Binary files differ
diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb
index 932d329943..ffd81c0079 100644
--- a/guides/bug_report_templates/action_controller_master.rb
+++ b/guides/bug_report_templates/action_controller_master.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-gem "bundler", "< 1.16"
-
begin
require "bundler/inline"
rescue LoadError => e
@@ -15,7 +13,6 @@ gemfile(true) do
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails"
- gem "arel", github: "rails/arel"
end
require "action_controller/railtie"
diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb
index 36d9137b71..4bcee07607 100644
--- a/guides/bug_report_templates/active_job_master.rb
+++ b/guides/bug_report_templates/active_job_master.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-gem "bundler", "< 1.16"
-
begin
require "bundler/inline"
rescue LoadError => e
@@ -15,7 +13,6 @@ gemfile(true) do
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails"
- gem "arel", github: "rails/arel"
end
require "active_job"
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index b66deb36f3..914f04f51a 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-gem "bundler", "< 1.16"
-
begin
require "bundler/inline"
rescue LoadError => e
@@ -15,7 +13,6 @@ gemfile(true) do
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails"
- gem "arel", github: "rails/arel"
gem "sqlite3"
end
diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb
index 737ce66d7b..194b093ac3 100644
--- a/guides/bug_report_templates/active_record_migrations_master.rb
+++ b/guides/bug_report_templates/active_record_migrations_master.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-gem "bundler", "< 1.16"
-
begin
require "bundler/inline"
rescue LoadError => e
@@ -15,7 +13,6 @@ gemfile(true) do
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails"
- gem "arel", github: "rails/arel"
gem "sqlite3"
end
diff --git a/guides/bug_report_templates/benchmark.rb b/guides/bug_report_templates/benchmark.rb
index f5c88086a9..046572148b 100644
--- a/guides/bug_report_templates/benchmark.rb
+++ b/guides/bug_report_templates/benchmark.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-gem "bundler", "< 1.16"
-
begin
require "bundler/inline"
rescue LoadError => e
@@ -15,7 +13,6 @@ gemfile(true) do
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails"
- gem "arel", github: "rails/arel"
gem "benchmark-ips"
end
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
index 240571ba9a..727f428960 100644
--- a/guides/bug_report_templates/generic_master.rb
+++ b/guides/bug_report_templates/generic_master.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
-gem "bundler", "< 1.16"
-
begin
require "bundler/inline"
rescue LoadError => e
@@ -15,7 +13,6 @@ gemfile(true) do
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails", github: "rails/rails"
- gem "arel", github: "rails/arel"
end
require "active_support"
diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb
index 1f2fe91ea1..78820a7856 100644
--- a/guides/rails_guides/markdown/renderer.rb
+++ b/guides/rails_guides/markdown/renderer.rb
@@ -75,7 +75,7 @@ HTML
#
# It is important that we do not eat more than one newline
# because formatting may be wrong otherwise. For example,
- # if a bulleted list follows the first item is not rendered
+ # if a bulleted list follows, the first item is not rendered
# as a list item, but as a paragraph starting with a plain
# asterisk.
body.gsub(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)(\n(?=\n)|\Z)/m) do
diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md
index ac5833e069..afe0550a17 100644
--- a/guides/source/2_2_release_notes.md
+++ b/guides/source/2_2_release_notes.md
@@ -125,7 +125,7 @@ There are two big additions to talk about here: transactional migrations and poo
Historically, multiple-step Rails migrations have been a source of trouble. If something went wrong during a migration, everything before the error changed the database and everything after the error wasn't applied. Also, the migration version was stored as having been executed, which means that it couldn't be simply rerun by `rake db:migrate:redo` after you fix the problem. Transactional migrations change this by wrapping migration steps in a DDL transaction, so that if any of them fail, the entire migration is undone. In Rails 2.2, transactional migrations are supported on PostgreSQL out of the box. The code is extensible to other database types in the future - and IBM has already extended it to support the DB2 adapter.
-* Lead Contributor: [Adam Wiggins](http://adam.heroku.com/)
+* Lead Contributor: [Adam Wiggins](http://about.adamwiggins.com/)
* More information:
* [DDL Transactions](http://adam.heroku.com/past/2008/9/3/ddl_transactions/)
* [A major milestone for DB2 on Rails](http://db2onrails.com/2008/11/08/a-major-milestone-for-db2-on-rails/)
@@ -391,7 +391,7 @@ You can unpack or install a single gem by specifying `GEM=_gem_name_` on the com
* Lead Contributor: [Matt Jones](https://github.com/al2o3cr)
* More information:
* [What's New in Edge Rails: Gem Dependencies](http://archives.ryandaigle.com/articles/2008/4/1/what-s-new-in-edge-rails-gem-dependencies)
- * [Rails 2.1.2 and 2.2RC1: Update Your RubyGems](http://afreshcup.com/2008/10/25/rails-212-and-22rc1-update-your-rubygems/)
+ * [Rails 2.1.2 and 2.2RC1: Update Your RubyGems](https://afreshcup.com/home/2008/10/25/rails-212-and-22rc1-update-your-rubygems)
* [Detailed discussion on Lighthouse](http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/1128)
### Other Railties Changes
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index 1020f4a8e7..634569fa2d 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -234,7 +234,7 @@ Rails chooses between file, template, and action depending on whether there is a
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/)
+ * [The Death of Application.rb](https://afreshcup.com/home/2008/11/17/rails-2x-the-death-of-applicationrb)
* [What's New in Edge Rails: Application.rb Duality is no More](http://archives.ryandaigle.com/articles/2008/11/19/what-s-new-in-edge-rails-application-rb-duality-is-no-more)
### HTTP Digest Authentication Support
@@ -468,7 +468,7 @@ options_from_collection_for_select(@product.sizes, :name, :id, :disabled => lamb
```
* Lead Contributor: [Tekin Suleyman](http://tekin.co.uk/)
-* More Information: [New in rails 2.3 - disabled option tags and lambdas for selecting and disabling options from collections](http://tekin.co.uk/2009/03/new-in-rails-23-disabled-option-tags-and-lambdas-for-selecting-and-disabling-options-from-collections/)
+* More Information: [New in rails 2.3 - disabled option tags and lambdas for selecting and disabling options from collections](https://tekin.co.uk/2009/03/new-in-rails-23-disabled-option-tags-and-lambdas-for-selecting-and-disabling-options-from-collections)
### A Note About Template Loading
@@ -533,7 +533,7 @@ If you look up the spec on the "json.org" site, you'll discover that all keys in
### Other Active Support Changes
* You can use `Enumerable#none?` to check that none of the elements match the supplied block.
-* If you're using Active Support [delegates](http://afreshcup.com/2008/10/19/coming-in-rails-22-delegate-prefixes/) the new `:allow_nil` option lets you return `nil` instead of raising an exception when the target object is nil.
+* If you're using Active Support [delegates](https://afreshcup.com/home/2008/10/19/coming-in-rails-22-delegate-prefixes) the new `:allow_nil` option lets you return `nil` instead of raising an exception when the target object is nil.
* `ActiveSupport::OrderedHash`: now implements `each_key` and `each_value`.
* `ActiveSupport::MessageEncryptor` provides a simple way to encrypt information for storage in an untrusted location (like cookies).
* Active Support's `from_xml` no longer depends on XmlSimple. Instead, Rails now includes its own XmlMini implementation, with just the functionality that it requires. This lets Rails dispense with the bundled copy of XmlSimple that it's been carting around.
@@ -592,7 +592,7 @@ The internals of the various <code>rake gem</code> tasks have been substantially
* Internal Rails testing has been switched from `Test::Unit::TestCase` to `ActiveSupport::TestCase`, and the Rails core requires Mocha to test.
* The default `environment.rb` file has been decluttered.
* The dbconsole script now lets you use an all-numeric password without crashing.
-* `Rails.root` now returns a `Pathname` object, which means you can use it directly with the `join` method to [clean up existing code](http://afreshcup.com/2008/12/05/a-little-rails_root-tidiness/) that uses `File.join`.
+* `Rails.root` now returns a `Pathname` object, which means you can use it directly with the `join` method to [clean up existing code](https://afreshcup.wordpress.com/2008/12/05/a-little-rails_root-tidiness/) that uses `File.join`.
* Various files in /public that deal with CGI and FCGI dispatching are no longer generated in every Rails application by default (you can still get them if you need them by adding `--with-dispatchers` when you run the `rails` command, or add them later with `rake rails:update:generate_dispatchers`).
* Rails Guides have been converted from AsciiDoc to Textile markup.
* Scaffolded views and controllers have been cleaned up a bit.
@@ -605,7 +605,7 @@ Deprecated
A few pieces of older code are deprecated in this release:
-* If you're one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you'll need to know that those scripts are no longer included in core Rails. If you need them, you'll be able to pick up copies via the [irs_process_scripts](https://github.com/rails/irs_process_scripts/tree) plugin.
+* If you're one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you'll need to know that those scripts are no longer included in core Rails. If you need them, you'll be able to pick up copies via the [irs_process_scripts](https://github.com/rails/irs_process_scripts) plugin.
* `render_component` goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the [render_component plugin](https://github.com/rails/render_component/tree/master).
* Support for Rails components has been removed.
* If you were one of the people who got used to running `script/performance/request` to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There's a new request_profiler plugin that you can install to get the exact same functionality back.
diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md
index f0e2cb3b63..7ffa7d4a5c 100644
--- a/guides/source/3_0_release_notes.md
+++ b/guides/source/3_0_release_notes.md
@@ -213,7 +213,7 @@ Railties now deprecates:
More information:
* [Discovering Rails 3 generators](http://blog.plataformatec.com.br/2010/01/discovering-rails-3-generators)
-* [The Rails Module (in Rails 3)](http://litanyagainstfear.com/blog/2010/02/03/the-rails-module/)
+* [The Rails Module (in Rails 3)](http://quaran.to/blog/2010/02/03/the-rails-module/)
Action Pack
-----------
diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md
index f6571544f9..ae6eb27f35 100644
--- a/guides/source/3_2_release_notes.md
+++ b/guides/source/3_2_release_notes.md
@@ -36,7 +36,7 @@ TIP: Note that Ruby 1.8.7 p248 and p249 have marshalling bugs that crash Rails.
* `coffee-rails ~> 3.2.1`
* `uglifier >= 1.0.3`
-* Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. You can start replacing these plugins by extracting them as gems and adding them in your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
+* Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. You can start replacing these plugins by extracting them as gems and adding them in your `Gemfile`. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
* There are a couple of new configuration changes you'd want to add in `config/environments/development.rb`:
@@ -156,7 +156,7 @@ Railties
will create indexes for `title` and `author` with the latter being a unique index. Some types such as decimal accept custom options. In the example, `price` will be a decimal column with precision and scale set to 7 and 2 respectively.
-* Turn gem has been removed from default Gemfile.
+* Turn gem has been removed from default `Gemfile`.
* Remove old plugin generator `rails generate plugin` in favor of `rails plugin new` command.
diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md
index 6bf65757ec..2c5e665e33 100644
--- a/guides/source/4_1_release_notes.md
+++ b/guides/source/4_1_release_notes.md
@@ -274,7 +274,7 @@ for detailed changes.
* The [Spring application
preloader](https://github.com/rails/spring) is now installed
by default for new applications. It uses the development group of
- the Gemfile, so will not be installed in
+ the `Gemfile`, so will not be installed in
production. ([Pull Request](https://github.com/rails/rails/pull/12958))
* `BACKTRACE` environment variable to show unfiltered backtraces for test
diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md
index 036a310ac8..7105df5634 100644
--- a/guides/source/4_2_release_notes.md
+++ b/guides/source/4_2_release_notes.md
@@ -368,7 +368,7 @@ Please refer to the [Changelog][railties] for detailed changes.
### Notable changes
-* Introduced `web-console` in the default application Gemfile.
+* Introduced `web-console` in the default application `Gemfile`.
([Pull Request](https://github.com/rails/rails/pull/11667))
* Added a `required` option to the model generator for associations.
diff --git a/guides/source/5_1_release_notes.md b/guides/source/5_1_release_notes.md
index 6b9a950a42..852d04b1f6 100644
--- a/guides/source/5_1_release_notes.md
+++ b/guides/source/5_1_release_notes.md
@@ -355,6 +355,9 @@ Please refer to the [Changelog][action-pack] for detailed changes.
([Commit](https://github.com/rails/rails/commit/79a5ea9eadb4d43b62afacedc0706cbe88c54496),
[Commit](https://github.com/rails/rails/commit/57e1c99a280bdc1b324936a690350320a1cd8111))
+* Removed deprecated support for calling `HashWithIndifferentAccess` methods on `ActionController::Parameters`.
+ ([Commit](https://github.com/rails/rails/pull/26746/commits/7093ceb480ad6a0a91b511832dad4c6a86981b93))
+
### Deprecations
* Deprecated `config.action_controller.raise_on_unfiltered_parameters`.
diff --git a/guides/source/5_2_release_notes.md b/guides/source/5_2_release_notes.md
new file mode 100644
index 0000000000..7b5c4b87e3
--- /dev/null
+++ b/guides/source/5_2_release_notes.md
@@ -0,0 +1,226 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
+Ruby on Rails 5.2 Release Notes
+===============================
+
+Highlights in Rails 5.2:
+
+* Active Storage
+* Redis Cache Store
+* HTTP/2 Early hints support
+* Credentials
+* Default Content Security Policy
+
+These release notes cover only the major changes. To learn about various bug
+fixes and changes, please refer to the change logs or check out the [list of
+commits](https://github.com/rails/rails/commits/5-2-stable) in the main Rails
+repository on GitHub.
+
+--------------------------------------------------------------------------------
+
+Upgrading to Rails 5.2
+----------------------
+
+If you're upgrading an existing application, it's a great idea to have good test
+coverage before going in. You should also first upgrade to Rails 5.1 in case you
+haven't and make sure your application still runs as expected before attempting
+an update to Rails 5.2.
+
+
+Major Features
+--------------
+
+### Active Storage
+
+[README](https://github.com/rails/rails/blob/d3893ec38ec61282c2598b01a298124356d6b35a/activestorage/README.md)
+
+### Redis Cache Store
+
+[Pull Request](https://github.com/rails/rails/pull/31134)
+
+
+### HTTP/2 Early hints support
+
+[Pull Request](https://github.com/rails/rails/pull/30744)
+
+
+### Credentials
+
+[Pull Request](https://github.com/rails/rails/pull/30067)
+
+
+### Default Content Security Policy
+
+[Pull Request](https://github.com/rails/rails/pull/31162)
+
+Incompatibilities
+-----------------
+
+ToDo
+
+Railties
+--------
+
+Please refer to the [Changelog][railties] for detailed changes.
+
+### Deprecations
+
+* Deprecate `capify!` method in generators and templates.
+ ([Pull Request](https://github.com/rails/rails/pull/29493))
+
+* Deprecated passing the environment's name as a regular argument to the
+ `rails dbconsole` and `rails console` commands.
+ ([Pull Request](https://github.com/rails/rails/pull/29358))
+
+* Deprecated using subclass of `Rails::Application` to start the Rails server.
+ ([Pull Request](https://github.com/rails/rails/pull/30127))
+
+* Deprecated `after_bundle` callback in Rails plugin templates.
+ ([Pull Request](https://github.com/rails/rails/pull/29446))
+
+### Notable changes
+
+ToDo
+
+Action Cable
+-----------
+
+Please refer to the [Changelog][action-cable] for detailed changes.
+
+### Removals
+
+* Removed deprecated evented redis adapter.
+ ([Commit](https://github.com/rails/rails/commit/48766e32d31))
+
+### Notable changes
+
+* Added support for `host`, `port`, `db` and `password` options in cable.yml
+ ([Pull Request](https://github.com/rails/rails/pull/29528))
+
+* Added support for compatibility with redis-rb gem for 4.0 version.
+ ([Pull Request](https://github.com/rails/rails/pull/30748))
+
+Action Pack
+-----------
+
+Please refer to the [Changelog][action-pack] for detailed changes.
+
+### Removals
+
+* Removed deprecated `ActionController::ParamsParser::ParseError`.
+ ([Commit](https://github.com/rails/rails/commit/e16c765ac6d))
+
+### Deprecations
+
+* Deprecated `#success?`, `#missing?` and `#error?` aliases of
+ `ActionDispatch::TestResponse`.
+ ([Pull Request](https://github.com/rails/rails/pull/30104))
+
+### Notable changes
+
+ToDo
+
+Action View
+-------------
+
+Please refer to the [Changelog][action-view] for detailed changes.
+
+### Removals
+
+* Removed deprecated Erubis ERB handler.
+ ([Commit](https://github.com/rails/rails/commit/7de7f12fd14))
+
+### Deprecations
+
+* Deprecated `image_alt` helper which used to add default alt text to
+ the images generated by `image_tag`.
+ ([Pull Request](https://github.com/rails/rails/pull/30213))
+
+### Notable changes
+
+ToDo
+
+Action Mailer
+-------------
+
+Please refer to the [Changelog][action-mailer] for detailed changes.
+
+### Notable changes
+
+ToDo
+
+Active Record
+-------------
+
+Please refer to the [Changelog][active-record] for detailed changes.
+
+ToDo
+
+### Deprecations
+
+ToDo
+
+### Notable changes
+
+ToDo
+
+Active Model
+------------
+
+Please refer to the [Changelog][active-model] for detailed changes.
+
+### Removals
+
+ToDo
+
+### Notable changes
+
+ToDo
+
+Active Support
+--------------
+
+Please refer to the [Changelog][active-support] for detailed changes.
+
+### Removals
+
+ToDo
+
+### Deprecations
+
+ToDo
+
+### Notable changes
+
+ToDo
+
+Active Job
+-----------
+
+Please refer to the [Changelog][active-job] for detailed changes.
+
+### Removals
+
+ToDo
+
+### Notable changes
+
+ToDo
+
+Credits
+-------
+
+See the
+[full list of contributors to Rails](http://contributors.rubyonrails.org/) for
+the many people who spent many hours making Rails, the stable and robust
+framework it is. Kudos to all of them.
+
+[railties]: https://github.com/rails/rails/blob/5-2-stable/railties/CHANGELOG.md
+[action-pack]: https://github.com/rails/rails/blob/5-2-stable/actionpack/CHANGELOG.md
+[action-view]: https://github.com/rails/rails/blob/5-2-stable/actionview/CHANGELOG.md
+[action-mailer]: https://github.com/rails/rails/blob/5-2-stable/actionmailer/CHANGELOG.md
+[action-cable]: https://github.com/rails/rails/blob/5-2-stable/actioncable/CHANGELOG.md
+[active-record]: https://github.com/rails/rails/blob/5-2-stable/activerecord/CHANGELOG.md
+[active-model]: https://github.com/rails/rails/blob/5-2-stable/activemodel/CHANGELOG.md
+[active-support]: https://github.com/rails/rails/blob/5-2-stable/activesupport/CHANGELOG.md
+[active-job]: https://github.com/rails/rails/blob/5-2-stable/activejob/CHANGELOG.md
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index 57403a4bf9..c250db2e0c 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -1,12 +1,14 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
Action Cable Overview
=====================
-In this guide you will learn how Action Cable works and how to use WebSockets to
+In this guide, you will learn how Action Cable works and how to use WebSockets to
incorporate real-time features into your Rails application.
After reading this guide, you will know:
-* What Action Cable is and its integration on backend and frontend
+* What Action Cable is and its integration backend and frontend
* How to setup Action Cable
* How to setup channels
* Deployment and Architecture setup for running Action Cable
@@ -129,7 +131,7 @@ subscriptions based on an identifier sent by the cable consumer.
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
# Called when the consumer has successfully
- # become a subscriber of this channel.
+ # become a subscriber to this channel.
def subscribed
end
end
@@ -225,7 +227,7 @@ A *broadcasting* is a pub/sub link where anything transmitted by a publisher
is routed directly to the channel subscribers who are streaming that named
broadcasting. Each channel can be streaming zero or more broadcastings.
-Broadcastings are purely an online queue and time dependent. If a consumer is
+Broadcastings are purely an online queue and time-dependent. If a consumer is
not streaming (subscribed to a given channel), they'll not get the broadcast
should they connect later.
@@ -515,8 +517,8 @@ user. For a user with an ID of 1, the broadcasting name would be
The channel has been instructed to stream everything that arrives at
`web_notifications:1` directly to the client by invoking the `received`
callback. The data passed as argument is the hash sent as the second parameter
-to the server-side broadcast call, JSON encoded for the trip across the wire,
-and unpacked for the data argument arriving to `received`.
+to the server-side broadcast call, JSON encoded for the trip across the wire
+and unpacked for the data argument arriving as `received`.
### More Complete Examples
@@ -569,7 +571,7 @@ This may change in the future. [#27214](https://github.com/rails/rails/issues/27
Action Cable will only accept requests from specified origins, which are
passed to the server config as an array. The origins can be instances of
-strings or regular expressions, against which a check for match will be performed.
+strings or regular expressions, against which a check for the match will be performed.
```ruby
config.action_cable.allowed_request_origins = ['http://rubyonrails.com', %r{http://ruby.*}]
@@ -592,7 +594,7 @@ environment configuration files.
### Other Configurations
-The other common option to configure, is the log tags applied to the
+The other common option to configure is the log tags applied to the
per-connection logger. Here's an example that uses
the user account id if available, else "no-account" while tagging:
@@ -607,7 +609,7 @@ config.action_cable.log_tags = [
For a full list of all configuration options, see the
`ActionCable::Server::Configuration` class.
-Also note that your server must provide at least the same number of database
+Also, note that your server must provide at least the same number of database
connections as you have workers. The default worker pool size is set to 4, so
that means you have to make at least that available. You can change that in
`config/database.yml` through the `pool` attribute.
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 28f7246197..6ecfb57db3 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -21,7 +21,7 @@ After reading this guide, you will know:
What Does a Controller Do?
--------------------------
-Action Controller is the C in MVC. After the router has determined which controller to use for a request, the controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible.
+Action Controller is the C in [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller). After the router has determined which controller to use for a request, the controller is responsible for making sense of the request, and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible.
For most conventional [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that's not a problem, this is just the most common way for a controller to work.
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index cb07781d1c..fe31e3403f 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -805,7 +805,7 @@ config.action_mailer.smtp_settings = {
user_name: '<username>',
password: '<password>',
authentication: 'plain',
- enable_starttls_auto: true }
+ enable_starttls_auto: true }
```
Note: As of July 15, 2014, Google increased [its security measures](https://support.google.com/accounts/answer/6010255) and now blocks attempts from apps it deems less secure.
You can change your Gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts. If your Gmail account has 2-factor authentication enabled,
diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md
index c1e02745de..1cba5c6fb6 100644
--- a/guides/source/action_view_overview.md
+++ b/guides/source/action_view_overview.md
@@ -149,10 +149,10 @@ end
#### Jbuilder
[Jbuilder](https://github.com/rails/jbuilder) is a gem that's
-maintained by the Rails team and included in the default Rails Gemfile.
+maintained by the Rails team and included in the default Rails `Gemfile`.
It's similar to Builder, but is used to generate JSON, instead of XML.
-If you don't have it, you can add the following to your Gemfile:
+If you don't have it, you can add the following to your `Gemfile`:
```ruby
gem 'jbuilder'
@@ -807,20 +807,22 @@ The core method of this helper, `form_for`, gives you the ability to create a fo
The HTML generated for this would be:
```html
-<form action="/people/create" method="post">
- <input id="person_first_name" name="person[first_name]" type="text" />
- <input id="person_last_name" name="person[last_name]" type="text" />
- <input name="commit" type="submit" value="Create" />
+<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="lTuvBzs7ANygT0NFinXj98tfw3Emfm65wwYLbUvoWsK2pngccIQSUorM2C035M9dZswXgWTvKwFS8W5TVblpYw==" />
+ <input type="text" name="person[first_name]" id="person_first_name" />
+ <input type="text" name="person[last_name]" id="person_last_name" />
+ <input type="submit" name="commit" value="Create" data-disable-with="Create" />
</form>
```
The params object created when this form is submitted would look like:
```ruby
-{ "action" => "create", "controller" => "people", "person" => { "first_name" => "William", "last_name" => "Smith" } }
+{"utf8" => "✓", "authenticity_token" => "lTuvBzs7ANygT0NFinXj98tfw3Emfm65wwYLbUvoWsK2pngccIQSUorM2C035M9dZswXgWTvKwFS8W5TVblpYw==", "person" => {"first_name" => "William", "last_name" => "Smith"}, "commit" => "Create", "controller" => "people", "action" => "create"}
```
-The params hash has a nested person value, which can therefore be accessed with params[:person] in the controller.
+The params hash has a nested person value, which can therefore be accessed with `params[:person]` in the controller.
#### check_box
diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md
index 069a624984..2f85b765a3 100644
--- a/guides/source/active_record_basics.md
+++ b/guides/source/active_record_basics.md
@@ -38,13 +38,15 @@ object on how to write to and read from the database.
### Object Relational Mapping
-Object Relational Mapping, commonly referred to as its abbreviation ORM, is
+[Object Relational Mapping](https://en.wikipedia.org/wiki/Object-relational_mapping), commonly referred to as its abbreviation ORM, is
a technique that connects the rich objects of an application to tables in
a relational database management system. Using ORM, the properties and
relationships of the objects in an application can be easily stored and
retrieved from a database without writing SQL statements directly and with less
overall database access code.
+NOTE: If you are not familiar enough with relational database management systems (RDBMS) or structured query language (SQL), please go through [this tutorial](https://www.w3schools.com/sql/default.asp) (or [this one](http://www.sqlcourse.com/)) or study them by other means. Understanding how relational databases work is crucial to understanding Active Records and Rails in general.
+
### Active Record as an ORM Framework
Active Record gives us several mechanisms, the most important being the ability
@@ -142,7 +144,7 @@ end
This will create a `Product` model, mapped to a `products` table at the
database. By doing this you'll also have the ability to map the columns of each
row in that table with the attributes of the instances of your model. Suppose
-that the `products` table was created using an SQL statement like:
+that the `products` table was created using an SQL (or one of its extensions) statement like:
```sql
CREATE TABLE products (
@@ -152,8 +154,9 @@ CREATE TABLE products (
);
```
-Following the table schema above, you would be able to write code like the
-following:
+Schema above declares a table with two columns: `id` and `name`. Each row of
+this table represents a certain product with these two parameters. Thus, you
+would be able to write code like the following:
```ruby
p = Product.new
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 53417f012e..630dafe632 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -213,6 +213,7 @@ The following methods trigger callbacks:
* `save!`
* `save(validate: false)`
* `toggle!`
+* `touch`
* `update_attribute`
* `update`
* `update!`
@@ -245,7 +246,6 @@ Just as with validations, it is also possible to skip callbacks by using the fol
* `increment`
* `increment_counter`
* `toggle`
-* `touch`
* `update_column`
* `update_columns`
* `update_all`
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index f8f36bf600..ab3af438f5 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -353,8 +353,7 @@ create_table :products, options: "ENGINE=BLACKHOLE" do |t|
end
```
-will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table
-(when using MySQL or MariaDB, the default is `ENGINE=InnoDB`).
+will append `ENGINE=BLACKHOLE` to the SQL statement used to create the table.
Also you can pass the `:comment` option with any description for the table
that will be stored in database itself and can be viewed with database administration
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 3786343fc3..4e28e31a53 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -801,7 +801,7 @@ The SQL that would be executed:
SELECT * FROM articles WHERE id > 10 ORDER BY id DESC
# Original query without `only`
-SELECT "articles".* FROM "articles" WHERE (id > 10) ORDER BY id desc LIMIT 20
+SELECT * FROM articles WHERE id > 10 ORDER BY id DESC LIMIT 20
```
@@ -820,14 +820,14 @@ Article.find(10).comments.reorder('name')
The SQL that would be executed:
```sql
-SELECT * FROM articles WHERE id = 10
+SELECT * FROM articles WHERE id = 10 LIMIT 1
SELECT * FROM comments WHERE article_id = 10 ORDER BY name
```
In the case where the `reorder` clause is not used, the SQL executed would be:
```sql
-SELECT * FROM articles WHERE id = 10
+SELECT * FROM articles WHERE id = 10 LIMIT 1
SELECT * FROM comments WHERE article_id = 10 ORDER BY posted_at DESC
```
@@ -1091,7 +1091,7 @@ This produces:
```sql
SELECT articles.* FROM articles
- INNER JOIN categories ON articles.category_id = categories.id
+ INNER JOIN categories ON categories.id = articles.category_id
INNER JOIN comments ON comments.article_id = articles.id
```
@@ -1871,14 +1871,14 @@ All calculation methods work directly on a model:
```ruby
Client.count
-# SELECT count(*) AS count_all FROM clients
+# SELECT COUNT(*) FROM clients
```
Or on a relation:
```ruby
Client.where(first_name: 'Ryan').count
-# SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan')
+# SELECT COUNT(*) FROM clients WHERE (first_name = 'Ryan')
```
You can also use various finder methods on a relation for performing complex calculations:
@@ -1890,9 +1890,9 @@ Client.includes("orders").where(first_name: 'Ryan', orders: { status: 'received'
Which will execute:
```sql
-SELECT count(DISTINCT clients.id) AS count_all FROM clients
- LEFT OUTER JOIN orders ON orders.client_id = clients.id WHERE
- (clients.first_name = 'Ryan' AND orders.status = 'received')
+SELECT COUNT(DISTINCT clients.id) FROM clients
+ LEFT OUTER JOIN orders ON orders.client_id = clients.id
+ WHERE (clients.first_name = 'Ryan' AND orders.status = 'received')
```
### Count
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index e9157f3db1..d076efcd54 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -953,7 +953,7 @@ should happen, an `Array` can be used. Moreover, you can apply both `:if` and
```ruby
class Computer < ApplicationRecord
validates :mouse, presence: true,
- if: [Proc.new { |c| c.market.retail? }, :desktop?],
+ if: [Proc.new { |c| c.market.retail? }, :desktop?],
unless: Proc.new { |c| c.trackpad.present? }
end
```
diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md
new file mode 100644
index 0000000000..97c56dfd93
--- /dev/null
+++ b/guides/source/active_storage_overview.md
@@ -0,0 +1,565 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
+Active Storage Overview
+=======================
+
+This guide covers how to attach files to your Active Record models.
+
+After reading this guide, you will know:
+
+* How to attach one or many files to a record.
+* How to delete an attached file.
+* How to link to an attached file.
+* How to use variants to transform images.
+* How to generate an image representation of a non-image file, such as a PDF or a video.
+* How to send file uploads directly from browsers to a storage service,
+ bypassing your application servers.
+* How to clean up files stored during testing.
+* How to implement support for additional storage services.
+
+--------------------------------------------------------------------------------
+
+What is Active Storage?
+-----------------------
+
+Active Storage facilitates uploading files to a cloud storage service like
+Amazon S3, Google Cloud Storage, or Microsoft Azure Storage and attaching those
+files to Active Record objects. It comes with a local disk-based service for
+development and testing and supports mirroring files to subordinate services for
+backups and migrations.
+
+Using Active Storage, an application can transform image uploads with
+[ImageMagick](https://www.imagemagick.org), generate image representations of
+non-image uploads like PDFs and videos, and extract metadata from arbitrary
+files.
+
+## Setup
+
+Active Storage uses two tables in your application’s database named
+`active_storage_blobs` and `active_storage_attachments`. After upgrading your
+application to Rails 5.2, run `rails active_storage:install` to generate a
+migration that creates these tables. Use `rails db:migrate` to run the
+migration.
+
+Declare Active Storage services in `config/storage.yml`. For each service your
+application uses, provide a name and the requisite configuration. The example
+below declares three services named `local`, `test`, and `amazon`:
+
+```yaml
+local:
+ service: Disk
+ root: <%= Rails.root.join("storage") %>
+
+test:
+ service: Disk
+ root: <%= Rails.root.join("tmp/storage") %>
+
+amazon:
+ service: S3
+ access_key_id: ""
+ secret_access_key: ""
+```
+
+Tell Active Storage which service to use by setting
+`Rails.application.config.active_storage.service`. Because each environment will
+likely use a different service, it is recommended to do this on a
+per-environment basis. To use the disk service from the previous example in the
+development environment, you would add the following to
+`config/environments/development.rb`:
+
+```ruby
+# Store files locally.
+config.active_storage.service = :local
+```
+
+To use the Amazon S3 service in production, you add the following to
+`config/environments/production.rb`:
+
+```ruby
+# Store files on Amazon S3.
+config.active_storage.service = :amazon
+```
+
+Continue reading for more information on the built-in service adapters (e.g.
+`Disk` and `S3`) and the configuration they require.
+
+### Disk Service
+
+Declare a Disk service in `config/storage.yml`:
+
+```yaml
+local:
+ service: Disk
+ root: <%= Rails.root.join("storage") %>
+```
+
+Optionally specify a host for generating URLs (the default is `http://localhost:3000`):
+
+```yaml
+local:
+ service: Disk
+ root: <%= Rails.root.join("storage") %>
+ host: http://myapp.test
+```
+
+### Amazon S3 Service
+
+Declare an S3 service in `config/storage.yml`:
+
+```yaml
+amazon:
+ service: S3
+ access_key_id: ""
+ secret_access_key: ""
+ region: ""
+ bucket: ""
+```
+
+Add the [`aws-sdk-s3`](https://github.com/aws/aws-sdk-ruby) gem to your `Gemfile`:
+
+```ruby
+gem "aws-sdk-s3", require: false
+```
+
+### Microsoft Azure Storage Service
+
+Declare an Azure Storage service in `config/storage.yml`:
+
+```yaml
+azure:
+ service: AzureStorage
+ path: ""
+ storage_account_name: ""
+ storage_access_key: ""
+ container: ""
+```
+
+Add the [`azure-storage`](https://github.com/Azure/azure-storage-ruby) gem to your `Gemfile`:
+
+```ruby
+gem "azure-storage", require: false
+```
+
+### Google Cloud Storage Service
+
+Declare a Google Cloud Storage service in `config/storage.yml`:
+
+```yaml
+google:
+ service: GCS
+ credentials: <%= Rails.root.join("path/to/keyfile.json") %>
+ project: ""
+ bucket: ""
+```
+
+Optionally provide a Hash of credentials instead of a keyfile path:
+
+```yaml
+google:
+ service: GCS
+ credentials:
+ type: "service_account"
+ project_id: ""
+ private_key_id: <%= Rails.application.credentials.dig(:gcs, :private_key_id) %>
+ private_key: <%= Rails.application.credentials.dig(:gcs, :private_key) %>
+ client_email: ""
+ client_id: ""
+ auth_uri: "https://accounts.google.com/o/oauth2/auth"
+ token_uri: "https://accounts.google.com/o/oauth2/token"
+ auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs"
+ client_x509_cert_url: ""
+ project: ""
+ bucket: ""
+```
+
+Add the [`google-cloud-storage`](https://github.com/GoogleCloudPlatform/google-cloud-ruby/tree/master/google-cloud-storage) gem to your `Gemfile`:
+
+```ruby
+gem "google-cloud-storage", "~> 1.8", require: false
+```
+
+### Mirror Service
+
+You can keep multiple services in sync by defining a mirror service. When a file
+is uploaded or deleted, it's done across all the mirrored services. Mirrored
+services can be used to facilitate a migration between services in production.
+You can start mirroring to the new service, copy existing files from the old
+service to the new, then go all-in on the new service. Define each of the
+services you'd like to use as described above and reference them from a mirrored
+service.
+
+```yaml
+s3_west_coast:
+ service: S3
+ access_key_id: ""
+ secret_access_key: ""
+ region: ""
+ bucket: ""
+
+s3_east_coast:
+ service: S3
+ access_key_id: ""
+ secret_access_key: ""
+ region: ""
+ bucket: ""
+
+production:
+ service: Mirror
+ primary: s3_east_coast
+ mirrors:
+ - s3_west_coast
+```
+
+NOTE: Files are served from the primary service.
+
+Attaching Files to Records
+--------------------------
+
+### `has_one_attached`
+
+The `has_one_attached` macro sets up a one-to-one mapping between records and
+files. Each record can have one file attached to it.
+
+For example, suppose your application has a `User` model. If you want each user to
+have an avatar, define the `User` model like this:
+
+```ruby
+class User < ApplicationRecord
+ has_one_attached :avatar
+end
+```
+
+You can create a user with an avatar:
+
+```ruby
+class SignupController < ApplicationController
+ def create
+ user = User.create!(user_params)
+ session[:user_id] = user.id
+ redirect_to root_path
+ end
+
+ private
+ def user_params
+ params.require(:user).permit(:email_address, :password, :avatar)
+ end
+end
+```
+
+Call `avatar.attach` to attach an avatar to an existing user:
+
+```ruby
+Current.user.avatar.attach(params[:avatar])
+```
+
+Call `avatar.attached?` to determine whether a particular user has an avatar:
+
+```ruby
+Current.user.avatar.attached?
+```
+
+### `has_many_attached`
+
+The `has_many_attached` macro sets up a one-to-many relationship between records
+and files. Each record can have many files attached to it.
+
+For example, suppose your application has a `Message` model. If you want each
+message to have many images, define the `Message` model like this:
+
+```ruby
+class Message < ApplicationRecord
+ has_many_attached :images
+end
+```
+
+You can create a message with images:
+
+```ruby
+class MessagesController < ApplicationController
+ def create
+ message = Message.create!(message_params)
+ redirect_to message
+ end
+
+ private
+ def message_params
+ params.require(:message).permit(:title, :content, images: [])
+ end
+end
+```
+
+Call `images.attach` to add new images to an existing message:
+
+```ruby
+@message.images.attach(params[:images])
+```
+
+Call `images.attached?` to determine whether a particular message has any images:
+
+```ruby
+@message.images.attached?
+```
+
+Removing Files
+--------------
+
+To remove an attachment from a model, call `purge` on the attachment. Removal
+can be done in the background if your application is setup to use Active Job.
+Purging deletes the blob and the file from the storage service.
+
+```ruby
+# Synchronously destroy the avatar and actual resource files.
+user.avatar.purge
+
+# Destroy the associated models and actual resource files async, via Active Job.
+user.avatar.purge_later
+```
+
+Linking to Files
+----------------
+
+Generate a permanent URL for the blob that points to the application. Upon
+access, a redirect to the actual service endpoint is returned. This indirection
+decouples the public URL from the actual one, and allows, for example, mirroring
+attachments in different services for high-availability. The redirection has an
+HTTP expiration of 5 min.
+
+```ruby
+url_for(user.avatar)
+```
+
+To create a download link, use the `rails_blob_{path|url}` helper. Using this
+helper allows you to set the disposition.
+
+```ruby
+rails_blob_path(user.avatar, disposition: "attachment")
+```
+
+Transforming Images
+-------------------
+
+To create variation of the image, call `variant` on the Blob.
+You can pass any [MiniMagick](https://github.com/minimagick/minimagick)
+supported transformation to the method.
+
+To enable variants, add `mini_magick` to your `Gemfile`:
+
+```ruby
+gem 'mini_magick'
+```
+
+When the browser hits the variant URL, Active Storage will lazy transform the
+original blob into the format you specified and redirect to its new service
+location.
+
+```erb
+<%= image_tag user.avatar.variant(resize: "100x100") %>
+```
+
+Previewing Files
+----------------
+
+Some non-image files can be previewed: that is, they can be presented as images.
+For example, a video file can be previewed by extracting its first frame. Out of
+the box, Active Storage supports previewing videos and PDF documents.
+
+```erb
+<ul>
+ <% @message.files.each do |file| %>
+ <li>
+ <%= image_tag file.preview(resize: "100x100>") %>
+ </li>
+ <% end %>
+</ul>
+```
+
+WARNING: Extracting previews requires third-party applications, `ffmpeg` for
+video and `mutool` for PDFs. 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.
+
+Direct Uploads
+--------------
+
+Active Storage, with its included JavaScript library, supports uploading
+directly from the client to the cloud.
+
+### Direct upload installation
+
+1. Include `activestorage.js` in your application's JavaScript bundle.
+
+ Using the asset pipeline:
+
+ ```js
+ //= require activestorage
+
+ ```
+
+ Using the npm package:
+
+ ```js
+ import * as ActiveStorage from "activestorage"
+ ActiveStorage.start()
+ ```
+
+2. Annotate file inputs with the direct upload URL.
+
+ ```ruby
+ <%= form.file_field :attachments, multiple: true, direct_upload: true %>
+ ```
+3. That's it! Uploads begin upon form submission.
+
+### Direct upload JavaScript events
+
+| Event name | Event target | Event data (`event.detail`) | Description |
+| --- | --- | --- | --- |
+| `direct-uploads:start` | `<form>` | None | A form containing files for direct upload fields was submitted. |
+| `direct-upload:initialize` | `<input>` | `{id, file}` | Dispatched for every file after form submission. |
+| `direct-upload:start` | `<input>` | `{id, file}` | A direct upload is starting. |
+| `direct-upload:before-blob-request` | `<input>` | `{id, file, xhr}` | Before making a request to your application for direct upload metadata. |
+| `direct-upload:before-storage-request` | `<input>` | `{id, file, xhr}` | Before making a request to store a file. |
+| `direct-upload:progress` | `<input>` | `{id, file, progress}` | As requests to store files progress. |
+| `direct-upload:error` | `<input>` | `{id, file, error}` | An error occurred. An `alert` will display unless this event is canceled. |
+| `direct-upload:end` | `<input>` | `{id, file}` | A direct upload has ended. |
+| `direct-uploads:end` | `<form>` | None | All direct uploads have ended. |
+
+### Example
+
+You can use these events to show the progress of an upload.
+
+![direct-uploads](https://user-images.githubusercontent.com/5355/28694528-16e69d0c-72f8-11e7-91a7-c0b8cfc90391.gif)
+
+To show the uploaded files in a form:
+
+```js
+// direct_uploads.js
+
+addEventListener("direct-upload:initialize", event => {
+ const { target, detail } = event
+ const { id, file } = detail
+ target.insertAdjacentHTML("beforebegin", `
+ <div id="direct-upload-${id}" class="direct-upload direct-upload--pending">
+ <div id="direct-upload-progress-${id}" class="direct-upload__progress" style="width: 0%"></div>
+ <span class="direct-upload__filename">${file.name}</span>
+ </div>
+ `)
+})
+
+addEventListener("direct-upload:start", event => {
+ const { id } = event.detail
+ const element = document.getElementById(`direct-upload-${id}`)
+ element.classList.remove("direct-upload--pending")
+})
+
+addEventListener("direct-upload:progress", event => {
+ const { id, progress } = event.detail
+ const progressElement = document.getElementById(`direct-upload-progress-${id}`)
+ progressElement.style.width = `${progress}%`
+})
+
+addEventListener("direct-upload:error", event => {
+ event.preventDefault()
+ const { id, error } = event.detail
+ const element = document.getElementById(`direct-upload-${id}`)
+ element.classList.add("direct-upload--error")
+ element.setAttribute("title", error)
+})
+
+addEventListener("direct-upload:end", event => {
+ const { id } = event.detail
+ const element = document.getElementById(`direct-upload-${id}`)
+ element.classList.add("direct-upload--complete")
+})
+```
+
+Add styles:
+
+```css
+/* direct_uploads.css */
+
+.direct-upload {
+ display: inline-block;
+ position: relative;
+ padding: 2px 4px;
+ margin: 0 3px 3px 0;
+ border: 1px solid rgba(0, 0, 0, 0.3);
+ border-radius: 3px;
+ font-size: 11px;
+ line-height: 13px;
+}
+
+.direct-upload--pending {
+ opacity: 0.6;
+}
+
+.direct-upload__progress {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ opacity: 0.2;
+ background: #0076ff;
+ transition: width 120ms ease-out, opacity 60ms 60ms ease-in;
+ transform: translate3d(0, 0, 0);
+}
+
+.direct-upload--complete .direct-upload__progress {
+ opacity: 0.4;
+}
+
+.direct-upload--error {
+ border-color: red;
+}
+
+input[type=file][data-direct-upload-url][disabled] {
+ display: none;
+}
+```
+
+Discarding Files Stored During System Tests
+-------------------------------------------
+
+System tests clean up test data by rolling back a transaction. Because destroy
+is never called on an object, the attached files are never cleaned up. If you
+want to clear the files, you can do it in an `after_teardown` callback. Doing it
+here ensures that all connections created during the test are complete and
+you won't receive an error from Active Storage saying it can't find a file.
+
+```ruby
+class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
+
+ def remove_uploaded_files
+ FileUtils.rm_rf("#{Rails.root}/storage_test")
+ end
+
+ def after_teardown
+ super
+ remove_uploaded_files
+ end
+end
+```
+
+If your system tests verify the deletion of a model with attachments and you're
+using Active Job, set your test environment to use the inline queue adapter so
+the purge job is executed immediately rather at an unknown time in the future.
+
+You may also want to use a separate service definition for the test environment
+so your tests don't delete the files you create during development.
+
+```ruby
+# Use inline job processing to make things happen immediately
+config.active_job.queue_adapter = :inline
+
+# Separate file storage in the test environment
+config.active_storage.service = :local_test
+```
+
+Implementing Support for Other Cloud Services
+---------------------------------------------
+
+If you need to support a cloud service other than these, you will need to
+implement the Service. Each service extends
+[`ActiveStorage::Service`](https://github.com/rails/rails/blob/master/activestorage/lib/active_storage/service.rb)
+by implementing the methods necessary to upload and download files to the cloud.
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index 66d2fbd939..8e2826bb85 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -1978,19 +1978,19 @@ Extensions to `BigDecimal`
The method `to_s` provides a default specifier of "F". This means that a simple call to `to_s` will result in floating point representation instead of engineering notation:
```ruby
-BigDecimal.new(5.00, 6).to_s # => "5.0"
+BigDecimal(5.00, 6).to_s # => "5.0"
```
and that symbol specifiers are also supported:
```ruby
-BigDecimal.new(5.00, 6).to_s(:db) # => "5.0"
+BigDecimal(5.00, 6).to_s(:db) # => "5.0"
```
Engineering notation is still supported:
```ruby
-BigDecimal.new(5.00, 6).to_s("e") # => "0.5E1"
+BigDecimal(5.00, 6).to_s("e") # => "0.5E1"
```
Extensions to `Enumerable`
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 25f78fd940..11c4a8222a 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -543,6 +543,13 @@ Active Storage
| `:key` | Secure token |
| `:service` | Name of the service |
+### service_delete_prefixed.active_storage
+
+| Key | Value |
+| ------------ | ------------------- |
+| `:prefix` | Key prefix |
+| `:service` | Name of the service |
+
### service_exist.active_storage
| Key | Value |
diff --git a/guides/source/api_app.md b/guides/source/api_app.md
index 43a7de88b0..b4d90d31de 100644
--- a/guides/source/api_app.md
+++ b/guides/source/api_app.md
@@ -1,6 +1,5 @@
**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
-
Using Rails for API-only Applications
=====================================
@@ -414,8 +413,10 @@ Some common modules you might want to add:
- `AbstractController::Translation`: Support for the `l` and `t` localization
and translation methods.
-- `ActionController::HttpAuthentication::Basic` (or `Digest` or `Token`): Support
- for basic, digest or token HTTP authentication.
+- Support for basic, digest or token HTTP authentication:
+ * `ActionController::HttpAuthentication::Basic::ControllerMethods`,
+ * `ActionController::HttpAuthentication::Digest::ControllerMethods`,
+ * `ActionController::HttpAuthentication::Token::ControllerMethods`
- `ActionView::Layouts`: Support for layouts when rendering.
- `ActionController::MimeResponds`: Support for `respond_to`.
- `ActionController::Cookies`: Support for `cookies`, which includes
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 805b0f0d62..618896d458 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -22,8 +22,7 @@ The asset pipeline provides a framework to concatenate and minify or compress
JavaScript and CSS assets. It also adds the ability to write these assets in
other languages and pre-processors such as CoffeeScript, Sass and ERB.
It allows assets in your application to be automatically combined with assets
-from other gems. For example, jquery-rails includes a copy of jquery.js
-and enables AJAX features in Rails.
+from other gems.
The asset pipeline is implemented by the
[sprockets-rails](https://github.com/rails/sprockets-rails) gem,
@@ -35,7 +34,7 @@ rails new appname --skip-sprockets
```
Rails automatically adds the `sass-rails`, `coffee-rails` and `uglifier`
-gems to your Gemfile, which are used by Sprockets for asset compression:
+gems to your `Gemfile`, which are used by Sprockets for asset compression:
```ruby
gem 'sass-rails'
@@ -44,8 +43,8 @@ gem 'coffee-rails'
```
Using the `--skip-sprockets` option will prevent Rails from adding
-them to your Gemfile, so if you later want to enable
-the asset pipeline you will have to add those gems to your Gemfile. Also,
+them to your `Gemfile`, so if you later want to enable
+the asset pipeline you will have to add those gems to your `Gemfile`. Also,
creating an application with the `--skip-sprockets` option will generate
a slightly different `config/application.rb` file, with a require statement
for the sprockets railtie that is commented-out. You will have to remove
@@ -850,7 +849,7 @@ This mode uses more memory, performs more poorly than the default and is not
recommended.
If you are deploying a production application to a system without any
-pre-existing JavaScript runtimes, you may want to add one to your Gemfile:
+pre-existing JavaScript runtimes, you may want to add one to your `Gemfile`:
```ruby
group :production do
@@ -1205,10 +1204,10 @@ Adding Assets to Your Gems
Assets can also come from external sources in the form of gems.
-A good example of this is the `jquery-rails` gem which comes with Rails as the
-standard JavaScript library gem. This gem contains an engine class which
-inherits from `Rails::Engine`. By doing this, Rails is informed that the
-directory for this gem may contain assets and the `app/assets`, `lib/assets` and
+A good example of this is the `jquery-rails` gem.
+This gem contains an engine class which inherits from `Rails::Engine`.
+By doing this, Rails is informed that the directory for this
+gem may contain assets and the `app/assets`, `lib/assets` and
`vendor/assets` directories of this engine are added to the search path of
Sprockets.
@@ -1244,11 +1243,7 @@ moving the files from `public/` to the new locations. See [Asset
Organization](#asset-organization) above for guidance on the correct locations
for different file types.
-Next will be avoiding duplicate JavaScript files. Since jQuery is the default
-JavaScript library from Rails 3.1 onwards, you don't need to copy `jquery.js`
-into `app/assets` and it will be included automatically.
-
-The third is updating the various environment files with the correct default
+Next is updating the various environment files with the correct default
options.
In `application.rb`:
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index 9616647f15..f895cadea5 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -572,40 +572,32 @@ class Book < ApplicationRecord
end
```
-This declaration needs to be backed up by the proper foreign key declaration on the books table:
+This declaration needs to be backed up by a corresponding foreign key column in the books table. For a brand new table, the migration might look something like this:
```ruby
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :books do |t|
- t.datetime :published_at
- t.string :book_number
- t.integer :author_id
+ t.datetime :published_at
+ t.string :book_number
+ t.references :author
end
end
end
```
-If you create an association some time after you build the underlying model, you need to remember to create an `add_column` migration to provide the necessary foreign key.
-
-It's a good practice to add an index on the foreign key to improve queries
-performance and a foreign key constraint to ensure referential data integrity:
+Whereas for an existing table, it might look like this:
```ruby
-class CreateBooks < ActiveRecord::Migration[5.0]
+class AddAuthorToBooks < ActiveRecord::Migration[5.0]
def change
- create_table :books do |t|
- t.datetime :published_at
- t.string :book_number
- t.integer :author_id
- end
-
- add_index :books, :author_id
- add_foreign_key :books, :authors
+ add_reference :books, :author
end
end
```
+NOTE: If you wish to [enforce referential integrity at the database level](/active_record_migrations.html#foreign-keys), add the `foreign_key: true` option to the ‘reference’ column declarations above.
+
#### Creating Join Tables for `has_and_belongs_to_many` Associations
If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical book of the class names. So a join between author and book models will give the default join table name of "authors_books" because "a" outranks "b" in lexical ordering.
@@ -735,12 +727,9 @@ a.first_name = 'David'
a.first_name == b.author.first_name # => true
```
-Active Record supports automatic identification for most associations with standard names. However, Active Record will not automatically identify bi-directional associations that contain any of the following options:
+Active Record supports automatic identification for most associations with standard names. However, Active Record will not automatically identify bi-directional associations that contain a scope or any of the following options:
-* `:conditions`
* `:through`
-* `:polymorphic`
-* `:class_name`
* `:foreign_key`
For example, consider the following model declarations:
@@ -787,12 +776,6 @@ a.first_name = 'David'
a.first_name == b.writer.first_name # => true
```
-There are a few limitations to `:inverse_of` support:
-
-* They do not work with `:through` associations.
-* They do not work with `:polymorphic` associations.
-* They do not work with `:as` associations.
-
Detailed Association Reference
------------------------------
@@ -804,7 +787,7 @@ The `belongs_to` association creates a one-to-one match with another model. In d
#### Methods Added by `belongs_to`
-When you declare a `belongs_to` association, the declaring class automatically gains five methods related to the association:
+When you declare a `belongs_to` association, the declaring class automatically gains 6 methods related to the association:
* `association`
* `association=(associate)`
@@ -1012,7 +995,7 @@ When we execute `@user.todos.create` then the `@todo` record will have its
##### `:inverse_of`
-The `:inverse_of` option specifies the name of the `has_many` or `has_one` association that is the inverse of this association. Does not work in combination with the `:polymorphic` options.
+The `:inverse_of` option specifies the name of the `has_many` or `has_one` association that is the inverse of this association.
```ruby
class Author < ApplicationRecord
@@ -1082,7 +1065,7 @@ You can use any of the standard [querying methods](active_record_querying.html)
The `where` method lets you specify the conditions that the associated object must meet.
```ruby
-class book < ApplicationRecord
+class Book < ApplicationRecord
belongs_to :author, -> { where active: true }
end
```
@@ -1155,7 +1138,7 @@ The `has_one` association creates a one-to-one match with another model. In data
#### Methods Added by `has_one`
-When you declare a `has_one` association, the declaring class automatically gains five methods related to the association:
+When you declare a `has_one` association, the declaring class automatically gains 6 methods related to the association:
* `association`
* `association=(associate)`
@@ -1299,7 +1282,7 @@ TIP: In any case, Rails will not create foreign key columns for you. You need to
##### `:inverse_of`
-The `:inverse_of` option specifies the name of the `belongs_to` association that is the inverse of this association. Does not work in combination with the `:through` or `:as` options.
+The `:inverse_of` option specifies the name of the `belongs_to` association that is the inverse of this association.
```ruby
class Supplier < ApplicationRecord
@@ -1428,7 +1411,7 @@ The `has_many` association creates a one-to-many relationship with another model
#### Methods Added by `has_many`
-When you declare a `has_many` association, the declaring class automatically gains 16 methods related to the association:
+When you declare a `has_many` association, the declaring class automatically gains 17 methods related to the association:
* `collection`
* `collection<<(object, ...)`
@@ -1561,7 +1544,8 @@ The `collection.size` method returns the number of objects in the collection.
##### `collection.find(...)`
-The `collection.find` method finds objects within the collection. It uses the same syntax and options as `ActiveRecord::Base.find`.
+The `collection.find` method finds objects within the collection. It uses the same syntax and options as
+[`ActiveRecord::Base.find`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find).
```ruby
@available_book = @author.books.find(1)
@@ -1693,7 +1677,7 @@ TIP: In any case, Rails will not create foreign key columns for you. You need to
##### `:inverse_of`
-The `:inverse_of` option specifies the name of the `belongs_to` association that is the inverse of this association. Does not work in combination with the `:through` or `:as` options.
+The `:inverse_of` option specifies the name of the `belongs_to` association that is the inverse of this association.
```ruby
class Author < ApplicationRecord
@@ -1960,7 +1944,7 @@ The `has_and_belongs_to_many` association creates a many-to-many relationship wi
#### Methods Added by `has_and_belongs_to_many`
-When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 16 methods related to the association:
+When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 17 methods related to the association:
* `collection`
* `collection<<(object, ...)`
@@ -2091,7 +2075,8 @@ The `collection.size` method returns the number of objects in the collection.
##### `collection.find(...)`
-The `collection.find` method finds objects within the collection. It uses the same syntax and options as `ActiveRecord::Base.find`. It also adds the additional condition that the object must be in the collection.
+The `collection.find` method finds objects within the collection. It uses the same syntax and options as
+[`ActiveRecord::Base.find`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find).
```ruby
@assembly = @part.assemblies.find(1)
@@ -2099,7 +2084,7 @@ The `collection.find` method finds objects within the collection. It uses the sa
##### `collection.where(...)`
-The `collection.where` method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed. It also adds the additional condition that the object must be in the collection.
+The `collection.where` method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed.
```ruby
@new_assemblies = @part.assemblies.where("created_at > ?", 2.days.ago)
diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md
index dea87a18f8..6d30910ce8 100644
--- a/guides/source/autoloading_and_reloading_constants.md
+++ b/guides/source/autoloading_and_reloading_constants.md
@@ -8,7 +8,7 @@ This guide documents how constant autoloading and reloading works.
After reading this guide, you will know:
* Key aspects of Ruby constants
-* What is `autoload_paths`
+* What are the `autoload_paths` and how does eager loading work in production?
* How constant autoloading works
* What is `require_dependency`
* How constant reloading works
@@ -430,8 +430,8 @@ if `House` is still unknown when `app/models/beach_house.rb` is being eager
loaded, Rails autoloads it.
-autoload_paths
---------------
+autoload_paths and eager_load_paths
+-----------------------------------
As you probably know, when `require` gets a relative file name:
@@ -451,7 +451,7 @@ the idea is that when a constant like `Post` is hit and missing, if there's a
`post.rb` file for example in `app/models` Rails is going to find it, evaluate
it, and have `Post` defined as a side-effect.
-Alright, Rails has a collection of directories similar to `$LOAD_PATH` in which
+All right, Rails has a collection of directories similar to `$LOAD_PATH` in which
to look up `post.rb`. That collection is called `autoload_paths` and by
default it contains:
@@ -465,17 +465,22 @@ default it contains:
* The directory `test/mailers/previews`.
-Also, this collection is configurable via `config.autoload_paths`. For example,
-`lib` was in the list years ago, but no longer is. An application can opt-in
-by adding this to `config/application.rb`:
+`eager_load_paths` is initially the `app` paths above
-```ruby
-config.autoload_paths << "#{Rails.root}/lib"
-```
+How files are autoloaded depends on `eager_load` and `cache_classes` config settings which typically vary in development, production, and test modes:
+
+ * In **development**, you want quicker startup with incremental loading of application code. So `eager_load` should be set to `false`, and Rails will autoload files as needed (see [Autoloading Algorithms](#autoloading-algorithms) below) -- and then reload them when they change (see [Constant Reloading](#constant-reloading) below).
+ * In **production**, however you want consistency and thread-safety and can live with a longer boot time. So `eager_load` is set to `true`, and then during boot (before the app is ready to receive requests) Rails loads all files in the `eager_load_paths` and then turns off auto loading (NB: autoloading may be needed during eager loading). Not autoloading after boot is a `good thing`, as autoloading can cause the app to be have thread-safety problems.
+ * In **test**, for speed of execution (of individual tests) `eager_load` is `false`, so Rails follows development behaviour.
+
+What is described above are the defaults with a newly generated Rails app. There are multiple ways this can be configured differently (see [Configuring Rails Applications](configuring.html#rails-general-configuration).
+). But using `autoload_paths` on its own in the past (before Rails 5) developers might configure `autoload_paths` to add in extra locations (e.g. `lib` which used to be an autoload path list years ago, but no longer is). However this is now discouraged for most purposes, as it is likely to lead to production-only errors. It is possible to add new locations to both `config.eager_load_paths` and `config.autoload_paths` but use at your own risk.
+
+See also ([Autoloading in the Test Environment](#autoloading-in-the-test-environment).
`config.autoload_paths` is not changeable from environment-specific configuration files.
-The value of `autoload_paths` can be inspected. In a just generated application
+The value of `autoload_paths` can be inspected. In a just-generated application
it is (edited):
```
@@ -1329,3 +1334,17 @@ class C < BasicObject
end
end
```
+
+### Autoloading in the Test Environment
+
+When configuring the `test` environment for autoloading you might consider multiple factors.
+
+For example it might be worth running your tests with an identical setup to production (`config.eager_load = true`, `config.cache_classes = true`) in order to catch any problems before they hit production (this is compensation for the lack of dev-prod parity). However this will slow down the boot time for individual tests on a dev machine (and is not immediately compatible with spring see below). So one possibility is to do this on a
+[CI](https://en.wikipedia.org/wiki/Continuous_integration) machine only (which should run without spring).
+
+On a development machine you can then have your tests running with whatever is fastest (ideally `config.eager_load = false`).
+
+With the [Spring](https://github.com/rails/spring) pre-loader (included with new Rails apps), you ideally keep `config.eager_load = false` as per development. Sometimes you may end up with a hybrid configuration (`config.eager_load = true`, `config.cache_classes = true` AND `config.enable_dependency_loading = true`), see [spring issue](https://github.com/rails/spring/issues/519#issuecomment-348324369). However it might be simpler to keep the same configuration as development, and work out whatever it is that is causing autoloading to fail (perhaps by the results of your CI test results).
+
+Occasionally you may need to explicitly eager_load by using `Rails
+.application.eager_load!` in the setup of your tests -- this might occur if your [tests involve multithreading](https://stackoverflow.com/questions/25796409/in-rails-how-can-i-eager-load-all-code-before-a-specific-rspec-test).
diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md
index 96650b5be9..cd9f4b4a68 100644
--- a/guides/source/caching_with_rails.md
+++ b/guides/source/caching_with_rails.md
@@ -32,7 +32,7 @@ Basic Caching
This is an introduction to three types of caching techniques: page, action and
fragment caching. By default Rails provides fragment caching. In order to use
page and action caching you will need to add `actionpack-page_caching` and
-`actionpack-action_caching` to your Gemfile.
+`actionpack-action_caching` to your `Gemfile`.
By default, caching is only enabled in your production environment. To play
around with caching locally you'll want to enable caching in your local
@@ -100,9 +100,9 @@ called key-based expiration.
Cache fragments will also be expired when the view fragment changes (e.g., the
HTML in the view changes). The string of characters at the end of the key is a
-template tree digest. It is an MD5 hash computed based on the contents of the
-view fragment you are caching. If you change the view fragment, the MD5 hash
-will change, expiring the existing file.
+template tree digest. It is a hash digest computed based on the contents of the
+view fragment you are caching. If you change the view fragment, the digest will
+change, expiring the existing file.
TIP: Cache stores like Memcached will automatically delete old cache files.
@@ -366,9 +366,9 @@ There are some common options used by all cache implementations. These can be pa
* `:namespace` - This option can be used to create a namespace within the cache store. It is especially useful if your application shares a cache with other applications.
-* `:compress` - This option can be used to indicate that compression should be used in the cache. This can be useful for transferring large cache entries over a slow network.
+* `:compress` - Enabled by default. Compresses cache entries so more data can be stored in the same memory footprint, leading to fewer cache evictions and higher hit rates.
-* `:compress_threshold` - This option is used in conjunction with the `:compress` option to indicate a threshold under which cache entries should not be compressed. This defaults to 16 kilobytes.
+* `:compress_threshold` - Defaults to 1kB. Cache entries larger than this threshold, specified in bytes, are compressed.
* `:expires_in` - This option sets an expiration time in seconds for the cache entry when it will be automatically removed from the cache.
@@ -444,6 +444,53 @@ The `write` and `fetch` methods on this cache accept two additional options that
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
```
+### ActiveSupport::Cache::RedisCacheStore
+
+The Redis cache store takes advantage of Redis support for least-recently-used
+and least-frequently-used key eviction when it reaches max memory, allowing it
+to behave much like a Memcached cache server.
+
+Deployment note: Redis doesn't expire keys by default, so take care to use a
+dedicated Redis cache server. Don't fill up your persistent-Redis server with
+volatile cache data! Read the
+[Redis cache server setup guide](https://redis.io/topics/lru-cache) in detail.
+
+For an all-cache Redis server, set `maxmemory-policy` to an `allkeys` policy.
+Redis 4+ support least-frequently-used (`allkeys-lfu`) eviction, an excellent
+default choice. Redis 3 and earlier should use `allkeys-lru` for
+least-recently-used eviction.
+
+Set cache read and write timeouts relatively low. Regenerating a cached value
+is often faster than waiting more than a second to retrieve it. Both read and
+write timeouts default to 1 second, but may be set lower if your network is
+consistently low latency.
+
+Cache reads and writes never raise exceptions. They just return `nil` instead,
+behaving as if there was nothing in the cache. To gauge whether your cache is
+hitting exceptions, you may provide an `error_handler` to report to an
+exception gathering service. It must accept three keyword arguments: `method`,
+the cache store method that was originally called; `returning`, the value that
+was returned to the user, typically `nil`; and `exception`, the exception that
+was rescued.
+
+Putting it all together, a production Redis cache store may look something
+like this:
+
+```ruby
+cache_servers = %w[ "redis://cache-01:6379/0", "redis://cache-02:6379/0", … ],
+config.cache_store = :redis_cache_store, url: cache_servers,
+
+ connect_timeout: 30, # Defaults to 20 seconds
+ read_timeout: 0.2, # Defaults to 1 second
+ write_timeout: 0.2, # Defaults to 1 second
+
+ error_handler: -> (method:, returning:, exception:) {
+ # Report errors to Sentry as warnings
+ Raven.capture_exception exception, level: 'warning",
+ tags: { method: method, returning: returning }
+ }
+```
+
### ActiveSupport::Cache::NullStore
This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with `Rails.cache` but caching may interfere with being able to see the results of code changes. With this cache store, all `fetch` and `read` operations will result in a miss.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 2d03f0a61e..b0f39e7ab5 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -62,12 +62,10 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.autoload_once_paths` accepts an array of paths from which Rails will autoload constants that won't be wiped per request. Relevant if `config.cache_classes` is `false`, which is the case in development mode by default. Otherwise, all autoloading happens only once. All elements of this array must also be in `autoload_paths`. Default is an empty array.
-* `config.autoload_paths` accepts an array of paths from which Rails will autoload constants. Default is all directories under `app`.
+* `config.autoload_paths` accepts an array of paths from which Rails will autoload constants. Default is all directories under `app`. It is no longer recommended to adjust this. See [Autoloading and Reloading Constants](autoloading_and_reloading_constants.html#autoload-paths-and-eager-load-paths)
* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to `false` in development mode, and `true` in test and production modes.
-* `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`.
-
* `config.beginning_of_week` sets the default beginning of week for the
application. Accepts a valid week day symbol (e.g. `:monday`).
@@ -202,6 +200,7 @@ The full set of methods that can be used in this block are as follows:
* `force_plural` allows pluralized model names. Defaults to `false`.
* `helper` defines whether or not to generate helpers. Defaults to `true`.
* `integration_tool` defines which integration tool to use to generate integration tests. Defaults to `:test_unit`.
+* `system_tests` defines which integration tool to use to generate system tests. Defaults to `:test_unit`.
* `javascripts` turns on the hook for JavaScript files in generators. Used in Rails for when the `scaffold` generator is run. Defaults to `true`.
* `javascript_engine` configures the engine to be used (for eg. coffee) when generating assets. Defaults to `:js`.
* `orm` defines which orm to use. Defaults to `false` and will use Active Record by default.
@@ -322,6 +321,10 @@ All these configuration options are delegated to the `I18n` library.
* `config.active_record.schema_migrations_table_name` lets you set a string to be used as the name of the schema migrations table.
+* `config.active_record.internal_metadata_table_name` lets you set a string to be used as the name of the internal metadata table.
+
+* `config.active_record.protected_environments` lets you set an array of names of environments where destructive actions should be prohibited.
+
* `config.active_record.pluralize_table_names` specifies whether Rails will look for singular or plural table names in the database. If set to `true` (the default), then the Customer class will use the `customers` table. If set to false, then the Customer class will use the `customer` table.
* `config.active_record.default_timezone` determines whether to use `Time.local` (if set to `:local`) or `Time.utc` (if set to `:utc`) when pulling dates and times from the database. The default is `:utc`.
@@ -399,7 +402,7 @@ by adding the following to your `application.rb` file:
The schema dumper adds one additional configuration option:
-* `ActiveRecord::SchemaDumper.ignore_tables` accepts an array of tables that should _not_ be included in any generated schema file. This setting is ignored unless `config.active_record.schema_format == :ruby`.
+* `ActiveRecord::SchemaDumper.ignore_tables` accepts an array of tables that should _not_ be included in any generated schema file.
### Configuring Action Controller
@@ -459,7 +462,10 @@ The schema dumper adds one additional configuration option:
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '1; mode=block',
- 'X-Content-Type-Options' => 'nosniff'
+ 'X-Content-Type-Options' => 'nosniff',
+ 'X-Download-Options' => 'noopen',
+ 'X-Permitted-Cross-Domain-Policies' => 'none',
+ 'Referrer-Policy' => 'strict-origin-when-cross-origin'
}
```
@@ -534,6 +540,8 @@ Defaults to `'signed cookie'`.
`config.action_view` includes a small number of configuration settings:
+* `config.action_view.cache_template_loading` controls whether or not templates should be reloaded on each request. Defaults to whatever is set for `config.cache_classes`.
+
* `config.action_view.field_error_proc` provides an HTML generator for displaying errors that come from Active Model. The default is
```ruby
@@ -578,6 +586,8 @@ Defaults to `'signed cookie'`.
* `config.action_view.form_with_generates_remote_forms` determines whether `form_with` generates remote forms or not. This defaults to `true`.
+* `config.action_view.form_with_generates_ids` determines whether `form_with` generates ids on inputs. This defaults to `true`.
+
### Configuring Action Mailer
There are a number of settings available on `config.action_mailer`:
@@ -666,6 +676,8 @@ There are a few configuration options available in Active Support:
* `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`.
+* `config.active_support.use_sha1_digests` specifies whether to use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header. Defaults to false.
+
* `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`.
* `ActiveSupport::Cache::Store.logger` specifies the logger to use within cache store operations.
@@ -740,6 +752,41 @@ main application.
You can set this as nil to not mount Action Cable as part of your
normal Rails server.
+
+### Configuring Active Storage
+
+`config.active_storage` provides the following configuration options:
+
+* `config.active_storage.analyzers` accepts an array of classes indicating the analyzers available for Active Storage blobs. The default is `[ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer]`. The former can extract width and height of an image blob; the latter can extract width, height, duration, angle, and aspect ratio of a video blob.
+
+* `config.active_storage.previewers` accepts an array of classes indicating the image previewers available in Active Storage blobs. The default is `[ActiveStorage::Previewer::PDFPreviewer, ActiveStorage::Previewer::VideoPreviewer]`. The former can generate a thumbnail from the first page of a PDF blob; the latter from the relevant frame of a video blob.
+
+* `config.active_storage.paths` accepts a hash of options indicating the locations of previewer/analyzer commands. The default is `{}`, meaning the commands will be looked for in the default path. Can include any of these options:
+ * `:ffprobe` - The location of the ffprobe executable.
+ * `:mutool` - The location of the mutool executable.
+ * `:ffmpeg` - The location of the ffmpeg executable.
+
+ ```ruby
+ config.active_storage.paths[:ffprobe] = '/usr/local/bin/ffprobe'
+ ```
+
+* `config.active_storage.variable_content_types` accepts an array of strings indicating the content types that Active Storage can transform through ImageMagick. The default is `%w(image/png image/gif image/jpg image/jpeg image/vnd.adobe.photoshop)`.
+
+* `config.active_storage.content_types_to_serve_as_binary` accepts an array of strings indicating the content types that Active Storage will always serve as an attachment, rather than inline. The default is `%w(text/html
+text/javascript image/svg+xml application/postscript application/x-shockwave-flash text/xml application/xml application/xhtml+xml)`.
+
+* `config.active_storage.queue` can be used to set the name of the Active Job queue used to perform jobs like analyzing the content of a blob or purging a blog.
+
+ ```ruby
+ config.active_job.queue = :low_priority
+ ```
+
+* `config.active_storage.logger` can be used to set the logger used by Active Storage. Accepts a logger conforming to the interface of Log4r or the default Ruby Logger class.
+
+ ```ruby
+ config.active_job.logger = ActiveSupport::Logger.new(STDOUT)
+ ```
+
### Configuring a Database
Just about every Rails application will interact with a database. You can connect to the database by setting an environment variable `ENV['DATABASE_URL']` or by using a configuration file called `config/database.yml`.
@@ -1003,11 +1050,11 @@ Deploying your application using a reverse proxy has definite advantages over tr
Many modern web servers can be used as a proxy server to balance third-party elements such as caching servers or application servers.
-One such application server you can use is [Unicorn](http://unicorn.bogomips.org/) to run behind a reverse proxy.
+One such application server you can use is [Unicorn](https://bogomips.org/unicorn/) to run behind a reverse proxy.
In this case, you would need to configure the proxy server (NGINX, Apache, etc) to accept connections from your application server (Unicorn). By default Unicorn will listen for TCP connections on port 8080, but you can change the port or configure it to use sockets instead.
-You can find more information in the [Unicorn readme](http://unicorn.bogomips.org/README.html) and understand the [philosophy](http://unicorn.bogomips.org/PHILOSOPHY.html) behind it.
+You can find more information in the [Unicorn readme](https://bogomips.org/unicorn/README.html) and understand the [philosophy](https://bogomips.org/unicorn/PHILOSOPHY.html) behind it.
Once you've configured the application server, you must proxy requests to it by configuring your web server appropriately. For example your NGINX config may include:
@@ -1035,7 +1082,7 @@ server {
}
```
-Be sure to read the [NGINX documentation](http://nginx.org/en/docs/) for the most up-to-date information.
+Be sure to read the [NGINX documentation](https://nginx.org/en/docs/) for the most up-to-date information.
Rails Environment Settings
@@ -1057,7 +1104,7 @@ After loading the framework and any gems in your application, Rails turns to loa
NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the initializers folder on down.
-TIP: If you have any ordering dependency in your initializers, you can control the load order through naming. Initializer files are loaded in alphabetical order by their path. For example, `01_critical.rb` will be loaded before `02_normal.rb`.
+TIP: While Rails supports numbering of initializer file names for load ordering purposes, a better technique is to place any code that need to load in a specific order within the same file. This reduces file name churn, makes dependencies more explicit, and can help surface new concepts within your application.
Initialization events
---------------------
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 7424818757..c1668f989b 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -84,7 +84,9 @@ discussions new features require.
Helping to Resolve Existing Issues
----------------------------------
-As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the [issues list](https://github.com/rails/rails/issues) in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually:
+As a next step beyond reporting issues, you can help the core team resolve existing ones by providing feedback about them. If you are new to Rails core development, that might be a great way to walk your first steps, you'll get familiar with the code base and the processes.
+
+If you check the [issues list](https://github.com/rails/rails/issues) in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually:
### Verifying Bug Reports
@@ -132,7 +134,8 @@ learn about Ruby on Rails, and the API, which serves as a reference.
You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing them up to date with the latest edge Rails.
-To do so, open a pull request to [Rails](https://github.com/rails/rails) on GitHub.
+To do so, make changes to Rails guides source files (located [here](https://github.com/rails/rails/tree/master/guides/source) on GitHub). Then open a pull request to apply your
+changes to master branch.
When working with documentation, please take into account the [API Documentation Guidelines](api_documentation_guidelines.html) and the [Ruby on Rails Guides Guidelines](ruby_on_rails_guides_guidelines.html).
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 59205ee465..5cddf79eeb 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -72,7 +72,7 @@
url: active_support_core_extensions.html
description: This guide documents the Ruby core extensions defined in Active Support.
-
- name: Rails Internationalization API
+ name: Rails Internationalization (I18n) API
url: i18n.html
description: This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country, and so on.
-
@@ -84,6 +84,10 @@
url: active_job_basics.html
description: This guide provides you with all you need to get started creating, enqueuing, and executing background jobs.
-
+ name: Active Storage Overview
+ url: active_storage_overview.html
+ description: This guide covers how to attach files to your Active Record models.
+ -
name: Testing Rails Applications
url: testing.html
description: This is a rather comprehensive guide to the various testing facilities in Rails. It covers everything from 'What is a test?' to Integration Testing. Enjoy.
@@ -104,7 +108,7 @@
url: command_line.html
description: This guide covers the command line tools provided by Rails.
-
- name: Asset Pipeline
+ name: The Asset Pipeline
url: asset_pipeline.html
description: This guide documents the asset pipeline.
-
@@ -151,7 +155,7 @@
url: rails_on_rack.html
description: This guide covers Rails integration with Rack and interfacing with other Rack components.
-
- name: Creating and Customizing Rails Generators
+ name: Creating and Customizing Rails Generators & Templates
url: generators.html
description: This guide covers the process of adding a brand new generator to your extension or providing an alternative to an element of a built-in Rails generator (such as providing alternative test stubs for the scaffold generator).
-
@@ -159,6 +163,11 @@
url: engines.html
description: This guide explains how to write a mountable engine.
work_in_progress: true
+ -
+ name: Threading and Code Execution in Rails
+ url: threading_and_code_execution.html
+ description: This guide describes the considerations needed and tools available when working directly with concurrency in a Rails application.
+ work_in_progress: true
-
name: Contributing to Ruby on Rails
documents:
@@ -178,7 +187,7 @@
name: Maintenance Policy
documents:
-
- name: Maintenance Policy
+ name: Maintenance Policy for Ruby on Rails
url: maintenance_policy.html
description: What versions of Ruby on Rails are currently supported, and when to expect new versions.
-
@@ -189,6 +198,10 @@
url: upgrading_ruby_on_rails.html
description: This guide helps in upgrading applications to latest Ruby on Rails versions.
-
+ name: Ruby on Rails 5.2 Release Notes
+ url: 5_2_release_notes.html
+ description: Release notes for Rails 5.2.
+ -
name: Ruby on Rails 5.1 Release Notes
url: 5_1_release_notes.html
description: Release notes for Rails 5.1.
diff --git a/guides/source/engines.md b/guides/source/engines.md
index b226eac347..8d81296fa5 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -921,7 +921,7 @@ engine:
mattr_accessor :author_class
```
-This method works like its brothers, `attr_accessor` and `cattr_accessor`, but
+This method works like its siblings, `attr_accessor` and `cattr_accessor`, but
provides a setter and getter method on the module with the specified name. To
use it, it must be referenced using `Blorgh.author_class`.
@@ -982,7 +982,7 @@ Blorgh.author_class = "User"
WARNING: It's very important here to use the `String` version of the class,
rather than the class itself. If you were to use the class, Rails would attempt
to load that class and then reference the related table. This could lead to
-problems if the table wasn't already existing. Therefore, a `String` should be
+problems if the table didn't already exist. Therefore, a `String` should be
used and then converted to a class using `constantize` in the engine later on.
Go ahead and try to create a new article. You will see that it works exactly in the
@@ -1514,14 +1514,14 @@ To hook into the initialization process of one of the following classes use the
## Configuration hooks
-These are the available configuration hooks. They do not hook into any particular framework, instead they run in context of the entire application.
+These are the available configuration hooks. They do not hook into any particular framework, but instead they run in context of the entire application.
-| Hook | Use Case |
-| ---------------------- | ------------------------------------------------------------------------------------- |
-| `before_configuration` | First configurable block to run. Called before any initializers are run. |
-| `before_initialize` | Second configurable block to run. Called before frameworks initialize. |
-| `before_eager_load` | Third configurable block to run. Does not run if `config.cache_classes` set to false. |
-| `after_initialize` | Last configurable block to run. Called after frameworks initialize. |
+| Hook | Use Case |
+| ---------------------- | ---------------------------------------------------------------------------------- |
+| `before_configuration` | First configurable block to run. Called before any initializers are run. |
+| `before_initialize` | Second configurable block to run. Called before frameworks initialize. |
+| `before_eager_load` | Third configurable block to run. Does not run if `config.eager_load` set to false. |
+| `after_initialize` | Last configurable block to run. Called after frameworks initialize. |
### Example
diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md
index 4ce67df93a..53c567727f 100644
--- a/guides/source/form_helpers.md
+++ b/guides/source/form_helpers.md
@@ -1,7 +1,7 @@
**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
-Form Helpers
-============
+Action View Form Helpers
+========================
Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of the need to handle form control naming and its numerous attributes. Rails does away with this complexity by providing view helpers for generating form markup. However, since these helpers have different use cases, developers need to know the differences between the helper methods before putting them to use.
@@ -920,7 +920,7 @@ When an association accepts nested attributes `fields_for` renders its block onc
```ruby
def new
@person = Person.new
- 2.times { @person.addresses.build}
+ 2.times { @person.addresses.build }
end
```
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index b007baea87..6cf99a7e5c 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -462,8 +462,7 @@ You're getting this error now because Rails expects plain actions like this one
to have views associated with them to display their information. With no view
available, Rails will raise an exception.
-In the above image, the bottom line has been truncated. Let's see what the full
-error message looks like:
+Let's look at the full error message again:
>ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot.
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index e6aa6181cc..2b545e6b82 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -977,7 +977,7 @@ en:
```
NOTE: In order to use this helper, you need to install [DynamicForm](https://github.com/joelmoss/dynamic_form)
-gem by adding this line to your Gemfile: `gem 'dynamic_form'`.
+gem by adding this line to your `Gemfile`: `gem 'dynamic_form'`.
### Translations for Action Mailer E-Mail Subjects
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index 1541ea38cd..c4f1df487b 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -93,7 +93,7 @@ require 'bundler/setup' # Set up gems listed in the Gemfile.
In a standard Rails application, there's a `Gemfile` which declares all
dependencies of the application. `config/boot.rb` sets
-`ENV['BUNDLE_GEMFILE']` to the location of this file. If the Gemfile
+`ENV['BUNDLE_GEMFILE']` to the location of this file. If the `Gemfile`
exists, then `bundler/setup` is required. The require is used by Bundler to
configure the load path for your Gemfile's dependencies.
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 334595e4d2..3981199e95 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -99,9 +99,9 @@
To get started, you can read our <%= link_to 'documentation contributions', 'http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation' %> section.
</p>
<p>
- You may also find incomplete content, or stuff that is not up to date.
+ You may also find incomplete content or stuff that is not up to date.
Please do add any missing documentation for master. Make sure to check
- <%= link_to 'Edge Guides','http://edgeguides.rubyonrails.org' %> first to verify
+ <%= link_to 'Edge Guides', 'http://edgeguides.rubyonrails.org' %> first to verify
if the issues are already fixed or not on the master branch.
Check the <%= link_to 'Ruby on Rails Guides Guidelines', 'ruby_on_rails_guides_guidelines.html' %>
for style and conventions.
@@ -111,7 +111,7 @@
<%= link_to 'open an issue', 'https://github.com/rails/rails/issues' %>.
</p>
<p>And last but not least, any kind of discussion regarding Ruby on Rails
- documentation is very welcome in the <%= link_to 'rubyonrails-docs mailing list', 'https://groups.google.com/forum/#!forum/rubyonrails-docs' %>.
+ documentation is very welcome on the <%= link_to 'rubyonrails-docs mailing list', 'https://groups.google.com/forum/#!forum/rubyonrails-docs' %>.
</p>
</div>
</div>
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index b9b327252f..15345c94b7 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -97,7 +97,7 @@ If we want to display the properties of all the books in our view, we can do so
<%= link_to "New book", new_book_path %>
```
-NOTE: The actual rendering is done by subclasses of `ActionView::TemplateHandlers`. This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. Beginning with Rails 2, the standard extensions are `.erb` for ERB (HTML with embedded Ruby), and `.builder` for Builder (XML generator).
+NOTE: The actual rendering is done by nested classes of the module [`ActionView::Template::Handlers`](http://api.rubyonrails.org/classes/ActionView/Template/Handlers.html). This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler.
### Using `render`
@@ -285,7 +285,7 @@ the response. Using `:plain` or `:html` might be more appropriate most of the
time.
NOTE: Unless overridden, your response returned from this render option will be
-`text/html`, as that is the default content type of Action Dispatch response.
+`text/plain`, as that is the default content type of Action Dispatch response.
#### Options for `render`
@@ -1266,7 +1266,7 @@ You can also pass in arbitrary local variables to any partial you are rendering
In this case, the partial will have access to a local variable `title` with the value "Products Page".
-TIP: Rails also makes a counter variable available within a partial called by the collection, named after the member of the collection followed by `_counter`. For example, if you're rendering `@products`, within the partial you can refer to `product_counter` to tell you how many times the partial has been rendered. This does not work in conjunction with the `as: :value` option.
+TIP: Rails also makes a counter variable available within a partial called by the collection, named after the title of the partial followed by `_counter`. For example, when rendering a collection `@products` the partial `_product.html.erb` can access the variable `product_counter` which indexes the number of times it has been rendered within the enclosing view.
You can also specify a second partial to be rendered between instances of the main partial by using the `:spacer_template` option:
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index aa1476ecc0..1627205b7b 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -122,9 +122,11 @@ use ActiveRecord::Migration::CheckPending
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
+use ActionDispatch::ContentSecurityPolicy::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
+use Rack::TempfileReaper
run MyApp::Application.routes
```
@@ -249,7 +251,7 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
**`Rails::Rack::Logger`**
-* Notifies the logs that the request has began. After request is complete, flushes all the logs.
+* Notifies the logs that the request has begun. After the request is complete, flushes all the logs.
**`ActionDispatch::ShowExceptions`**
@@ -283,18 +285,26 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol
* Sets up the flash keys. Only available if `config.action_controller.session_store` is set to a value.
+**`ActionDispatch::ContentSecurityPolicy::Middleware`**
+
+* Provides a DSL to configure a Content-Security-Policy header.
+
**`Rack::Head`**
* Converts HEAD requests to `GET` requests and serves them as so.
**`Rack::ConditionalGet`**
-* Adds support for "Conditional `GET`" so that server responds with nothing if page wasn't changed.
+* Adds support for "Conditional `GET`" so that server responds with nothing if the page wasn't changed.
**`Rack::ETag`**
* Adds ETag header on all String bodies. ETags are used to validate cache.
+**`Rack::TempfileReaper`**
+
+* Cleans up tempfiles used to buffer multipart requests.
+
TIP: It's possible to use any of the above middlewares in your custom Rack stack.
Resources
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 638f77be13..efc0e32b56 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -852,6 +852,49 @@ You can specify unicode character routes directly. For example:
get 'こんにちは', to: 'welcome#index'
```
+### Direct routes
+
+You can create custom URL helpers directly. For example:
+
+```ruby
+direct :homepage do
+ "http://www.rubyonrails.org"
+end
+
+# >> homepage_url
+# => "http://www.rubyonrails.org"
+```
+
+The return value of the block must be a valid argument for the `url_for` method. So, you can pass a valid string URL, Hash, Array, an Active Model instance, or an Active Model class.
+
+```ruby
+direct :commentable do |model|
+ [ model, anchor: model.dom_id ]
+end
+
+direct :main do
+ { controller: 'pages', action: 'index', subdomain: 'www' }
+end
+```
+
+### Using `resolve`
+
+The `resolve` method allows customizing polymorphic mapping of models. For example:
+
+``` ruby
+resource :basket
+
+resolve("Basket") { [:basket] }
+```
+
+``` erb
+<%= form_for @basket do |form| %>
+ <!-- basket form -->
+<% end %>
+```
+
+This will generate the singular URL `/basket` instead of the usual `/baskets/:id`.
+
Customizing Resourceful Routes
------------------------------
diff --git a/guides/source/security.md b/guides/source/security.md
index cfa777d433..28ddbdc26a 100644
--- a/guides/source/security.md
+++ b/guides/source/security.md
@@ -1,7 +1,7 @@
**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
-Ruby on Rails Security Guide
-============================
+Securing Rails Applications
+===========================
This manual describes common security problems in web applications and how to avoid them with Rails.
@@ -52,7 +52,7 @@ User.find(session[:user_id])
NOTE: _The session ID is a 32-character random hex string._
-The session ID is generated using `SecureRandom.hex` which generates a random hex string using platform specific methods (such as OpenSSL, /dev/urandom or Win32) for generating cryptographically secure random numbers. Currently it is not feasible to brute-force Rails' session IDs.
+The session ID is generated using `SecureRandom.hex` which generates a random hex string using platform specific methods (such as OpenSSL, /dev/urandom or Win32 CryptoAPI) for generating cryptographically secure random numbers. Currently it is not feasible to brute-force Rails' session IDs.
### Session Hijacking
@@ -192,9 +192,9 @@ rotations going at any one time.
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)
+[MessageEncryptor API](http://api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html)
and
-[MessageVerifier API](api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html)
+[MessageVerifier API](http://api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html)
documentation.
### Replay Attacks for CookieStore Sessions
@@ -474,7 +474,7 @@ The common admin interface works like this: it's located at www.example.com/admi
* Does the admin really have to access the interface from everywhere in the world? Think about _limiting the login to a bunch of source IP addresses_. Examine request.remote_ip to find out about the user's IP address. This is not bullet-proof, but a great barrier. Remember that there might be a proxy in use, though.
-* _Put the admin interface to a special sub-domain_ such as admin.application.com and make it a separate application with its own user management. This makes stealing an admin cookie from the usual domain, www.application.com, impossible. This is because of the same origin policy in your browser: An injected (XSS) script on www.application.com may not read the cookie for admin.application.com and vice-versa.
+* _Put the admin interface to a special subdomain_ such as admin.application.com and make it a separate application with its own user management. This makes stealing an admin cookie from the usual domain, www.application.com, impossible. This is because of the same origin policy in your browser: An injected (XSS) script on www.application.com may not read the cookie for admin.application.com and vice-versa.
User Management
---------------
@@ -551,7 +551,7 @@ Here are some ideas how to hide honeypot fields by JavaScript and/or CSS:
* make the elements very small or color them the same as the background of the page
* leave the fields displayed, but tell humans to leave them blank
-The most simple negative CAPTCHA is one hidden honeypot field. On the server side, you will check the value of the field: If it contains any text, it must be a bot. Then, you can either ignore the post or return a positive result, but not saving the post to the database. This way the bot will be satisfied and moves on. You can do this with annoying users, too.
+The most simple negative CAPTCHA is one hidden honeypot field. On the server side, you will check the value of the field: If it contains any text, it must be a bot. Then, you can either ignore the post or return a positive result, but not saving the post to the database. This way the bot will be satisfied and moves on.
You can find more sophisticated negative CAPTCHAs in Ned Batchelder's [blog post](http://nedbatchelder.com/text/stopbots.html):
@@ -573,18 +573,6 @@ config.filter_parameters << :password
NOTE: Provided parameters will be filtered out by partial matching regular expression. Rails adds default `:password` in the appropriate initializer (`initializers/filter_parameter_logging.rb`) and cares about typical application parameters `password` and `password_confirmation`.
-### Good Passwords
-
-INFO: _Do you find it hard to remember all your passwords? Don't write them down, but use the initial letters of each word in an easy to remember sentence._
-
-Bruce Schneier, a security technologist, [has analyzed](http://www.schneier.com/blog/archives/2006/12/realworld_passw.html) 34,000 real-world user names and passwords from the MySpace phishing attack mentioned [below](#examples-from-the-underground). It turns out that most of the passwords are quite easy to crack. The 20 most common passwords are:
-
-password1, abc123, myspace1, password, blink182, qwerty1, ****you, 123abc, baseball1, football1, 123456, soccer, monkey1, liverpool1, princess1, jordan23, slipknot1, superman1, iloveyou1, and monkey.
-
-It is interesting that only 4% of these passwords were dictionary words and the great majority is actually alphanumeric. However, password cracker dictionaries contain a large number of today's passwords, and they try out all kinds of (alphanumerical) combinations. If an attacker knows your user name and you use a weak password, your account will be easily cracked.
-
-A good password is a long alphanumeric combination of mixed cases. As this is quite hard to remember, it is advisable to enter only the _first letters of a sentence that you can easily remember_. For example "The quick brown fox jumps over the lazy dog" will be "Tqbfjotld". Note that this is just an example, you should not use well known phrases like these, as they might appear in cracker dictionaries, too.
-
### Regular Expressions
INFO: _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._
@@ -1070,7 +1058,10 @@ Every HTTP response from your Rails application receives the following default s
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '1; mode=block',
- 'X-Content-Type-Options' => 'nosniff'
+ 'X-Content-Type-Options' => 'nosniff',
+ 'X-Download-Options' => 'noopen',
+ 'X-Permitted-Cross-Domain-Policies' => 'none',
+ 'Referrer-Policy' => 'strict-origin-when-cross-origin'
}
```
diff --git a/guides/source/testing.md b/guides/source/testing.md
index c5b2a694e7..af6127f4a5 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -1,7 +1,7 @@
**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
-A Guide to Testing Rails Applications
-=====================================
+Testing Rails Applications
+==========================
This guide covers built-in mechanisms in Rails for testing your application.
@@ -319,6 +319,8 @@ specify to make your test failure messages clearer.
| `assert_not_includes( collection, obj, [msg] )` | Ensures that `obj` is not in `collection`.|
| `assert_in_delta( expected, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.|
| `assert_not_in_delta( expected, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.|
+| `assert_in_epsilon ( expected, actual, [epsilon], [msg] )` | Ensures that the numbers `expected` and `actual` have a relative error less than `epsilon`.|
+| `assert_not_in_epsilon ( expected, actual, [epsilon], [msg] )` | Ensures that the numbers `expected` and `actual` don't have a relative error less than `epsilon`.|
| `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.|
| `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.|
| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.|
@@ -645,7 +647,7 @@ system tests should live.
If you want to change the default settings you can change what the system
tests are "driven by". Say you want to change the driver from Selenium to
-Poltergeist. First add the `poltergeist` gem to your Gemfile. Then in your
+Poltergeist. First add the `poltergeist` gem to your `Gemfile`. Then in your
`application_system_test_case.rb` file do the following:
```ruby
@@ -671,6 +673,17 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
end
```
+If you want to use a headless browser, you could use Headless Chrome or Headless Firefox by adding
+`headless_chrome` or `headless_firefox` in the `:using` argument.
+
+```ruby
+require "test_helper"
+
+class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :headless_chrome
+end
+```
+
If your Capybara configuration requires more setup than provided by Rails, this
additional configuration could be added into the `application_system_test_case.rb`
file.
@@ -765,9 +778,37 @@ Then the test will fill in the title and body of the article with the specified
text. Once the fields are filled in, "Create Article" is clicked on which will
send a POST request to create the new article in the database.
-We will be redirected back to the the articles index page and there we assert
+We will be redirected back to the articles index page and there we assert
that the text from the new article's title is on the articles index page.
+#### Testing for multiple screen sizes
+If you want to test for mobile sizes on top of testing for desktop,
+you can create another class that inherits from SystemTestCase and use in your
+test suite. In this example a file called `mobile_system_test_case.rb` is created
+in the `/test` directory with the following configuration.
+
+```ruby
+require "test_helper"
+
+class MobileSystemTestCase < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :chrome, screen_size: [375, 667]
+end
+```
+To use this configuration, create a test inside `test/system` that inherits from `MobileSystemTestCase`.
+Now you can test your app using multiple different configurations.
+
+```ruby
+require "mobile_system_test_case"
+
+class PostsTest < MobileSystemTestCase
+
+ test "visiting the index" do
+ visit posts_url
+ assert_selector "h1", text: "Posts"
+ end
+end
+```
+
#### Taking it further
The beauty of system testing is that it is similar to integration testing in
@@ -1494,7 +1535,7 @@ Testing Jobs
------------
Since your custom jobs can be queued at different levels inside your application,
-you'll need to test both, the jobs themselves (their behavior when they get enqueued)
+you'll need to test both the jobs themselves (their behavior when they get enqueued)
and that other entities correctly enqueue them.
### A Basic Test Case
diff --git a/guides/source/threading_and_code_execution.md b/guides/source/threading_and_code_execution.md
new file mode 100644
index 0000000000..e4febc7507
--- /dev/null
+++ b/guides/source/threading_and_code_execution.md
@@ -0,0 +1,324 @@
+**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
+
+Threading and Code Execution in Rails
+=====================================
+
+After reading this guide, you will know:
+
+* What code Rails will automatically execute concurrently
+* How to integrate manual concurrency with Rails internals
+* How to wrap all application code
+* How to affect application reloading
+
+--------------------------------------------------------------------------------
+
+Automatic Concurrency
+---------------------
+
+Rails automatically allows various operations to be performed at the same time.
+
+When using a threaded web server, such as the default Puma, multiple HTTP
+requests will be served simultaneously, with each request provided its own
+controller instance.
+
+Threaded Active Job adapters, including the built-in Async, will likewise
+execute several jobs at the same time. Action Cable channels are managed this
+way too.
+
+These mechanisms all involve multiple threads, each managing work for a unique
+instance of some object (controller, job, channel), while sharing the global
+process space (such as classes and their configurations, and global variables).
+As long as your code doesn't modify any of those shared things, it can mostly
+ignore that other threads exist.
+
+The rest of this guide describes the mechanisms Rails uses to make it "mostly
+ignorable", and how extensions and applications with special needs can use them.
+
+Executor
+--------
+
+The Rails Executor separates application code from framework code: any time the
+framework invokes code you've written in your application, it will be wrapped by
+the Executor.
+
+The Executor consists of two callbacks: `to_run` and `to_complete`. The Run
+callback is called before the application code, and the Complete callback is
+called after.
+
+### Default callbacks
+
+In a default Rails application, the Executor callbacks are used to:
+
+* track which threads are in safe positions for autoloading and reloading
+* enable and disable the Active Record query cache
+* return acquired Active Record connections to the pool
+* constrain internal cache lifetimes
+
+Prior to Rails 5.0, some of these were handled by separate Rack middleware
+classes (such as `ActiveRecord::ConnectionAdapters::ConnectionManagement`), or
+directly wrapping code with methods like
+`ActiveRecord::Base.connection_pool.with_connection`. The Executor replaces
+these with a single more abstract interface.
+
+### Wrapping application code
+
+If you're writing a library or component that will invoke application code, you
+should wrap it with a call to the executor:
+
+```ruby
+Rails.application.executor.wrap do
+ # call application code here
+end
+```
+
+TIP: If you repeatedly invoke application code from a long-running process, you
+may want to wrap using the Reloader instead.
+
+Each thread should be wrapped before it runs application code, so if your
+application manually delegates work to other threads, such as via `Thread.new`
+or Concurrent Ruby features that use thread pools, you should immediately wrap
+the block:
+
+```ruby
+Thread.new do
+ Rails.application.executor.wrap do
+ # your code here
+ end
+end
+```
+
+NOTE: Concurrent Ruby uses a `ThreadPoolExecutor`, which it sometimes configures
+with an `executor` option. Despite the name, it is unrelated.
+
+The Executor is safely re-entrant; if it is already active on the current
+thread, `wrap` is a no-op.
+
+If it's impractical to wrap the application code in a block (for
+example, the Rack API makes this problematic), you can also use the `run!` /
+`complete!` pair:
+
+```ruby
+Thread.new do
+ execution_context = Rails.application.executor.run!
+ # your code here
+ensure
+ execution_context.complete! if execution_context
+end
+```
+
+### Concurrency
+
+The Executor will put the current thread into `running` mode in the Load
+Interlock. This operation will block temporarily if another thread is currently
+either autoloading a constant or unloading/reloading the application.
+
+Reloader
+--------
+
+Like the Executor, the Reloader also wraps application code. If the Executor is
+not already active on the current thread, the Reloader will invoke it for you,
+so you only need to call one. This also guarantees that everything the Reloader
+does, including all its callback invocations, occurs wrapped inside the
+Executor.
+
+```ruby
+Rails.application.reloader.wrap do
+ # call application code here
+end
+```
+
+The Reloader is only suitable where a long-running framework-level process
+repeatedly calls into application code, such as for a web server or job queue.
+Rails automatically wraps web requests and Active Job workers, so you'll rarely
+need to invoke the Reloader for yourself. Always consider whether the Executor
+is a better fit for your use case.
+
+### Callbacks
+
+Before entering the wrapped block, the Reloader will check whether the running
+application needs to be reloaded -- for example, because a model's source file has
+been modified. If it determines a reload is required, it will wait until it's
+safe, and then do so, before continuing. When the application is configured to
+always reload regardless of whether any changes are detected, the reload is
+instead performed at the end of the block.
+
+The Reloader also provides `to_run` and `to_complete` callbacks; they are
+invoked at the same points as those of the Executor, but only when the current
+execution has initiated an application reload. When no reload is deemed
+necessary, the Reloader will invoke the wrapped block with no other callbacks.
+
+### Class Unload
+
+The most significant part of the reloading process is the Class Unload, where
+all autoloaded classes are removed, ready to be loaded again. This will occur
+immediately before either the Run or Complete callback, depending on the
+`reload_classes_only_on_change` setting.
+
+Often, additional reloading actions need to be performed either just before or
+just after the Class Unload, so the Reloader also provides `before_class_unload`
+and `after_class_unload` callbacks.
+
+### Concurrency
+
+Only long-running "top level" processes should invoke the Reloader, because if
+it determines a reload is needed, it will block until all other threads have
+completed any Executor invocations.
+
+If this were to occur in a "child" thread, with a waiting parent inside the
+Executor, it would cause an unavoidable deadlock: the reload must occur before
+the child thread is executed, but it cannot be safely performed while the parent
+thread is mid-execution. Child threads should use the Executor instead.
+
+Framework Behavior
+------------------
+
+The Rails framework components use these tools to manage their own concurrency
+needs too.
+
+`ActionDispatch::Executor` and `ActionDispatch::Reloader` are Rack middlewares
+that wraps the request with a supplied Executor or Reloader, respectively. They
+are automatically included in the default application stack. The Reloader will
+ensure any arriving HTTP request is served with a freshly-loaded copy of the
+application if any code changes have occurred.
+
+Active Job also wraps its job executions with the Reloader, loading the latest
+code to execute each job as it comes off the queue.
+
+Action Cable uses the Executor instead: because a Cable connection is linked to
+a specific instance of a class, it's not possible to reload for every arriving
+websocket message. Only the message handler is wrapped, though; a long-running
+Cable connection does not prevent a reload that's triggered by a new incoming
+request or job. Instead, Action Cable uses the Reloader's `before_class_unload`
+callback to disconnect all its connections. When the client automatically
+reconnects, it will be speaking to the new version of the code.
+
+The above are the entry points to the framework, so they are responsible for
+ensuring their respective threads are protected, and deciding whether a reload
+is necessary. Other components only need to use the Executor when they spawn
+additional threads.
+
+### Configuration
+
+The Reloader only checks for file changes when `cache_classes` is false and
+`reload_classes_only_on_change` is true (which is the default in the
+`development` environment).
+
+When `cache_classes` is true (in `production`, by default), the Reloader is only
+a pass-through to the Executor.
+
+The Executor always has important work to do, like database connection
+management. When `cache_classes` and `eager_load` are both true (`production`),
+no autoloading or class reloading will occur, so it does not need the Load
+Interlock. If either of those are false (`development`), then the Executor will
+use the Load Interlock to ensure constants are only loaded when it is safe.
+
+Load Interlock
+--------------
+
+The Load Interlock allows autoloading and reloading to be enabled in a
+multi-threaded runtime environment.
+
+When one thread is performing an autoload by evaluating the class definition
+from the appropriate file, it is important no other thread encounters a
+reference to the partially-defined constant.
+
+Similarly, it is only safe to perform an unload/reload when no application code
+is in mid-execution: after the reload, the `User` constant, for example, may
+point to a different class. Without this rule, a poorly-timed reload would mean
+`User.new.class == User`, or even `User == User`, could be false.
+
+Both of these constraints are addressed by the Load Interlock. It keeps track of
+which threads are currently running application code, loading a class, or
+unloading autoloaded constants.
+
+Only one thread may load or unload at a time, and to do either, it must wait
+until no other threads are running application code. If a thread is waiting to
+perform a load, it doesn't prevent other threads from loading (in fact, they'll
+cooperate, and each perform their queued load in turn, before all resuming
+running together).
+
+### `permit_concurrent_loads`
+
+The Executor automatically acquires a `running` lock for the duration of its
+block, and autoload knows when to upgrade to a `load` lock, and switch back to
+`running` again afterwards.
+
+Other blocking operations performed inside the Executor block (which includes
+all application code), however, can needlessly retain the `running` lock. If
+another thread encounters a constant it must autoload, this can cause a
+deadlock.
+
+For example, assuming `User` is not yet loaded, the following will deadlock:
+
+```ruby
+Rails.application.executor.wrap do
+ th = Thread.new do
+ Rails.application.executor.wrap do
+ User # inner thread waits here; it cannot load
+ # User while another thread is running
+ end
+ end
+
+ th.join # outer thread waits here, holding 'running' lock
+end
+```
+
+To prevent this deadlock, the outer thread can `permit_concurrent_loads`. By
+calling this method, the thread guarantees it will not dereference any
+possibly-autoloaded constant inside the supplied block. The safest way to meet
+that promise is to put it as close as possible to the blocking call:
+
+```ruby
+Rails.application.executor.wrap do
+ th = Thread.new do
+ Rails.application.executor.wrap do
+ User # inner thread can acquire the 'load' lock,
+ # load User, and continue
+ end
+ end
+
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ th.join # outer thread waits here, but has no lock
+ end
+end
+```
+
+Another example, using Concurrent Ruby:
+
+```ruby
+Rails.application.executor.wrap do
+ futures = 3.times.collect do |i|
+ Concurrent::Future.execute do
+ Rails.application.executor.wrap do
+ # do work here
+ end
+ end
+ end
+
+ values = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
+ futures.collect(&:value)
+ end
+end
+```
+
+
+### ActionDispatch::DebugLocks
+
+If your application is deadlocking and you think the Load Interlock may be
+involved, you can temporarily add the ActionDispatch::DebugLocks middleware to
+`config/application.rb`:
+
+```ruby
+config.middleware.insert_before Rack::Sendfile,
+ ActionDispatch::DebugLocks
+```
+
+If you then restart the application and re-trigger the deadlock condition,
+`/rails/locks` will show a summary of all threads currently known to the
+interlock, which lock level they are holding or awaiting, and their current
+backtrace.
+
+Generally a deadlock will be caused by the interlock conflicting with some other
+external lock or blocking I/O call. Once you find it, you can wrap it with
+`permit_concurrent_loads`.
+
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 9bc87e4bf0..51b284ff12 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -1,7 +1,7 @@
**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON http://guides.rubyonrails.org.**
-A Guide for Upgrading Ruby on Rails
-===================================
+Upgrading Ruby on Rails
+=======================
This guide provides steps to be followed when you upgrade your applications to a newer version of Ruby on Rails. These steps are also available in individual release guides.
@@ -45,7 +45,7 @@ TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterp
### The Update Task
Rails provides the `app:update` task (`rake rails:update` on 4.2 and earlier). After updating the Rails version
-in the Gemfile, run this task.
+in the `Gemfile`, run this task.
This will help you with the creation of new files and changes of old files in an
interactive session.
@@ -65,6 +65,17 @@ Overwrite /myapp/config/application.rb? (enter "h" for help) [Ynaqdh]
Don't forget to review the difference, to see if there were any unexpected changes.
+Upgrading from Rails 5.1 to Rails 5.2
+-------------------------------------
+
+For more information on changes made to Rails 5.2 please see the [release notes](5_2_release_notes.html).
+
+### Bootsnap
+
+Rails 5.2 adds bootsnap gem in the [newly generated app's Gemfile](https://github.com/rails/rails/pull/29313).
+The `app:update` task sets it up in `boot.rb`. If you want to use it, then add it in the Gemfile,
+otherwise change the `boot.rb` to not use bootsnap.
+
Upgrading from Rails 5.0 to Rails 5.1
-------------------------------------
@@ -72,7 +83,7 @@ For more information on changes made to Rails 5.1 please see the [release notes]
### Top-level `HashWithIndifferentAccess` is soft-deprecated
-If your application uses the the top-level `HashWithIndifferentAccess` class, you
+If your application uses the top-level `HashWithIndifferentAccess` class, you
should slowly move your code to instead use `ActiveSupport::HashWithIndifferentAccess`.
It is only soft-deprecated, which means that your code will not break at the
@@ -179,7 +190,7 @@ See [#19034](https://github.com/rails/rails/pull/19034) for more details.
`assigns` and `assert_template` have been extracted to the `rails-controller-testing` gem. To
continue using these methods in your controller tests, add `gem 'rails-controller-testing'` to
-your Gemfile.
+your `Gemfile`.
If you are using Rspec for testing, please see the extra configuration required in the gem's
documentation.
@@ -212,7 +223,7 @@ true.
`ActiveModel::Serializers::Xml` has been extracted from Rails to the `activemodel-serializers-xml`
gem. To continue using XML serialization in your application, add `gem 'activemodel-serializers-xml'`
-to your Gemfile.
+to your `Gemfile`.
### Removed Support for Legacy `mysql` Database Adapter
@@ -278,7 +289,7 @@ You can now just call the dependency once with a wildcard.
### `ActionView::Helpers::RecordTagHelper` moved to external gem (record_tag_helper)
-`content_tag_for` and `div_for` have been removed in favor of just using `content_tag`. To continue using the older methods, add the `record_tag_helper` gem to your Gemfile:
+`content_tag_for` and `div_for` have been removed in favor of just using `content_tag`. To continue using the older methods, add the `record_tag_helper` gem to your `Gemfile`:
```ruby
gem 'record_tag_helper', '~> 1.0'
@@ -415,7 +426,7 @@ First, add `gem 'web-console', '~> 2.0'` to the `:development` group in your `Ge
### Responders
-`respond_with` and the class-level `respond_to` methods have been extracted to the `responders` gem. To use them, simply add `gem 'responders', '~> 2.0'` to your Gemfile. Calls to `respond_with` and `respond_to` (again, at the class level) will no longer work without having included the `responders` gem in your dependencies:
+`respond_with` and the class-level `respond_to` methods have been extracted to the `responders` gem. To use them, simply add `gem 'responders', '~> 2.0'` to your `Gemfile`. Calls to `respond_with` and `respond_to` (again, at the class level) will no longer work without having included the `responders` gem in your dependencies:
```ruby
# app/controllers/users_controller.rb
@@ -559,7 +570,7 @@ Read the [gem's readme](https://github.com/rails/rails-html-sanitizer) for more
The documentation for `PermitScrubber` and `TargetScrubber` explains how you
can gain complete control over when and how elements should be stripped.
-If your application needs to use the old sanitizer implementation, include `rails-deprecated_sanitizer` in your Gemfile:
+If your application needs to use the old sanitizer implementation, include `rails-deprecated_sanitizer` in your `Gemfile`:
```ruby
gem 'rails-deprecated_sanitizer'
@@ -567,7 +578,7 @@ gem 'rails-deprecated_sanitizer'
### Rails DOM Testing
-The [`TagAssertions` module](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/TagAssertions.html) (containing methods such as `assert_tag`), [has been deprecated](https://github.com/rails/rails/blob/6061472b8c310158a2a2e8e9a6b81a1aef6b60fe/actionpack/lib/action_dispatch/testing/assertions/dom.rb) in favor of the `assert_select` methods from the `SelectorAssertions` module, which has been extracted into the [rails-dom-testing gem](https://github.com/rails/rails-dom-testing).
+The [`TagAssertions` module](http://api.rubyonrails.org/v4.1/classes/ActionDispatch/Assertions/TagAssertions.html) (containing methods such as `assert_tag`), [has been deprecated](https://github.com/rails/rails/blob/6061472b8c310158a2a2e8e9a6b81a1aef6b60fe/actionpack/lib/action_dispatch/testing/assertions/dom.rb) in favor of the `assert_select` methods from the `SelectorAssertions` module, which has been extracted into the [rails-dom-testing gem](https://github.com/rails/rails-dom-testing).
### Masked Authenticity Tokens
@@ -617,7 +628,7 @@ migration DSL counterpart.
The migration procedure is as follows:
-1. remove `gem "foreigner"` from the Gemfile.
+1. remove `gem "foreigner"` from the `Gemfile`.
2. run `bundle install`.
3. run `bin/rake db:schema:dump`.
4. make sure that `db/schema.rb` contains every foreign key definition with
@@ -769,7 +780,7 @@ and has been removed from Rails.
If your application currently depends on MultiJSON directly, you have a few options:
-1. Add 'multi_json' to your Gemfile. Note that this might cease to work in the future
+1. Add 'multi_json' to your `Gemfile`. Note that this might cease to work in the future
2. Migrate away from MultiJSON by using `obj.to_json`, and `JSON.parse(str)` instead.
@@ -810,7 +821,7 @@ part of the rewrite, the following features have been removed from the encoder:
If your application depends on one of these features, you can get them back by
adding the [`activesupport-json_encoder`](https://github.com/rails/activesupport-json_encoder)
-gem to your Gemfile.
+gem to your `Gemfile`.
#### JSON representation of Time objects
@@ -1135,7 +1146,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
+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
file (in `config/application.rb`):
@@ -1147,7 +1158,7 @@ Bundler.require(*Rails.groups)
### vendor/plugins
-Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must replace any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
+Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must replace any plugins by extracting them to gems and adding them to your `Gemfile`. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
### Active Record
@@ -1214,7 +1225,7 @@ end
### Active Resource
-Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile.
+Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your `Gemfile`.
### Active Model
@@ -1414,7 +1425,7 @@ config.active_record.mass_assignment_sanitizer = :strict
### vendor/plugins
-Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. While it's not strictly necessary as part of a Rails 3.2 upgrade, you can start replacing any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
+Rails 3.2 deprecates `vendor/plugins` and Rails 4.0 will remove them completely. While it's not strictly necessary as part of a Rails 3.2 upgrade, you can start replacing any plugins by extracting them to gems and adding them to your `Gemfile`. If you choose not to make them gems, you can move them into, say, `lib/my_plugin/*` and add an appropriate initializer in `config/initializers/my_plugin.rb`.
### Active Record
diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md
index b2716c7faa..b9ea4ad47a 100644
--- a/guides/source/working_with_javascript_in_rails.md
+++ b/guides/source/working_with_javascript_in_rails.md
@@ -188,15 +188,20 @@ bind to the `ajax:success` event. On failure, use `ajax:error`. Check it out:
```coffeescript
$(document).ready ->
- $("#new_article").on("ajax:success", (e, data, status, xhr) ->
+ $("#new_article").on("ajax:success", (event) ->
+ [data, status, xhr] = event.detail
$("#new_article").append xhr.responseText
- ).on "ajax:error", (e, xhr, status, error) ->
+ ).on "ajax:error", (event) ->
$("#new_article").append "<p>ERROR</p>"
```
Obviously, you'll want to be a bit more sophisticated than that, but it's a
start.
+NOTE: As of Rails 5.1 and the new `rails-ujs`, the parameters `data, status, xhr`
+have been bundled into `event.detail`. For information about the previously used
+`jquery-ujs` in Rails 5 and earlier, read the [`jquery-ujs` wiki](https://github.com/rails/jquery-ujs/wiki/ajax).
+
#### link_to
[`link_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to)
@@ -225,7 +230,7 @@ and write some CoffeeScript like this:
```coffeescript
$ ->
- $("a[data-remote]").on "ajax:success", (e, data, status, xhr) ->
+ $("a[data-remote]").on "ajax:success", (event) ->
alert "The article was deleted."
```
@@ -343,39 +348,6 @@ This generates a form with:
<input data-disable-with="Saving..." type="submit">
```
-Dealing with Ajax events
-------------------------
-
-Here are the different events that are fired when you deal with elements
-that have a `data-remote` attribute:
-
-NOTE: All handlers bound to these events are always passed the event object as the
-first argument. The table below describes the extra parameters passed after the
-event argument. For example, if the extra parameters are listed as `xhr, settings`,
-then to access them, you would define your handler with `function(event, xhr, settings)`.
-
-| Event name | Extra parameters | Fired |
-|---------------------|------------------|-------------------------------------------------------------|
-| `ajax:before` | | Before the whole ajax business, aborts if stopped. |
-| `ajax:beforeSend` | xhr, options | Before the request is sent, aborts if stopped. |
-| `ajax:send` | xhr | When the request is sent. |
-| `ajax:success` | xhr, status, err | After completion, if the response was a success. |
-| `ajax:error` | xhr, status, err | After completion, if the response was an error. |
-| `ajax:complete` | xhr, status | After the request has been completed, no matter the outcome.|
-| `ajax:aborted:file` | elements | If there are non-blank file inputs, aborts if stopped. |
-
-### Stoppable events
-
-If you stop `ajax:before` or `ajax:beforeSend` by returning false from the
-handler method, the Ajax request will never take place. The `ajax:before` event
-is also useful for manipulating form data before serialization. The
-`ajax:beforeSend` event is also useful for adding custom request headers.
-
-If you stop the `ajax:aborted:file` event, the default behavior of allowing the
-browser to submit the form via normal means (i.e. non-Ajax submission) will be
-canceled and the form will not be submitted at all. This is useful for
-implementing your own Ajax file upload workaround.
-
### Rails-ujs event handlers
Rails 5.1 introduced rails-ujs and dropped jQuery as a dependency.
@@ -401,10 +373,26 @@ Example usage:
```html
document.body.addEventListener('ajax:success', function(event) {
var detail = event.detail;
- var data = detail[0], status = detail[1], xhr = detail[2];
+ var data = detail[0], status = detail[1], xhr = detail[2];
})
```
+NOTE: As of Rails 5.1 and the new `rails-ujs`, the parameters `data, status, xhr`
+have been bundled into `event.detail`. For information about the previously used
+`jquery-ujs` in Rails 5 and earlier, read the [`jquery-ujs` wiki](https://github.com/rails/jquery-ujs/wiki/ajax).
+
+### Stoppable events
+
+If you stop `ajax:before` or `ajax:beforeSend` by returning false from the
+handler method, the Ajax request will never take place. The `ajax:before` event
+can manipulate form data before serialization and the
+`ajax:beforeSend` event is useful for adding custom request headers.
+
+If you stop the `ajax:aborted:file` event, the default behavior of allowing the
+browser to submit the form via normal means (i.e. non-Ajax submission) will be
+canceled and the form will not be submitted at all. This is useful for
+implementing your own Ajax file upload workaround.
+
Server-Side Concerns
--------------------
@@ -504,7 +492,7 @@ replace the entire `<body>` of the page with the `<body>` of the response. It
will then use PushState to change the URL to the correct one, preserving
refresh semantics and giving you pretty URLs.
-The only thing you have to do to enable Turbolinks is have it in your Gemfile,
+The only thing you have to do to enable Turbolinks is have it in your `Gemfile`,
and put `//= require turbolinks` in your JavaScript manifest, which is usually
`app/assets/javascripts/application.js`.
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index e69a1231b1..b3b35307e3 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,153 +1,3 @@
-* `rails new` and `rails plugin new` get `Active Storage` by default.
- Add ability to skip `Active Storage` with `--skip-active-storage`
- and do so automatically when `--skip-active-record` is used.
- *bogdanvlviv*
-* 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*
-
-* Add `--skip-yarn` option to the plugin generator.
-
- *bogdanvlviv*
-
-* Optimize routes indentation.
-
- *Yoshiyuki Hirano*
-
-* Optimize indentation for generator actions.
-
- *Yoshiyuki Hirano*
-
-* Skip unused components when running `bin/rails` in Rails plugin.
-
- *Yoshiyuki Hirano*
-
-* Add `git_source` to `Gemfile` for plugin generator.
-
- *Yoshiyuki Hirano*
-
-* Add `--skip-action-cable` option to the plugin generator.
-
- *bogdanvlviv*
-
-* Deprecate support of use `Rails::Application` subclass to start Rails server.
-
- *Yuji Yaginuma*
-
-* Add `ruby x.x.x` version to `Gemfile` and create `.ruby-version`
- root file containing the current Ruby version when new Rails applications are
- created.
-
- *Alberto Almagro*
-
-* Support `-` as a platform-agnostic way to run a script from stdin with
- `rails runner`
-
- *Cody Cutrer*
-
-* Add `bootsnap` to default `Gemfile`.
-
- *Burke Libbey*
-
-* Properly expand shortcuts for environment's name running the `console`
- and `dbconsole` commands.
-
- *Robin Dupret*
-
-* Passing the environment's name as a regular argument to the
- `rails dbconsole` and `rails console` commands is deprecated.
- The `-e` option should be used instead.
-
- Previously:
-
- $ bin/rails dbconsole production
-
- Now:
-
- $ bin/rails dbconsole -e production
-
- *Robin Dupret*, *Kasper Timm Hansen*
-
-* Allow passing a custom connection name to the `rails dbconsole`
- command when using a 3-level database configuration.
-
- $ bin/rails dbconsole -c replica
-
- *Robin Dupret*, *Jeremy Daer*
-
-* Skip unused components when running `bin/rails app:update`.
-
- If the initial app generation skipped Action Cable, Active Record etc.,
- the update task honors those skips too.
-
- *Yuji Yaginuma*
-
-* Make Rails' test runner work better with minitest plugins.
-
- By demoting the Rails test runner to just another minitest plugin —
- and thereby not eager loading it — we can co-exist much better with
- other minitest plugins such as pride and minitest-focus.
-
- *Kasper Timm Hansen*
-
-* Load environment file in `dbconsole` command.
-
- Fixes #29717.
-
- *Yuji Yaginuma*
-
-* Add `rails secrets:show` command.
-
- *Yuji Yaginuma*
-
-* Allow mounting the same engine several times in different locations.
-
- Fixes #20204.
-
- *David Rodríguez*
-
-* Clear screenshot files in `tmp:clear` task.
-
- *Yuji Yaginuma*
-
-* Add `railtie.rb` to the plugin generator
-
- *Tsukuru Tanimichi*
-
-* Deprecate `capify!` method in generators and templates.
-
- *Yuji Yaginuma*
-
-* Allow irb options to be passed from `rails console` command.
-
- Fixes #28988.
-
- *Yuji Yaginuma*
-
-* Added a shared section to `config/database.yml` that will be loaded for all environments.
-
- *Pierre Schambacher*
-
-* Namespace error pages' CSS selectors to stop the styles from bleeding into other pages
- when using Turbolinks.
-
- *Jan Krutisch*
-
-
-Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/railties/CHANGELOG.md) for previous changes.
+Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/railties/CHANGELOG.md) for previous changes.
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index f9e4444f07..cce00cbc3a 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2017 David Heinemeier Hansson
+Copyright (c) 2004-2018 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index ade8cb6a48..a200a1005c 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -174,8 +174,9 @@ module Rails
# team. Details at https://github.com/rails/rails/pull/6952#issuecomment-7661220
@caching_key_generator ||=
if secret_key_base
- ActiveSupport::CachingKeyGenerator.new \
+ ActiveSupport::CachingKeyGenerator.new(
ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
+ )
else
ActiveSupport::LegacyKeyGenerator.new(secrets.secret_token)
end
@@ -265,7 +266,9 @@ module Rails
"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_rotations" => config.action_dispatch.cookies_rotations
+ "action_dispatch.cookies_rotations" => config.action_dispatch.cookies_rotations,
+ "action_dispatch.content_security_policy" => config.content_security_policy,
+ "action_dispatch.content_security_policy_report_only" => config.content_security_policy_report_only
)
end
end
@@ -400,8 +403,9 @@ module Rails
secrets.secret_token ||= config.secret_token
if secrets.secret_token.present?
- ActiveSupport::Deprecation.warn \
+ ActiveSupport::Deprecation.warn(
"`secrets.secret_token` is deprecated in favor of `secret_key_base` and will be removed in Rails 6.0."
+ )
end
secrets
@@ -424,19 +428,53 @@ module Rails
if Rails.env.test? || Rails.env.development?
Digest::MD5.hexdigest self.class.name
else
- validate_secret_key_base \
+ 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`.
+ # 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 <tt>ENV["RAILS_MASTER_KEY"]</tt> 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"
+ @credentials ||= encrypted("config/credentials.yml.enc")
+ end
+
+ # Shorthand to decrypt any encrypted configurations or files.
+ #
+ # For any file added with <tt>bin/rails encrypted:edit</tt> call +read+ to decrypt
+ # the file with the master key.
+ # The master key is either stored in +config/master.key+ or <tt>ENV["RAILS_MASTER_KEY"]</tt>.
+ #
+ # Rails.application.encrypted("config/mystery_man.txt.enc").read
+ # # => "We've met before, haven't we?"
+ #
+ # It's also possible to interpret encrypted YAML files with +config+.
+ #
+ # Rails.application.encrypted("config/credentials.yml.enc").config
+ # # => { next_guys_line: "I don't think so. Where was it you think we met?" }
+ #
+ # Any top-level configs are also accessible directly on the return value:
+ #
+ # Rails.application.encrypted("config/credentials.yml.enc").next_guys_line
+ # # => "I don't think so. Where was it you think we met?"
+ #
+ # The files or configs can also be encrypted with a custom key. To decrypt with
+ # a key in the +ENV+, use:
+ #
+ # Rails.application.encrypted("config/special_tokens.yml.enc", env_key: "SPECIAL_TOKENS")
+ #
+ # Or to decrypt with a file, that should be version control ignored, relative to +Rails.root+:
+ #
+ # Rails.application.encrypted("config/special_tokens.yml.enc", key_path: "config/special_tokens.key")
+ def encrypted(path, key_path: "config/master.key", env_key: "RAILS_MASTER_KEY")
+ ActiveSupport::EncryptedConfiguration.new(
+ config_path: Rails.root.join(path),
+ key_path: Rails.root.join(key_path),
+ env_key: env_key,
+ raise_if_missing_key: config.require_master_key
+ )
end
def to_app #:nodoc:
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 290ec13878..6743ab2a54 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -16,44 +16,48 @@ module Rails
:ssl_options, :public_file_server,
:session_options, :time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x, :enable_dependency_loading,
- :read_encrypted_secrets, :log_level
+ :read_encrypted_secrets, :log_level, :content_security_policy_report_only,
+ :require_master_key
attr_reader :encoding, :api_only
def initialize(*)
super
- self.encoding = Encoding::UTF_8
- @allow_concurrency = nil
- @consider_all_requests_local = false
- @filter_parameters = []
- @filter_redirect = []
- @helpers_paths = []
- @public_file_server = ActiveSupport::OrderedOptions.new
- @public_file_server.enabled = true
- @public_file_server.index_name = "index"
- @force_ssl = false
- @ssl_options = {}
- @session_store = nil
- @time_zone = "UTC"
- @beginning_of_week = :monday
- @log_level = :debug
- @generators = app_generators
- @cache_store = [ :file_store, "#{root}/tmp/cache/" ]
- @railties_order = [:all]
- @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"]
- @reload_classes_only_on_change = true
- @file_watcher = ActiveSupport::FileUpdateChecker
- @exceptions_app = nil
- @autoflush_log = true
- @log_formatter = ActiveSupport::Logger::SimpleFormatter.new
- @eager_load = nil
- @secret_token = nil
- @secret_key_base = nil
- @api_only = false
- @debug_exception_response_format = nil
- @x = Custom.new
- @enable_dependency_loading = false
- @read_encrypted_secrets = false
+ self.encoding = Encoding::UTF_8
+ @allow_concurrency = nil
+ @consider_all_requests_local = false
+ @filter_parameters = []
+ @filter_redirect = []
+ @helpers_paths = []
+ @public_file_server = ActiveSupport::OrderedOptions.new
+ @public_file_server.enabled = true
+ @public_file_server.index_name = "index"
+ @force_ssl = false
+ @ssl_options = {}
+ @session_store = nil
+ @time_zone = "UTC"
+ @beginning_of_week = :monday
+ @log_level = :debug
+ @generators = app_generators
+ @cache_store = [ :file_store, "#{root}/tmp/cache/" ]
+ @railties_order = [:all]
+ @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"]
+ @reload_classes_only_on_change = true
+ @file_watcher = ActiveSupport::FileUpdateChecker
+ @exceptions_app = nil
+ @autoflush_log = true
+ @log_formatter = ActiveSupport::Logger::SimpleFormatter.new
+ @eager_load = nil
+ @secret_token = nil
+ @secret_key_base = nil
+ @api_only = false
+ @debug_exception_response_format = nil
+ @x = Custom.new
+ @enable_dependency_loading = false
+ @read_encrypted_secrets = false
+ @content_security_policy = nil
+ @content_security_policy_report_only = false
+ @require_master_key = false
end
def load_defaults(target_version)
@@ -71,7 +75,6 @@ module Rails
end
self.ssl_options = { hsts: { subdomains: true } }
-
when "5.1"
load_defaults "5.0"
@@ -82,7 +85,6 @@ module Rails
if respond_to?(:action_view)
action_view.form_with_generates_remote_forms = true
end
-
when "5.2"
load_defaults "5.1"
@@ -100,12 +102,19 @@ module Rails
if respond_to?(:active_support)
active_support.use_authenticated_message_encryption = true
+ active_support.use_sha1_digests = true
end
if respond_to?(:action_controller)
action_controller.default_protect_from_forgery = true
end
+ if respond_to?(:action_view)
+ action_view.form_with_generates_ids = true
+ end
+ when "6.0"
+ load_defaults "5.2"
+
else
raise "Unknown version #{target_version.to_s.inspect}"
end
@@ -228,6 +237,10 @@ module Rails
SourceAnnotationExtractor::Annotation
end
+ def content_security_policy(&block)
+ @content_security_policy ||= ActionDispatch::ContentSecurityPolicy.new(&block)
+ end
+
class Custom #:nodoc:
def initialize
@configurations = Hash.new
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index ea2273c1f2..73c7defe7f 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -63,9 +63,14 @@ module Rails
middleware.use ::ActionDispatch::Flash
end
+ unless config.api_only
+ middleware.use ::ActionDispatch::ContentSecurityPolicy::Middleware
+ end
+
middleware.use ::Rack::Head
middleware.use ::Rack::ConditionalGet
middleware.use ::Rack::ETag, "no-cache"
+ middleware.use ::Rack::TempfileReaper
end
end
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 3d938be951..c4b188aeee 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -58,7 +58,7 @@ module Rails
end
# This needs to happen before eager load so it happens
- # in exactly the same point regardless of config.cache_classes
+ # in exactly the same point regardless of config.eager_load
initializer :run_prepare_callbacks do |app|
app.reloader.prepare!
end
diff --git a/railties/lib/rails/command/helpers/editor.rb b/railties/lib/rails/command/helpers/editor.rb
new file mode 100644
index 0000000000..6191d97672
--- /dev/null
+++ b/railties/lib/rails/command/helpers/editor.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require "active_support/encrypted_file"
+
+module Rails
+ module Command
+ module Helpers
+ module Editor
+ private
+ def ensure_editor_available(command:)
+ if ENV["EDITOR"].to_s.empty?
+ say "No $EDITOR to open file in. Assign one like this:"
+ say ""
+ say %(EDITOR="mate --wait" #{command})
+ 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 catch_editing_exceptions
+ yield
+ rescue Interrupt
+ say "Aborted changing file: nothing saved."
+ rescue ActiveSupport::EncryptedFile::MissingKeyError => error
+ say error.message
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/credentials/credentials_command.rb b/railties/lib/rails/commands/credentials/credentials_command.rb
index 1ef7c1f343..fa54c0362a 100644
--- a/railties/lib/rails/commands/credentials/credentials_command.rb
+++ b/railties/lib/rails/commands/credentials/credentials_command.rb
@@ -1,10 +1,13 @@
# frozen_string_literal: true
require "active_support"
+require "rails/command/helpers/editor"
module Rails
module Command
class CredentialsCommand < Rails::Command::Base # :nodoc:
+ include Helpers::Editor
+
no_commands do
def help
say "Usage:\n #{self.class.banner}"
@@ -16,43 +19,27 @@ module Rails
def edit
require_application_and_environment!
- ensure_editor_available || (return)
- ensure_master_key_has_been_added
+ ensure_editor_available(command: "bin/rails credentials:edit") || (return)
+ ensure_master_key_has_been_added if Rails.application.credentials.key.nil?
ensure_credentials_have_been_added
- change_credentials_in_system_editor
+ catch_editing_exceptions do
+ change_credentials_in_system_editor
+ end
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."
+
+ say Rails.application.credentials.read.presence || missing_credentials_message
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
+ master_key_generator.ignore_master_key_file
end
def ensure_credentials_have_been_added
@@ -79,6 +66,14 @@ module Rails
Rails::Generators::CredentialsGenerator.new
end
+
+ def missing_credentials_message
+ if Rails.application.credentials.key.nil?
+ "Missing master key to decrypt credentials. See bin/rails credentials:help"
+ else
+ "No credentials have been added yet. Use bin/rails credentials:edit to change that."
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/commands/encrypted/encrypted_command.rb b/railties/lib/rails/commands/encrypted/encrypted_command.rb
new file mode 100644
index 0000000000..3bc8f76ce4
--- /dev/null
+++ b/railties/lib/rails/commands/encrypted/encrypted_command.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require "pathname"
+require "active_support"
+require "rails/command/helpers/editor"
+
+module Rails
+ module Command
+ class EncryptedCommand < Rails::Command::Base # :nodoc:
+ include Helpers::Editor
+
+ class_option :key, aliases: "-k", type: :string,
+ default: "config/master.key", desc: "The Rails.root relative path to the encryption key"
+
+ no_commands do
+ def help
+ say "Usage:\n #{self.class.banner}"
+ say ""
+ end
+ end
+
+ def edit(file_path)
+ require_application_and_environment!
+ encrypted = Rails.application.encrypted(file_path, key_path: options[:key])
+
+ ensure_editor_available(command: "bin/rails encrypted:edit") || (return)
+ ensure_encryption_key_has_been_added(options[:key]) if encrypted.key.nil?
+ ensure_encrypted_file_has_been_added(file_path, options[:key])
+
+ catch_editing_exceptions do
+ change_encrypted_file_in_system_editor(file_path, options[:key])
+ end
+
+ say "File encrypted and saved."
+ rescue ActiveSupport::MessageEncryptor::InvalidMessage
+ say "Couldn't decrypt #{file_path}. Perhaps you passed the wrong key?"
+ end
+
+ def show(file_path)
+ require_application_and_environment!
+ encrypted = Rails.application.encrypted(file_path, key_path: options[:key])
+
+ say encrypted.read.presence || missing_encrypted_message(key: encrypted.key, key_path: options[:key], file_path: file_path)
+ end
+
+ private
+ def ensure_encryption_key_has_been_added(key_path)
+ encryption_key_file_generator.add_key_file(key_path)
+ encryption_key_file_generator.ignore_key_file(key_path)
+ end
+
+ def ensure_encrypted_file_has_been_added(file_path, key_path)
+ encrypted_file_generator.add_encrypted_file_silently(file_path, key_path)
+ end
+
+ def change_encrypted_file_in_system_editor(file_path, key_path)
+ Rails.application.encrypted(file_path, key_path: key_path).change do |tmp_path|
+ system("#{ENV["EDITOR"]} #{tmp_path}")
+ end
+ end
+
+
+ def encryption_key_file_generator
+ require "rails/generators"
+ require "rails/generators/rails/encryption_key_file/encryption_key_file_generator"
+
+ Rails::Generators::EncryptionKeyFileGenerator.new
+ end
+
+ def encrypted_file_generator
+ require "rails/generators"
+ require "rails/generators/rails/encrypted_file/encrypted_file_generator"
+
+ Rails::Generators::EncryptedFileGenerator.new
+ end
+
+ def missing_encrypted_message(key:, key_path:, file_path:)
+ if key.nil?
+ "Missing '#{key_path}' to decrypt data. See bin/rails encrypted:help"
+ else
+ "File '#{file_path}' does not exist. Use bin/rails encrypted:edit #{file_path} to change that."
+ end
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/commands/secrets/secrets_command.rb b/railties/lib/rails/commands/secrets/secrets_command.rb
index c91139e33b..a36ccf314c 100644
--- a/railties/lib/rails/commands/secrets/secrets_command.rb
+++ b/railties/lib/rails/commands/secrets/secrets_command.rb
@@ -15,7 +15,7 @@ module Rails
end
def setup
- generator.start
+ deprecate_in_favor_of_credentials_and_exit
end
def edit
@@ -42,11 +42,10 @@ module Rails
rescue Rails::Secrets::MissingKeyError => error
say error.message
rescue Errno::ENOENT => error
- raise unless error.message =~ /secrets\.yml\.enc/
-
- Rails::Secrets.read_template_for_editing do |tmp_path|
- system("#{ENV["EDITOR"]} #{tmp_path}")
- generator.skip_secrets_file { setup }
+ if error.message =~ /secrets\.yml\.enc/
+ deprecate_in_favor_of_credentials_and_exit
+ else
+ raise
end
end
@@ -55,11 +54,11 @@ module Rails
end
private
- def generator
- require "rails/generators"
- require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
+ def deprecate_in_favor_of_credentials_and_exit
+ say "Encrypted secrets is deprecated in favor of credentials. Run:"
+ say "bin/rails credentials:help"
- Rails::Generators::EncryptedSecretsGenerator
+ exit 1
end
end
end
diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb
index 703ec59087..e546fe3e4b 100644
--- a/railties/lib/rails/commands/server/server_command.rb
+++ b/railties/lib/rails/commands/server/server_command.rb
@@ -27,7 +27,7 @@ module Rails
app = super
if app.is_a?(Class)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Use `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0.
+ Using `Rails::Application` subclass to start the server is deprecated and will be removed in Rails 6.0.
Please change `run #{app}` to `run Rails.application` in config.ru.
MSG
end
diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb
index 92b5e0392a..54bfbdd516 100644
--- a/railties/lib/rails/gem_version.rb
+++ b/railties/lib/rails/gem_version.rb
@@ -7,8 +7,8 @@ module Rails
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 5592e8d78e..6a7175c02b 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -218,6 +218,9 @@ module Rails
rails.delete("app")
rails.delete("plugin")
rails.delete("encrypted_secrets")
+ rails.delete("encrypted_file")
+ rails.delete("encryption_key_file")
+ rails.delete("master_key")
rails.delete("credentials")
hidden_namespaces.each { |n| groups.delete(n.to_s) }
@@ -274,8 +277,9 @@ module Rails
else
options = sorted_groups.flat_map(&:last)
suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3)
+ suggestions.map! { |s| "'#{s}'" }
msg = "Could not find generator '#{namespace}'. ".dup
- msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n"
+ msg << "Maybe you meant #{ suggestions[0...-1].join(', ')} or #{suggestions[-1]}\n"
msg << "Run `rails generate --help` for more options."
puts msg
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index bdeddff645..863a914912 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -84,6 +84,9 @@ module Rails
class_option :skip_system_test, type: :boolean, default: false,
desc: "Skip system test files"
+ class_option :skip_bootsnap, type: :boolean, default: false,
+ desc: "Skip bootsnap gem"
+
class_option :dev, type: :boolean, default: false,
desc: "Setup the #{name} with Gemfile pointing to your Rails checkout"
@@ -192,7 +195,7 @@ module Rails
def webserver_gemfile_entry # :doc:
return [] if options[:skip_puma]
comment = "Use Puma as the app server"
- GemfileEntry.new("puma", "~> 3.7", comment)
+ GemfileEntry.new("puma", "~> 3.11", comment)
end
def include_all_railties? # :doc:
@@ -266,17 +269,14 @@ module Rails
end
def rails_gemfile_entry
- dev_edge_common = [
- GemfileEntry.github("arel", "rails/arel"),
- ]
if options.dev?
[
GemfileEntry.path("rails", Rails::Generators::RAILS_DEV_PATH)
- ] + dev_edge_common
+ ]
elsif options.edge?
[
GemfileEntry.github("rails", "rails/rails")
- ] + dev_edge_common
+ ]
else
[GemfileEntry.version("rails",
rails_version_specifier,
@@ -300,8 +300,8 @@ module Rails
def gem_for_database
# %w( mysql postgresql sqlite3 oracle frontbase ibm_db sqlserver jdbcmysql jdbcsqlite3 jdbcpostgresql )
case options[:database]
- when "mysql" then ["mysql2", [">= 0.3.18", "< 0.5"]]
- when "postgresql" then ["pg", ["~> 0.18"]]
+ when "mysql" then ["mysql2", ["~> 0.4.4"]]
+ when "postgresql" then ["pg", [">= 0.18", "< 2.0"]]
when "oracle" then ["activerecord-oracle_enhanced-adapter", nil]
when "frontbase" then ["ruby-frontbase", nil]
when "sqlserver" then ["activerecord-sqlserver-adapter", nil]
@@ -315,11 +315,13 @@ module Rails
def convert_database_option_for_jruby
if defined?(JRUBY_VERSION)
- case options[:database]
- when "postgresql" then options[:database].replace "jdbcpostgresql"
- when "mysql" then options[:database].replace "jdbcmysql"
- when "sqlite3" then options[:database].replace "jdbcsqlite3"
+ opt = options.dup
+ case opt[:database]
+ when "postgresql" then opt[:database] = "jdbcpostgresql"
+ when "mysql" then opt[:database] = "jdbcmysql"
+ when "sqlite3" then opt[:database] = "jdbcsqlite3"
end
+ self.options = opt.freeze
end
end
@@ -438,6 +440,10 @@ module Rails
!options[:skip_listen] && os_supports_listen_out_of_the_box?
end
+ def depend_on_bootsnap?
+ !options[:skip_bootsnap] && !options[:dev]
+ end
+
def os_supports_listen_out_of_the_box?
RbConfig::CONFIG["host_os"] =~ /darwin|linux/
end
@@ -459,16 +465,6 @@ module Rails
end
end
- def run_active_storage
- unless skip_active_storage?
- if bundle_install?
- rails_command "active_storage:install", capture: options[:quiet]
- else
- log("Active Storage installation was skipped. Please run `bin/rails active_storage:install` to install Active Storage files.")
- end
- end
- end
-
def empty_directory_with_keep_file(destination, config = {})
empty_directory(destination, config)
keep_file(destination)
diff --git a/railties/lib/rails/generators/erb/controller/templates/view.html.erb b/railties/lib/rails/generators/erb/controller/templates/view.html.erb.tt
index cd54d13d83..cd54d13d83 100644
--- a/railties/lib/rails/generators/erb/controller/templates/view.html.erb
+++ b/railties/lib/rails/generators/erb/controller/templates/view.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb.tt
index b5045671b3..b5045671b3 100644
--- a/railties/lib/rails/generators/erb/mailer/templates/view.html.erb
+++ b/railties/lib/rails/generators/erb/mailer/templates/view.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb.tt
index 342285df19..342285df19 100644
--- a/railties/lib/rails/generators/erb/mailer/templates/view.text.erb
+++ b/railties/lib/rails/generators/erb/mailer/templates/view.text.erb.tt
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt
index 0eb9d82bbb..518cb1121e 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt
@@ -15,15 +15,15 @@
<div class="field">
<% if attribute.password_digest? -%>
<%%= form.label :password %>
- <%%= form.password_field :password, id: :<%= field_id(:password) %> %>
+ <%%= form.password_field :password %>
</div>
<div class="field">
<%%= form.label :password_confirmation %>
- <%%= form.password_field :password_confirmation, id: :<%= field_id(:password_confirmation) %> %>
+ <%%= form.password_field :password_confirmation %>
<% else -%>
<%%= form.label :<%= attribute.column_name %> %>
- <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, id: :<%= field_id(attribute.column_name) %> %>
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %> %>
<% end -%>
</div>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb.tt
index 81329473d9..81329473d9 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt
index e1ede7c713..e1ede7c713 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb.tt
index 9b2b2f4875..9b2b2f4875 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb.tt
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt
index 5e634153be..5e634153be 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 2728459968..f7fd30a5fb 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -75,7 +75,7 @@ module Rails
when :date then :date_select
when :text then :text_area
when :boolean then :check_box
- else
+ else
:text_field
end
end
@@ -91,7 +91,7 @@ module Rails
when :text then "MyText"
when :boolean then false
when :references, :belongs_to then nil
- else
+ else
""
end
end
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 99165168fd..98fcc95964 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -114,10 +114,6 @@ module Rails
"new_#{singular_route_name}_url"
end
- def field_id(attribute_name)
- [singular_table_name, attribute_name].join("_")
- end
-
def singular_table_name # :doc:
@singular_table_name ||= (pluralize_table_names? ? table_name.singularize : table_name)
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 1c32fad3ea..fd9da7803f 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -128,6 +128,7 @@ module Rails
active_storage_config_exist = File.exist?("config/storage.yml")
rack_cors_config_exist = File.exist?("config/initializers/cors.rb")
assets_config_exist = File.exist?("config/initializers/assets.rb")
+ csp_config_exist = File.exist?("config/initializers/content_security_policy.rb")
config
@@ -155,27 +156,27 @@ module Rails
unless assets_config_exist
remove_file "config/initializers/assets.rb"
end
+
+ unless csp_config_exist
+ remove_file "config/initializers/content_security_policy.rb"
+ end
end
end
def master_key
- return if options[:pretend]
+ return if options[:pretend] || options[:dummy_app]
require "rails/generators/rails/master_key/master_key_generator"
-
- after_bundle do
- Rails::Generators::MasterKeyGenerator.new([], quiet: options[:quiet]).add_master_key_file
- end
+ master_key_generator = Rails::Generators::MasterKeyGenerator.new([], quiet: options[:quiet])
+ master_key_generator.add_master_key_file_silently
+ master_key_generator.ignore_master_key_file_silently
end
def credentials
- return if options[:pretend]
+ return if options[:pretend] || options[:dummy_app]
require "rails/generators/rails/credentials/credentials_generator"
-
- after_bundle do
- Rails::Generators::CredentialsGenerator.new([], quiet: options[:quiet]).add_credentials_file_silently
- end
+ Rails::Generators::CredentialsGenerator.new([], quiet: options[:quiet]).add_credentials_file_silently
end
def database_yml
@@ -347,6 +348,14 @@ module Rails
build(:public_directory)
end
+ def create_tmp_files
+ build(:tmp)
+ end
+
+ def create_vendor_files
+ build(:vendor)
+ end
+
def create_test_files
build(:test) unless options[:skip_test]
end
@@ -359,14 +368,6 @@ module Rails
build(:storage) unless skip_active_storage?
end
- def create_tmp_files
- build(:tmp)
- end
-
- def create_vendor_files
- build(:vendor)
- end
-
def delete_app_assets_if_api_option
if options[:api]
remove_dir "app/assets"
@@ -436,6 +437,7 @@ module Rails
def delete_non_api_initializers_if_api_option
if options[:api]
remove_file "config/initializers/cookies_serializer.rb"
+ remove_file "config/initializers/content_security_policy.rb"
end
end
@@ -461,7 +463,6 @@ module Rails
public_task :apply_rails_template, :run_bundle
public_task :run_webpack, :generate_spring_binstubs
- public_task :run_active_storage
def run_after_bundle_callbacks
@after_bundle_callbacks.each(&:call)
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
index 61026f5182..23bb89f4ce 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile.tt
@@ -29,9 +29,11 @@ ruby <%= "'#{RUBY_VERSION}'" -%>
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
+<% if depend_on_bootsnap? -%>
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false
+<%- end -%>
<%- if options.api? -%>
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'
@@ -41,13 +43,6 @@ gem 'bootsnap', '>= 1.1.0', require: false
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
- <%- if depends_on_system_test? -%>
- # Adds support for Capybara system testing and selenium driver
- gem 'capybara', '~> 2.15'
- gem 'selenium-webdriver'
- # Easy installation and use of chromedriver to run system tests with Chrome
- gem 'chromedriver-helper'
- <%- end -%>
end
group :development do
@@ -70,6 +65,16 @@ group :development do
<% end -%>
<% end -%>
end
+
+<%- if depends_on_system_test? -%>
+group :test do
+ # Adds support for Capybara system testing and selenium driver
+ gem 'capybara', '~> 2.15'
+ gem 'selenium-webdriver'
+ # Easy installation and use of chromedriver to run system tests with Chrome
+ gem 'chromedriver-helper'
+end
+<%- end -%>
<% end -%>
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
diff --git a/railties/lib/rails/generators/rails/app/templates/README.md b/railties/lib/rails/generators/rails/app/templates/README.md.tt
index 7db80e4ca1..7db80e4ca1 100644
--- a/railties/lib/rails/generators/rails/app/templates/README.md
+++ b/railties/lib/rails/generators/rails/app/templates/README.md.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile.tt
index e85f913914..e85f913914 100644
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js.tt
index 739aa5f022..739aa5f022 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/cable.js.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css.tt
index d05ea0f511..d05ea0f511 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css
+++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb.tt
index d672697283..d672697283 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/channel.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb.tt
index 0ff5442f47..0ff5442f47 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/channels/application_cable/connection.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb.tt
index de6be7945c..de6be7945c 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/helpers/application_helper.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb b/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb.tt
index a009ace51c..a009ace51c 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/jobs/application_job.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb b/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb.tt
index 286b2239d1..286b2239d1 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/mailers/application_mailer.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb b/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb.tt
index 10a4cba84d..10a4cba84d 100644
--- a/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb
+++ b/railties/lib/rails/generators/rails/app/templates/app/models/application_record.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/bundle b/railties/lib/rails/generators/rails/app/templates/bin/bundle.tt
index a84f0afe47..a84f0afe47 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/bundle
+++ b/railties/lib/rails/generators/rails/app/templates/bin/bundle.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rails b/railties/lib/rails/generators/rails/app/templates/bin/rails.tt
index 513a2e0183..513a2e0183 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/rails
+++ b/railties/lib/rails/generators/rails/app/templates/bin/rails.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/rake b/railties/lib/rails/generators/rails/app/templates/bin/rake.tt
index d14fc8395b..d14fc8395b 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/rake
+++ b/railties/lib/rails/generators/rails/app/templates/bin/rake.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn b/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt
index b4e4d95286..90ddcc520e 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/yarn
+++ b/railties/lib/rails/generators/rails/app/templates/bin/yarn.tt
@@ -1,7 +1,7 @@
APP_ROOT = File.expand_path('..', __dir__)
Dir.chdir(APP_ROOT) do
begin
- exec "yarnpkg #{ARGV.join(' ')}"
+ exec "yarnpkg", *ARGV
rescue Errno::ENOENT
$stderr.puts "Yarn executable was not detected in the system."
$stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru.tt
index f7ba0b527b..f7ba0b527b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config.ru
+++ b/railties/lib/rails/generators/rails/app/templates/config.ru.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt
index 9e03e86771..d1a09f9c3c 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb.tt
@@ -27,8 +27,9 @@ module <%= app_const_base %>
config.load_defaults <%= Rails::VERSION::STRING.to_f %>
# Settings in config/environments/* take precedence over those specified here.
- # Application configuration should go into files in config/initializers
- # -- all .rb files in that directory are automatically loaded.
+ # Application configuration can go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded after loading
+ # the framework and any gems in your application.
<%- if options.api? -%>
# Only loads a smaller set of middleware suitable for API only apps.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt
index b9e460cef3..42d46b8175 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb.tt
@@ -1,4 +1,6 @@
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
require 'bundler/setup' # Set up gems listed in the Gemfile.
+<% if depend_on_bootsnap? -%>
require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
+<%- end -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml.tt
index 8e53156c71..8e53156c71 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/cable.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt
index 917b52e535..917b52e535 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt
index d40117a27f..d40117a27f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt
index 563be77710..563be77710 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt
index 2a67bdca25..2a67bdca25 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt
index 70df04079d..70df04079d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt
index 371415e6a8..371415e6a8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
index 04afaa0596..04afaa0596 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt
index 6da0601b24..6da0601b24 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
index 145cfb7f74..145cfb7f74 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt
index 9510568124..9510568124 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt
index 049de65f22..049de65f22 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb.tt
index 426333bb46..426333bb46 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index b383228dc0..a87649b50f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -14,7 +14,7 @@ Rails.application.configure do
# Enable/disable caching. By default caching is disabled.
# Run rails dev:cache to toggle caching.
- if Rails.root.join('tmp/caching-dev.txt').exist?
+ if Rails.root.join('tmp', 'caching-dev.txt').exist?
config.action_controller.perform_caching = true
config.cache_store = :memory_store
@@ -46,6 +46,9 @@ Rails.application.configure do
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
+ # Highlight code that triggered database queries in logs.
+ config.active_record.verbose_query_logs = true
+
<%- end -%>
<%- unless options.skip_sprockets? -%>
# Debug mode disables concatenation and preprocessing of assets.
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 4c0f36db98..926326b5bb 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
@@ -34,8 +34,6 @@ Rails.application.configure do
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
- # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
-
<%- end -%>
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb.tt
index 89d2efab2b..89d2efab2b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/application_controller_renderer.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb.tt
index 59385cdf37..59385cdf37 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/backtrace_silencers.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt
new file mode 100644
index 0000000000..edde7f42b8
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt
@@ -0,0 +1,22 @@
+# Be sure to restart your server when you modify this file.
+
+# Define an application-wide content security policy
+# For further information see the following documentation
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+
+# Rails.application.config.content_security_policy do |policy|
+# policy.default_src :self, :https
+# policy.font_src :self, :https, :data
+# policy.img_src :self, :https, :data
+# policy.object_src :none
+# policy.script_src :self, :https
+# policy.style_src :self, :https, :unsafe_inline
+
+# # Specify URI for violation reports
+# # policy.report_uri "/csp-violation-report-endpoint"
+# end
+
+# Report CSP violations to a specified URI
+# For further information see the following documentation:
+# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
+# Rails.application.config.content_security_policy_report_only = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb.tt
index 5a6a32d371..5a6a32d371 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cookies_serializer.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt
index 3b1c1b5ed1..3b1c1b5ed1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt
index 4a994e1e7b..4a994e1e7b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb.tt
index ac033bf9dc..ac033bf9dc 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb.tt
index dc1899682b..dc1899682b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/mime_types.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt
index 25dcddb27a..b4ef455802 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_5_2.rb.tt
@@ -10,7 +10,7 @@
# This is needed for recyclable cache keys.
# Rails.application.config.active_record.cache_versioning = true
-# Use AES 256 GCM authenticated encryption for encrypted cookies.
+# Use AES-256-GCM authenticated encryption for encrypted cookies.
# Existing cookies will be converted on read then written with the new scheme.
# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true
@@ -25,3 +25,6 @@
# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and
# 'f' after migrating old data.
# Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
+
+# Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header.
+# Rails.application.config.active_support.use_sha1_digests = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
index 1e19380dcb..a5eccf816b 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb.tt
@@ -26,31 +26,9 @@ environment ENV.fetch("RAILS_ENV") { "development" }
# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
-# process behavior so workers use less memory. If you use this option
-# you need to make sure to reconnect any threads in the `on_worker_boot`
-# block.
+# process behavior so workers use less memory.
#
# preload_app!
-# If you are preloading your application and using Active Record, it's
-# recommended that you close any connections to the database before workers
-# are forked to prevent connection leakage.
-#
-# before_fork do
-# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
-# end
-
-# The code in the `on_worker_boot` will be called if you are using
-# clustered mode by specifying a number of `workers`. After each worker
-# process is booted, this block will be run. If you are using the `preload_app!`
-# option, you will want to use this block to reconnect to any threads
-# or connections that may have been created at application boot, as Ruby
-# cannot share connections between processes.
-#
-# on_worker_boot do
-# ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
-# end
-#
-
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt
index 787824f888..787824f888 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/spring.rb b/railties/lib/rails/generators/rails/app/templates/config/spring.rb.tt
index 9fa7863f99..9fa7863f99 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/spring.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/spring.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/config/storage.yml b/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt
index 9bada4b66d..1c0cde0b09 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/storage.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/storage.yml.tt
@@ -18,7 +18,7 @@ local:
# google:
# service: GCS
# project: your_project
-# keyfile: <%%= Rails.root.join("path/to/gcs.keyfile") %>
+# credentials: <%%= Rails.root.join("path/to/gcs.keyfile") %>
# bucket: your_own_bucket
# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore.tt
index 2cd8335aba..2cd8335aba 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/package.json b/railties/lib/rails/generators/rails/app/templates/package.json.tt
index 46db57dcbe..46db57dcbe 100644
--- a/railties/lib/rails/generators/rails/app/templates/package.json
+++ b/railties/lib/rails/generators/rails/app/templates/package.json.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/ruby-version b/railties/lib/rails/generators/rails/app/templates/ruby-version.tt
index c444f33b0f..c444f33b0f 100644
--- a/railties/lib/rails/generators/rails/app/templates/ruby-version
+++ b/railties/lib/rails/generators/rails/app/templates/ruby-version.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb b/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt
index d19212abd5..d19212abd5 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb.tt
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
index 6ad1f11781..52d68cc77c 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
@@ -1,3 +1,4 @@
+ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
diff --git a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css
index 7594abf268..afad32db02 100644
--- a/railties/lib/rails/generators/rails/assets/templates/stylesheet.css
+++ b/railties/lib/rails/generators/rails/assets/templates/stylesheet.css
@@ -1,4 +1,4 @@
-/*
+/*
Place all the styles related to the matching controller here.
They will automatically be included in application.css.
*/
diff --git a/railties/lib/rails/generators/rails/controller/templates/controller.rb b/railties/lib/rails/generators/rails/controller/templates/controller.rb.tt
index 633e0b3177..633e0b3177 100644
--- a/railties/lib/rails/generators/rails/controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/controller/templates/controller.rb.tt
diff --git a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb
index 52cb4bd8bf..0fb4d5fbd1 100644
--- a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb
+++ b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb
@@ -2,19 +2,17 @@
require "rails/generators/base"
require "rails/generators/rails/master_key/master_key_generator"
+require "active_support/core_ext/string/strip"
require "active_support/encrypted_configuration"
module Rails
module Generators
- class CredentialsGenerator < Base
- CONFIG_PATH = "config/credentials.yml.enc"
- KEY_PATH = "config/master.key"
-
+ class CredentialsGenerator < Base # :nodoc:
def add_credentials_file
- unless File.exist?(CONFIG_PATH)
+ unless credentials.content_path.exist?
template = credentials_template
- say "Adding #{CONFIG_PATH} to store encrypted credentials."
+ say "Adding #{credentials.content_path} to store encrypted credentials."
say ""
say "The following content has been encrypted with the Rails master key:"
say ""
@@ -29,17 +27,30 @@ module Rails
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)
+ unless credentials.content_path.exist?
+ credentials.write(credentials_template)
end
end
private
+ def credentials
+ ActiveSupport::EncryptedConfiguration.new(
+ config_path: "config/credentials.yml.enc",
+ key_path: "config/master.key",
+ env_key: "RAILS_MASTER_KEY",
+ raise_if_missing_key: true
+ )
+ end
+
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)}"
+ <<-YAML.strip_heredoc
+ # aws:
+ # access_key_id: 123
+ # secret_access_key: 345
+
+ # Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
+ secret_key_base: #{SecureRandom.hex(64)}
+ YAML
end
end
end
diff --git a/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb b/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb
new file mode 100644
index 0000000000..ef398f52a1
--- /dev/null
+++ b/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require "rails/generators/base"
+require "active_support/core_ext/string/strip"
+require "active_support/encrypted_file"
+
+module Rails
+ module Generators
+ class EncryptedFileGenerator < Base # :nodoc:
+ def add_encrypted_file_silently(file_path, key_path, template = encrypted_file_template)
+ unless File.exist?(file_path)
+ setup = { content_path: file_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true }
+ ActiveSupport::EncryptedFile.new(setup).write(template)
+ end
+ end
+
+ private
+ def encrypted_file_template
+ <<-YAML.strip_heredoc
+ # aws:
+ # access_key_id: 123
+ # secret_access_key: 345
+
+ YAML
+ 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
deleted file mode 100644
index 1aa7a2622a..0000000000
--- a/railties/lib/rails/generators/rails/encrypted_secrets/encrypted_secrets_generator.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-require "rails/generators/base"
-require "rails/secrets"
-
-module Rails
- module Generators
- class EncryptedSecretsGenerator < Base
- def add_secrets_key_file
- unless File.exist?("config/secrets.yml.key") || File.exist?("config/secrets.yml.enc")
- key = Rails::Secrets.generate_key
-
- say "Adding config/secrets.yml.key to store the encryption key: #{key}"
- say ""
- say "Save this in a password manager your team can access."
- say ""
- say "If you lose the key, no one, including you, can access any encrypted secrets."
-
- say ""
- create_file "config/secrets.yml.key", key
- say ""
- end
- end
-
- def ignore_key_file
- if File.exist?(".gitignore")
- unless File.read(".gitignore").include?(key_ignore)
- say "Ignoring config/secrets.yml.key so it won't end up in Git history:"
- say ""
- append_to_file ".gitignore", key_ignore
- say ""
- end
- else
- say "IMPORTANT: Don't commit config/secrets.yml.key. Add this to your ignore file:"
- say key_ignore, :on_green
- say ""
- end
- end
-
- def add_encrypted_secrets_file
- unless (defined?(@@skip_secrets_file) && @@skip_secrets_file) || File.exist?("config/secrets.yml.enc")
- say "Adding config/secrets.yml.enc to store secrets that needs to be encrypted."
- say ""
- say "For now the file contains this but it's been encrypted with the generated key:"
- say ""
- say Secrets.template, :on_green
- say ""
-
- Secrets.write(Secrets.template)
-
- say "You can edit encrypted secrets with `bin/rails secrets:edit`."
- say ""
- end
-
- say "Add this to your config/environments/production.rb:"
- say "config.read_encrypted_secrets = true"
- end
-
- def self.skip_secrets_file
- @@skip_secrets_file = true
- yield
- ensure
- @@skip_secrets_file = false
- end
-
- private
- def key_ignore
- [ "", "# Ignore encrypted secrets key file.", "config/secrets.yml.key", "" ].join("\n")
- end
- end
- end
-end
diff --git a/railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb b/railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb
new file mode 100644
index 0000000000..90068c678d
--- /dev/null
+++ b/railties/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require "pathname"
+require "rails/generators/base"
+require "active_support/encrypted_file"
+
+module Rails
+ module Generators
+ class EncryptionKeyFileGenerator < Base # :nodoc:
+ def add_key_file(key_path)
+ key_path = Pathname.new(key_path)
+
+ unless key_path.exist?
+ key = ActiveSupport::EncryptedFile.generate_key
+
+ log "Adding #{key_path} to store the 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 ""
+ add_key_file_silently(key_path, key)
+ log ""
+ end
+ end
+
+ def add_key_file_silently(key_path, key = nil)
+ create_file key_path, key || ActiveSupport::EncryptedFile.generate_key
+ end
+
+ def ignore_key_file(key_path, ignore: key_ignore(key_path))
+ if File.exist?(".gitignore")
+ unless File.read(".gitignore").include?(ignore)
+ log "Ignoring #{key_path} so it won't end up in Git history:"
+ log ""
+ append_to_file ".gitignore", ignore
+ log ""
+ end
+ else
+ log "IMPORTANT: Don't commit #{key_path}. Add this to your ignore file:"
+ log ignore, :on_green
+ log ""
+ end
+ end
+
+ def ignore_key_file_silently(key_path, ignore: key_ignore(key_path))
+ append_to_file ".gitignore", ignore if File.exist?(".gitignore")
+ end
+
+ private
+ def key_ignore(key_path)
+ [ "", "/#{key_path}", "" ].join("\n")
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/helper/templates/helper.rb b/railties/lib/rails/generators/rails/helper/templates/helper.rb.tt
index b4173151b4..b4173151b4 100644
--- a/railties/lib/rails/generators/rails/helper/templates/helper.rb
+++ b/railties/lib/rails/generators/rails/helper/templates/helper.rb.tt
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
index 23dbed20ac..82968985dc 100644
--- a/railties/lib/rails/generators/rails/master_key/master_key_generator.rb
+++ b/railties/lib/rails/generators/rails/master_key/master_key_generator.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
-require "rails/generators/base"
require "pathname"
+require "rails/generators/base"
+require "rails/generators/rails/encryption_key_file/encryption_key_file_generator"
require "active_support/encrypted_file"
module Rails
module Generators
- class MasterKeyGenerator < Base
+ class MasterKeyGenerator < Base # :nodoc:
MASTER_KEY_PATH = Pathname.new("config/master.key")
def add_master_key_file
@@ -20,27 +21,26 @@ module Rails
log "If you lose the key, no one, including you, can access anything encrypted with it."
log ""
- create_file MASTER_KEY_PATH, key
+ add_master_key_file_silently(key)
log ""
-
- ignore_master_key_file
end
end
+ def add_master_key_file_silently(key = nil)
+ key_file_generator.add_key_file_silently(MASTER_KEY_PATH, key)
+ end
+
+ def ignore_master_key_file
+ key_file_generator.ignore_key_file(MASTER_KEY_PATH, ignore: key_ignore)
+ end
+
+ def ignore_master_key_file_silently
+ key_file_generator.ignore_key_file_silently(MASTER_KEY_PATH, ignore: key_ignore)
+ 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
+ def key_file_generator
+ EncryptionKeyFileGenerator.new([], options)
end
def key_ignore
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index bc2dcf008e..a83c911806 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -98,6 +98,7 @@ task default: :test
opts[:skip_listen] = true
opts[:skip_git] = true
opts[:skip_turbolinks] = true
+ opts[:dummy_app] = true
invoke Rails::Generators::AppGenerator,
[ File.expand_path(dummy_path, destination_root) ], opts
@@ -166,7 +167,7 @@ task default: :test
gemfile_in_app_path = File.join(rails_app_path, "Gemfile")
if File.exist? gemfile_in_app_path
- entry = "gem '#{name}', path: '#{relative_path}'"
+ entry = "\ngem '#{name}', path: '#{relative_path}'"
append_file gemfile_in_app_path, entry
end
end
@@ -262,6 +263,10 @@ task default: :test
public_task :apply_rails_template
def run_after_bundle_callbacks
+ unless @after_bundle_callbacks.empty?
+ ActiveSupport::Deprecation.warn("`after_bundle` is deprecated and will be removed in the next version of Rails. ")
+ end
+
@after_bundle_callbacks.each do |callback|
callback.call
end
diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt
index 9a8c4bf098..9a8c4bf098 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Gemfile b/railties/lib/rails/generators/rails/plugin/templates/Gemfile.tt
index 290259b4db..290259b4db 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Gemfile.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt
index ff2fb3ba4e..ff2fb3ba4e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE
+++ b/railties/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/README.md b/railties/lib/rails/generators/rails/plugin/templates/README.md.tt
index 1632409bea..1632409bea 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/README.md
+++ b/railties/lib/rails/generators/rails/plugin/templates/README.md.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/Rakefile b/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt
index f3efe21cf1..f3efe21cf1 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin/templates/Rakefile.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb.tt
index 154452bfe5..154452bfe5 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/config/routes.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore b/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt
index 7a68da5c4b..7a68da5c4b 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/gitignore
+++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb.tt
index 3285055eb7..3285055eb7 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt
index 8938770fc4..8938770fc4 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt
index 7bdf4ee5fb..7bdf4ee5fb 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb.tt
index b08f4ef9ae..b08f4ef9ae 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/version.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake.tt
index 88a2c4120f..88a2c4120f 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake
+++ b/railties/lib/rails/generators/rails/plugin/templates/lib/tasks/%namespaced_name%_tasks.rake.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb.tt
index 06ffe2f1ed..06ffe2f1ed 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb.tt
index c9aef85d40..c9aef85d40 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/boot.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js.tt
index 03937cf8ff..03937cf8ff 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/dummy_manifest.js.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js b/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js.tt
index 2f23844f5e..2f23844f5e 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/engine_manifest.js.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js.tt
index f3d80c87f5..f3d80c87f5 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/javascripts.js.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb.tt
index 694510edc0..694510edc0 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb.tt
index 1ee05d7871..1ee05d7871 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/%namespaced_name%_test.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb b/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt
index d19212abd5..d19212abd5 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb.tt
diff --git a/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb.tt
index 29e59d8407..29e59d8407 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/integration/navigation_test.rb.tt
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.tt
index 7fa9973931..755d19ef5d 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.tt
@@ -1,3 +1,6 @@
+# Configure Rails Environment
+ENV["RAILS_ENV"] = "test"
+
require_relative "<%= File.join('..', options[:dummy_path], 'config/environment') -%>"
<% unless options[:skip_active_record] -%>
ActiveRecord::Migrator.migrations_paths = [File.expand_path("../<%= options[:dummy_path] -%>/db/migrate", __dir__)]
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt
index 400afec6dc..400afec6dc 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb.tt
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt
index 05f1c2b2d3..05f1c2b2d3 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt
diff --git a/railties/lib/rails/generators/rails/task/templates/task.rb b/railties/lib/rails/generators/rails/task/templates/task.rb.tt
index 1e3ed5f158..1e3ed5f158 100644
--- a/railties/lib/rails/generators/rails/task/templates/task.rb
+++ b/railties/lib/rails/generators/rails/task/templates/task.rb.tt
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.tt
index ff41fef9e9..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.tt
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.tt
index a7f1fc4fba..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.tt
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.tt
index 118e0f1271..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.tt
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 9225af4e0c..a99ce914c0 100644
--- a/railties/lib/rails/generators/test_unit/job/job_generator.rb
+++ b/railties/lib/rails/generators/test_unit/job/job_generator.rb
@@ -8,7 +8,7 @@ module TestUnit # :nodoc:
check_class_collision suffix: "JobTest"
def create_test_file
- template "unit_test.rb.erb", File.join("test/jobs", class_path, "#{file_name}_job_test.rb")
+ template "unit_test.rb", File.join("test/jobs", class_path, "#{file_name}_job_test.rb")
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.tt
index f5351d0ec6..f5351d0ec6 100644
--- a/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.erb
+++ b/railties/lib/rails/generators/test_unit/job/templates/unit_test.rb.tt
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.tt
index a2f2d30de5..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.tt
diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb.tt
index b063cbc47b..b063cbc47b 100644
--- a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb
+++ b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb.tt
diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt
index 0681780c97..0681780c97 100644
--- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml
+++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml.tt
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.tt
index c9bc7d5b90..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.tt
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 b6c13b41ae..e2e8b18eab 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -23,7 +23,7 @@ module TestUnit # :nodoc:
template template_file,
File.join("test/controllers", controller_class_path, "#{controller_file_name}_controller_test.rb")
- unless options.api? || options[:system_tests].nil?
+ if !options.api? && options[:system_tests]
template "system_test.rb", File.join("test/system", class_path, "#{file_name.pluralize}_test.rb")
end
end
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.tt
index f21861d8e6..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.tt
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.tt
index 195d60be20..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.tt
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.tt
index f83f5a5c62..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.tt
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.tt
index d19212abd5..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.tt
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.tt
index b5ce2ba5c8..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.tt
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index d8f361f524..d5c9973c6b 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -99,7 +99,7 @@ module Rails
end
property "Database schema version" do
- ActiveRecord::Migrator.current_version rescue nil
+ ActiveRecord::Base.connection.migration_context.current_version rescue nil
end
end
end
diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb
index 66636e5d6b..0b0e802358 100644
--- a/railties/lib/rails/mailers_controller.rb
+++ b/railties/lib/rails/mailers_controller.rb
@@ -6,9 +6,9 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH
before_action :require_local!, unless: :show_previews?
- before_action :find_preview, only: :preview
+ before_action :find_preview, :set_locale, only: :preview
- helper_method :part_query
+ helper_method :part_query, :locale_query
def index
@previews = ActionMailer::Preview.all
@@ -84,4 +84,12 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc:
def part_query(mime_type)
request.query_parameters.merge(part: mime_type).to_query
end
+
+ def locale_query(locale)
+ request.query_parameters.merge(locale: locale).to_query
+ end
+
+ def set_locale
+ I18n.locale = params[:locale] || I18n.default_locale
+ end
end
diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb
index 48853129bc..70274b948c 100644
--- a/railties/lib/rails/railtie/configuration.rb
+++ b/railties/lib/rails/railtie/configuration.rb
@@ -55,7 +55,7 @@ module Rails
ActiveSupport.on_load(:before_configuration, yield: true, &block)
end
- # Third configurable block to run. Does not run if +config.cache_classes+
+ # Third configurable block to run. Does not run if +config.eager_load+
# set to false.
def before_eager_load(&block)
ActiveSupport.on_load(:before_eager_load, yield: true, &block)
diff --git a/railties/lib/rails/secrets.rb b/railties/lib/rails/secrets.rb
index aea72b2d01..30e3478c9b 100644
--- a/railties/lib/rails/secrets.rb
+++ b/railties/lib/rails/secrets.rb
@@ -32,23 +32,10 @@ module Rails
end
end
- def generate_key
- SecureRandom.hex(OpenSSL::Cipher.new(@cipher).key_len)
- end
-
def key
ENV["RAILS_MASTER_KEY"] || read_key_file || handle_missing_key
end
- def template
- <<-end_of_template.strip_heredoc
- # See `secrets.yml` for tips on generating suitable keys.
- # production:
- # external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289
-
- end_of_template
- end
-
def encrypt(data)
encryptor.encrypt_and_sign(data)
end
@@ -70,10 +57,6 @@ module Rails
writing(read, &block)
end
- def read_template_for_editing(&block)
- writing(template, &block)
- end
-
private
def handle_missing_key
raise MissingKeyError
diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake
index c24d1ad532..8d77904210 100644
--- a/railties/lib/rails/tasks/engine.rake
+++ b/railties/lib/rails/tasks/engine.rake
@@ -53,7 +53,7 @@ namespace :db do
desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)."
app_task "rollback"
- desc "Create a db/schema.rb file that can be portably used against any DB supported by Active Record"
+ desc "Create a db/schema.rb file that can be portably used against any database supported by Active Record"
app_task "schema:dump"
desc "Load a schema.rb file into the database"
@@ -62,7 +62,7 @@ namespace :db do
desc "Load the seed data from db/seeds.rb"
app_task "seed"
- desc "Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)"
+ desc "Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the database first)"
app_task "setup"
desc "Dump the database structure to an SQL file"
@@ -70,6 +70,9 @@ namespace :db do
desc "Retrieves the current schema version number"
app_task "version"
+
+ # desc 'Load the test schema'
+ app_task "test:prepare"
end
def find_engine_path(path)
diff --git a/railties/lib/rails/templates/rails/mailers/email.html.erb b/railties/lib/rails/templates/rails/mailers/email.html.erb
index 89c1129f90..2a41c29602 100644
--- a/railties/lib/rails/templates/rails/mailers/email.html.erb
+++ b/railties/lib/rails/templates/rails/mailers/email.html.erb
@@ -95,11 +95,25 @@
</dd>
<% end %>
+ <dt>Format:</dt>
<% if @email.multipart? %>
<dd>
- <select onchange="formatChanged(this);">
- <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="?<%= part_query('text/html') %>">View as HTML email</option>
- <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="?<%= part_query('text/plain') %>">View as plain-text email</option>
+ <select id="part" onchange="refreshBody();">
+ <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="<%= part_query('text/html') %>">View as HTML email</option>
+ <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="<%= part_query('text/plain') %>">View as plain-text email</option>
+ </select>
+ </dd>
+ <% else %>
+ <dd id="mime_type" data-mime-type="<%= part_query(@email.mime_type) %>"><%= @email.mime_type == 'text/html' ? 'HTML email' : 'plain-text email' %></dd>
+ <% end %>
+
+ <% if I18n.available_locales.count > 1 %>
+ <dt>Locale:</dt>
+ <dd>
+ <select id="locale" onchange="refreshBody();">
+ <% I18n.available_locales.each do |locale| %>
+ <option <%= I18n.locale == locale ? 'selected' : '' %> value="<%= locale_query(locale) %>"><%= locale %></option>
+ <% end %>
</select>
</dd>
<% end %>
@@ -116,15 +130,27 @@
<% end %>
<script>
- function formatChanged(form) {
- var part_name = form.options[form.selectedIndex].value
- var iframe =document.getElementsByName('messageBody')[0];
- iframe.contentWindow.location.replace(part_name);
+ function refreshBody() {
+ var part_select = document.querySelector('select#part');
+ var locale_select = document.querySelector('select#locale');
+ var iframe = document.getElementsByName('messageBody')[0];
+ var part_param = part_select ?
+ part_select.options[part_select.selectedIndex].value :
+ document.querySelector('#mime_type').dataset.mimeType;
+ var locale_param = locale_select ? locale_select.options[locale_select.selectedIndex].value : null;
+ var fresh_location;
+ if (locale_param) {
+ fresh_location = '?' + part_param + '&' + locale_param;
+ } else {
+ fresh_location = '?' + part_param;
+ }
+ iframe.contentWindow.location = fresh_location;
if (history.replaceState) {
- var url = location.pathname.replace(/\.(txt|html)$/, '');
- var format = /html/.test(part_name) ? '.html' : '.txt';
- window.history.replaceState({}, '', url + format);
+ var url = location.pathname.replace(/\.(txt|html)$/, '');
+ var format = /html/.test(part_param) ? '.html' : '.txt';
+ var state_to_replace = locale_param ? (url + format + '?' + locale_param) : (url + format);
+ window.history.replaceState({}, '', state_to_replace);
}
}
</script>
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 732c5c1e1f..76c28ac85e 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -29,10 +29,6 @@ if defined?(ActiveRecord::Base)
end
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
-
- def create_fixtures(*fixture_set_names, &block)
- FixtureSet.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, {}, &block)
- end
end
# :enddoc:
diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb
index 7d3164f1eb..28b93cee5a 100644
--- a/railties/lib/rails/test_unit/reporter.rb
+++ b/railties/lib/rails/test_unit/reporter.rb
@@ -64,11 +64,17 @@ module Rails
end
def format_line(result)
- "%s#%s = %.2f s = %s" % [result.class, result.name, result.time, result.result_code]
+ klass = result.respond_to?(:klass) ? result.klass : result.class
+ "%s#%s = %.2f s = %s" % [klass, result.name, result.time, result.result_code]
end
def format_rerun_snippet(result)
- location, line = result.method(result.name).source_location
+ location, line = if result.respond_to?(:source_location)
+ result.source_location
+ else
+ result.method(result.name).source_location
+ end
+
"#{executable} #{relative_path_for(location)}:#{line}"
end
diff --git a/railties/lib/rails/test_unit/runner.rb b/railties/lib/rails/test_unit/runner.rb
index 5c2f6451e2..de5744c662 100644
--- a/railties/lib/rails/test_unit/runner.rb
+++ b/railties/lib/rails/test_unit/runner.rb
@@ -13,7 +13,7 @@ module Rails
class << self
def attach_before_load_options(opts)
opts.on("--warnings", "-w", "Run with Ruby warnings enabled") {}
- opts.on("--environment", "-e", "Run tests in the ENV environment") {}
+ opts.on("-e", "--environment ENV", "Run tests in the ENV environment") {}
end
def parse_options(argv)
diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb
index 05b17b4a7a..608bc2fbe3 100644
--- a/railties/test/application/configuration/custom_test.rb
+++ b/railties/test/application/configuration/custom_test.rb
@@ -33,7 +33,7 @@ module ApplicationTests
assert_nil x.i_do_not_exist.zomg
# test that custom configuration responds to all messages
- assert_equal true, x.respond_to?(:i_do_not_exist)
+ assert_respond_to x, :i_do_not_exist
assert_kind_of Method, x.method(:i_do_not_exist)
assert_kind_of ActiveSupport::OrderedOptions, x.i_do_not_exist
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index bb8cc0876c..236d73d5fd 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -94,7 +94,7 @@ module ApplicationTests
with_rails_env "development" do
app "development"
- assert Rails.application.config.log_tags.blank?
+ assert_predicate Rails.application.config.log_tags, :blank?
end
end
@@ -707,6 +707,14 @@ module ApplicationTests
assert_match(/Missing.*RAILS_MASTER_KEY/, error)
end
+ test "credentials does not raise error when require_master_key is false and master key does not exist" do
+ remove_file "config/master.key"
+ add_to_config "config.require_master_key = false"
+ app "development"
+
+ assert_not app.credentials.secret_key_base
+ end
+
test "protect from forgery is the default in a new app" do
make_basic_app
@@ -757,6 +765,68 @@ module ApplicationTests
assert_match(/label/, last_response.body)
end
+ test "form_with can be configured with form_with_generates_ids" do
+ app_file "config/initializers/form_builder.rb", <<-RUBY
+ Rails.configuration.action_view.form_with_generates_ids = false
+ RUBY
+
+ app_file "app/models/post.rb", <<-RUBY
+ class Post
+ include ActiveModel::Model
+ attr_accessor :name
+ end
+ RUBY
+
+ app_file "app/controllers/posts_controller.rb", <<-RUBY
+ class PostsController < ApplicationController
+ def index
+ render inline: "<%= begin; form_with(model: Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>"
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.prepend do
+ resources :posts
+ end
+ RUBY
+
+ app "development"
+
+ get "/posts"
+
+ assert_no_match(/id=('|")post_name('|")/, last_response.body)
+ end
+
+ test "form_with outputs ids by default" do
+ app_file "app/models/post.rb", <<-RUBY
+ class Post
+ include ActiveModel::Model
+ attr_accessor :name
+ end
+ RUBY
+
+ app_file "app/controllers/posts_controller.rb", <<-RUBY
+ class PostsController < ApplicationController
+ def index
+ render inline: "<%= begin; form_with(model: Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>"
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.prepend do
+ resources :posts
+ end
+ RUBY
+
+ app "development"
+
+ get "/posts"
+
+ assert_match(/id=('|")post_name('|")/, last_response.body)
+ end
+
test "form_with can be configured with form_with_generates_remote_forms" do
app_file "config/initializers/form_builder.rb", <<-RUBY
Rails.configuration.action_view.form_with_generates_remote_forms = false
@@ -1255,7 +1325,7 @@ module ApplicationTests
assert_equal 200, last_response.status
end
- test "config.action_controller.action_on_unpermitted_parameters is :log by default on development" do
+ test "config.action_controller.action_on_unpermitted_parameters is :log by default in development" do
app "development"
force_lazy_load_hooks { ActionController::Base }
@@ -1264,7 +1334,7 @@ module ApplicationTests
assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters
end
- test "config.action_controller.action_on_unpermitted_parameters is :log by default on test" do
+ test "config.action_controller.action_on_unpermitted_parameters is :log by default in test" do
app "test"
force_lazy_load_hooks { ActionController::Base }
@@ -1273,7 +1343,7 @@ module ApplicationTests
assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters
end
- test "config.action_controller.action_on_unpermitted_parameters is false by default on production" do
+ test "config.action_controller.action_on_unpermitted_parameters is false by default in production" do
app "production"
force_lazy_load_hooks { ActionController::Base }
@@ -1349,11 +1419,11 @@ module ApplicationTests
test "Rails.application#env_config exists and include some existing parameters" do
make_basic_app
- assert_equal app.env_config["action_dispatch.parameter_filter"], app.config.filter_parameters
- assert_equal app.env_config["action_dispatch.show_exceptions"], app.config.action_dispatch.show_exceptions
- assert_equal app.env_config["action_dispatch.logger"], Rails.logger
- assert_equal app.env_config["action_dispatch.backtrace_cleaner"], Rails.backtrace_cleaner
- assert_equal app.env_config["action_dispatch.key_generator"], Rails.application.key_generator
+ assert_equal app.env_config["action_dispatch.parameter_filter"], app.config.filter_parameters
+ assert_equal app.env_config["action_dispatch.show_exceptions"], app.config.action_dispatch.show_exceptions
+ assert_equal app.env_config["action_dispatch.logger"], Rails.logger
+ assert_equal app.env_config["action_dispatch.backtrace_cleaner"], Rails.backtrace_cleaner
+ assert_equal app.env_config["action_dispatch.key_generator"], Rails.application.key_generator
end
test "config.colorize_logging default is true" do
@@ -1408,7 +1478,7 @@ module ApplicationTests
test "respond_to? accepts include_private" do
make_basic_app
- assert_not Rails.configuration.respond_to?(:method_missing)
+ assert_not_respond_to Rails.configuration, :method_missing
assert Rails.configuration.respond_to?(:method_missing, true)
end
@@ -1420,12 +1490,18 @@ module ApplicationTests
assert_not ActiveRecord::Base.dump_schema_after_migration
end
- test "config.active_record.dump_schema_after_migration is true by default on development" do
+ test "config.active_record.dump_schema_after_migration is true by default in development" do
app "development"
assert ActiveRecord::Base.dump_schema_after_migration
end
+ test "config.active_record.verbose_query_logs is false by default in development" do
+ app "development"
+
+ assert_not ActiveRecord::Base.verbose_query_logs
+ end
+
test "config.annotations wrapping SourceAnnotationExtractor::Annotation class" do
make_basic_app do |application|
application.config.annotations.register_extensions("coffee") do |tag|
@@ -1663,9 +1739,7 @@ module ApplicationTests
test "default SQLite3Adapter.represent_boolean_as_integer for 5.1 is false" do
remove_from_config '.*config\.load_defaults.*\n'
- add_to_top_of_config <<-RUBY
- config.load_defaults 5.1
- RUBY
+
app_file "app/models/post.rb", <<-RUBY
class Post < ActiveRecord::Base
end
@@ -1757,7 +1831,7 @@ module ApplicationTests
test "api_only is false by default" do
app "development"
- refute Rails.application.config.api_only
+ assert_not Rails.application.config.api_only
end
test "api_only generator config is set when api_only is set" do
@@ -1814,6 +1888,58 @@ module ApplicationTests
assert_equal "https://example.org/", last_response.location
end
+ test "ActiveSupport::MessageEncryptor.use_authenticated_message_encryption is true by default for new apps" do
+ app "development"
+
+ assert_equal true, ActiveSupport::MessageEncryptor.use_authenticated_message_encryption
+ end
+
+ test "ActiveSupport::MessageEncryptor.use_authenticated_message_encryption is false by default for upgraded apps" do
+ remove_from_config '.*config\.load_defaults.*\n'
+
+ app "development"
+
+ assert_equal false, ActiveSupport::MessageEncryptor.use_authenticated_message_encryption
+ end
+
+ test "ActiveSupport::MessageEncryptor.use_authenticated_message_encryption can be configured via config.active_support.use_authenticated_message_encryption" do
+ remove_from_config '.*config\.load_defaults.*\n'
+
+ app_file "config/initializers/new_framework_defaults_5_2.rb", <<-RUBY
+ Rails.application.config.active_support.use_authenticated_message_encryption = true
+ RUBY
+
+ app "development"
+
+ assert_equal true, ActiveSupport::MessageEncryptor.use_authenticated_message_encryption
+ end
+
+ test "ActiveSupport::Digest.hash_digest_class is Digest::SHA1 by default for new apps" do
+ app "development"
+
+ assert_equal Digest::SHA1, ActiveSupport::Digest.hash_digest_class
+ end
+
+ test "ActiveSupport::Digest.hash_digest_class is Digest::MD5 by default for upgraded apps" do
+ remove_from_config '.*config\.load_defaults.*\n'
+
+ app "development"
+
+ assert_equal Digest::MD5, ActiveSupport::Digest.hash_digest_class
+ end
+
+ test "ActiveSupport::Digest.hash_digest_class can be configured via config.active_support.use_sha1_digests" do
+ remove_from_config '.*config\.load_defaults.*\n'
+
+ app_file "config/initializers/new_framework_defaults_5_2.rb", <<-RUBY
+ Rails.application.config.active_support.use_sha1_digests = true
+ RUBY
+
+ app "development"
+
+ assert_equal Digest::SHA1, ActiveSupport::Digest.hash_digest_class
+ end
+
private
def force_lazy_load_hooks
yield # Tasty clarifying sugar, homie! We only need to reference a constant to load it.
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 13164f49c2..4a14042cd3 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -73,7 +73,7 @@ class ConsoleTest < ActiveSupport::TestCase
MODEL
load_environment
- assert User.new.respond_to?(:name)
+ assert_respond_to User.new, :name
app_file "app/models/user.rb", <<-MODEL
class User
@@ -81,9 +81,9 @@ class ConsoleTest < ActiveSupport::TestCase
end
MODEL
- assert !User.new.respond_to?(:age)
+ assert_not_respond_to User.new, :age
irb_context.reload!(false)
- assert User.new.respond_to?(:age)
+ assert_respond_to User.new, :age
end
def test_access_to_helpers
diff --git a/railties/test/application/content_security_policy_test.rb b/railties/test/application/content_security_policy_test.rb
new file mode 100644
index 0000000000..97f2957c33
--- /dev/null
+++ b/railties/test/application/content_security_policy_test.rb
@@ -0,0 +1,197 @@
+# frozen_string_literal: true
+
+require "isolation/abstract_unit"
+require "rack/test"
+
+module ApplicationTests
+ class ContentSecurityPolicyTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "default content security policy is empty" do
+ controller :pages, <<-RUBY
+ class PagesController < ApplicationController
+ def index
+ render html: "<h1>Welcome to Rails!</h1>"
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ root to: "pages#index"
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_equal ";", last_response.headers["Content-Security-Policy"]
+ end
+
+ test "global content security policy in an initializer" do
+ controller :pages, <<-RUBY
+ class PagesController < ApplicationController
+ def index
+ render html: "<h1>Welcome to Rails!</h1>"
+ end
+ end
+ RUBY
+
+ app_file "config/initializers/content_security_policy.rb", <<-RUBY
+ Rails.application.config.content_security_policy do |p|
+ p.default_src :self, :https
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ root to: "pages#index"
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_policy "default-src 'self' https:;"
+ end
+
+ test "global report only content security policy in an initializer" do
+ controller :pages, <<-RUBY
+ class PagesController < ApplicationController
+ def index
+ render html: "<h1>Welcome to Rails!</h1>"
+ end
+ end
+ RUBY
+
+ app_file "config/initializers/content_security_policy.rb", <<-RUBY
+ Rails.application.config.content_security_policy do |p|
+ p.default_src :self, :https
+ end
+
+ Rails.application.config.content_security_policy_report_only = true
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ root to: "pages#index"
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_policy "default-src 'self' https:;", report_only: true
+ end
+
+ test "override content security policy in a controller" do
+ controller :pages, <<-RUBY
+ class PagesController < ApplicationController
+ content_security_policy do |p|
+ p.default_src "https://example.com"
+ end
+
+ def index
+ render html: "<h1>Welcome to Rails!</h1>"
+ end
+ end
+ RUBY
+
+ app_file "config/initializers/content_security_policy.rb", <<-RUBY
+ Rails.application.config.content_security_policy do |p|
+ p.default_src :self, :https
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ root to: "pages#index"
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_policy "default-src https://example.com;"
+ end
+
+ test "override content security policy to report only in a controller" do
+ controller :pages, <<-RUBY
+ class PagesController < ApplicationController
+ content_security_policy_report_only
+
+ def index
+ render html: "<h1>Welcome to Rails!</h1>"
+ end
+ end
+ RUBY
+
+ app_file "config/initializers/content_security_policy.rb", <<-RUBY
+ Rails.application.config.content_security_policy do |p|
+ p.default_src :self, :https
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ root to: "pages#index"
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_policy "default-src 'self' https:;", report_only: true
+ end
+
+ test "global content security policy added to rack app" do
+ app_file "config/initializers/content_security_policy.rb", <<-RUBY
+ Rails.application.config.content_security_policy do |p|
+ p.default_src :self, :https
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+
+ app = ->(env) {
+ [200, { "Content-Type" => "text/html" }, ["<p>Hello, World!</p>"]]
+ }
+
+ root to: app
+ end
+ RUBY
+
+ app("development")
+
+ get "/"
+ assert_policy "default-src 'self' https:;"
+ end
+
+ private
+
+ def assert_policy(expected, report_only: false)
+ assert_equal 200, last_response.status
+
+ if report_only
+ expected_header = "Content-Security-Policy-Report-Only"
+ unexpected_header = "Content-Security-Policy"
+ else
+ expected_header = "Content-Security-Policy"
+ unexpected_header = "Content-Security-Policy-Report-Only"
+ end
+
+ assert_nil last_response.headers[unexpected_header]
+ assert_equal expected, last_response.headers[expected_header]
+ end
+ end
+end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index d2b77bd015..e631318f82 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -266,7 +266,7 @@ module ApplicationTests
ActiveRecord::Base.connection
RUBY
require "#{app_path}/config/environment"
- assert !ActiveRecord::Base.connection_pool.active_connection?
+ assert_not_predicate ActiveRecord::Base.connection_pool, :active_connection?
end
end
end
diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb
index 23b20d578c..c65c955734 100644
--- a/railties/test/application/initializers/notifications_test.rb
+++ b/railties/test/application/initializers/notifications_test.rb
@@ -32,6 +32,7 @@ module ApplicationTests
logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
ActiveRecord::Base.logger = logger
+ ActiveRecord::Base.verbose_query_logs = false
# Mimic Active Record notifications
instrument "sql.active_record", name: "SQL", sql: "SHOW tables"
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index de1e240fd3..2632dd7cde 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -352,11 +352,11 @@ class LoadingTest < ActiveSupport::TestCase
def test_initialize_can_be_called_at_any_time
require "#{app_path}/config/application"
- assert !Rails.initialized?
- assert !Rails.application.initialized?
+ assert_not_predicate Rails, :initialized?
+ assert_not_predicate Rails.application, :initialized?
Rails.initialize!
- assert Rails.initialized?
- assert Rails.application.initialized?
+ assert_predicate Rails, :initialized?
+ assert_predicate Rails.application, :initialized?
end
private
diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb
index 4e77cece1b..ba186bda44 100644
--- a/railties/test/application/mailer_previews_test.rb
+++ b/railties/test/application/mailer_previews_test.rb
@@ -296,7 +296,7 @@ module ApplicationTests
test "mailer preview not found" do
app("development")
get "/rails/mailers/notifier"
- assert last_response.not_found?
+ assert_predicate last_response, :not_found?
assert_match "Mailer preview &#39;notifier&#39; not found", last_response.body
end
@@ -326,7 +326,7 @@ module ApplicationTests
app("development")
get "/rails/mailers/notifier/bar"
- assert last_response.not_found?
+ assert_predicate last_response, :not_found?
assert_match "Email &#39;bar&#39; not found in NotifierPreview", last_response.body
end
@@ -382,7 +382,7 @@ module ApplicationTests
app("development")
get "/rails/mailers/notifier/foo?part=text%2Fhtml"
- assert last_response.not_found?
+ assert_predicate last_response, :not_found?
assert_match "Email part &#39;text/html&#39; not found in NotifierPreview#foo", last_response.body
end
@@ -450,11 +450,67 @@ module ApplicationTests
get "/rails/mailers/notifier/foo.html"
assert_equal 200, last_response.status
- assert_match '<option selected value="?part=text%2Fhtml">View as HTML email</option>', last_response.body
+ assert_match '<option selected value="part=text%2Fhtml">View as HTML email</option>', last_response.body
get "/rails/mailers/notifier/foo.txt"
assert_equal 200, last_response.status
- assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body
+ assert_match '<option selected value="part=text%2Fplain">View as plain-text email</option>', last_response.body
+ end
+
+ test "locale menu selects correct option" do
+ app_file "config/initializers/available_locales.rb", <<-RUBY
+ Rails.application.configure do
+ config.i18n.available_locales = %i[en ja]
+ end
+ RUBY
+
+ mailer "notifier", <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ html_template "notifier/foo", <<-RUBY
+ <p>Hello, World!</p>
+ RUBY
+
+ text_template "notifier/foo", <<-RUBY
+ Hello, World!
+ RUBY
+
+ mailer_preview "notifier", <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app("development")
+
+ get "/rails/mailers/notifier/foo.html"
+ assert_equal 200, last_response.status
+ assert_match '<option selected value="locale=en">en', last_response.body
+ assert_match '<option value="locale=ja">ja', last_response.body
+
+ get "/rails/mailers/notifier/foo.html?locale=ja"
+ assert_equal 200, last_response.status
+ assert_match '<option value="locale=en">en', last_response.body
+ assert_match '<option selected value="locale=ja">ja', last_response.body
+
+ get "/rails/mailers/notifier/foo.txt"
+ assert_equal 200, last_response.status
+ assert_match '<option selected value="locale=en">en', last_response.body
+ assert_match '<option value="locale=ja">ja', last_response.body
+
+ get "/rails/mailers/notifier/foo.txt?locale=ja"
+ assert_equal 200, last_response.status
+ assert_match '<option value="locale=en">en', last_response.body
+ assert_match '<option selected value="locale=ja">ja', last_response.body
end
test "mailer previews create correct links when loaded on a subdirectory" do
@@ -520,8 +576,8 @@ module ApplicationTests
get "/rails/mailers/notifier/foo.txt"
assert_equal 200, last_response.status
assert_match '<iframe seamless name="messageBody" src="?part=text%2Fplain">', last_response.body
- assert_match '<option selected value="?part=text%2Fplain">', last_response.body
- assert_match '<option value="?part=text%2Fhtml">', last_response.body
+ assert_match '<option selected value="part=text%2Fplain">', last_response.body
+ assert_match '<option value="part=text%2Fhtml">', last_response.body
get "/rails/mailers/notifier/foo?part=text%2Fplain"
assert_equal 200, last_response.status
@@ -530,8 +586,8 @@ module ApplicationTests
get "/rails/mailers/notifier/foo.html?name=Ruby"
assert_equal 200, last_response.status
assert_match '<iframe seamless name="messageBody" src="?name=Ruby&amp;part=text%2Fhtml">', last_response.body
- assert_match '<option selected value="?name=Ruby&amp;part=text%2Fhtml">', last_response.body
- assert_match '<option value="?name=Ruby&amp;part=text%2Fplain">', last_response.body
+ assert_match '<option selected value="name=Ruby&amp;part=text%2Fhtml">', last_response.body
+ assert_match '<option value="name=Ruby&amp;part=text%2Fplain">', last_response.body
get "/rails/mailers/notifier/foo?name=Ruby&part=text%2Fhtml"
assert_equal 200, last_response.status
diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb
index 9822ec563d..3768d8ce2d 100644
--- a/railties/test/application/middleware/cache_test.rb
+++ b/railties/test/application/middleware/cache_test.rb
@@ -140,7 +140,7 @@ module ApplicationTests
etag = last_response.headers["ETag"]
get "/expires/expires_etag", { private: true }, { "HTTP_IF_NONE_MATCH" => etag }
- assert_equal "miss", last_response.headers["X-Rack-Cache"]
+ assert_equal "miss", last_response.headers["X-Rack-Cache"]
assert_not_equal body, last_response.body
end
@@ -174,7 +174,7 @@ module ApplicationTests
last = last_response.headers["Last-Modified"]
get "/expires/expires_last_modified", { private: true }, { "HTTP_IF_MODIFIED_SINCE" => last }
- assert_equal "miss", last_response.headers["X-Rack-Cache"]
+ assert_equal "miss", last_response.headers["X-Rack-Cache"]
assert_not_equal body, last_response.body
end
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 0a5a524692..b9e4a9ccc0 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -42,9 +42,11 @@ module ApplicationTests
"ActionDispatch::Cookies",
"ActionDispatch::Session::CookieStore",
"ActionDispatch::Flash",
+ "ActionDispatch::ContentSecurityPolicy::Middleware",
"Rack::Head",
"Rack::ConditionalGet",
- "Rack::ETag"
+ "Rack::ETag",
+ "Rack::TempfileReaper"
], middleware
end
@@ -68,7 +70,8 @@ module ApplicationTests
"ActionDispatch::Callbacks",
"Rack::Head",
"Rack::ConditionalGet",
- "Rack::ETag"
+ "Rack::ETag",
+ "Rack::TempfileReaper"
], middleware
end
@@ -248,7 +251,7 @@ module ApplicationTests
test "can't change middleware after it's built" do
boot!
- assert_raise RuntimeError do
+ assert_raise frozen_error_class do
app.config.middleware.use Rack::Config
end
end
diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb
index e9bc91785c..10d3313f6e 100644
--- a/railties/test/application/per_request_digest_cache_test.rb
+++ b/railties/test/application/per_request_digest_cache_test.rb
@@ -59,7 +59,7 @@ class PerRequestDigestCacheTest < ActiveSupport::TestCase
assert_equal 200, last_response.status
values = ActionView::LookupContext::DetailsKey.digest_caches.first.values
- assert_equal [ "8ba099b7749542fe765ff34a6824d548" ], values
+ assert_equal [ "effc8928d0b33535c8a21d24ec617161" ], values
assert_equal %w(david dingus), last_response.body.split.map(&:strip)
end
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index fd22477539..5b4c42c189 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -26,12 +26,12 @@ module ApplicationTests
FileUtils.rm_rf("#{app_path}/config/database.yml")
end
- def db_create_and_drop(expected_database)
+ def db_create_and_drop(expected_database, environment_loaded: true)
Dir.chdir(app_path) do
output = rails("db:create")
assert_match(/Created database/, output)
assert File.exist?(expected_database)
- assert_equal expected_database, ActiveRecord::Base.connection_config[:database]
+ assert_equal expected_database, ActiveRecord::Base.connection_config[:database] if environment_loaded
output = rails("db:drop")
assert_match(/Dropped database/, output)
assert !File.exist?(expected_database)
@@ -49,6 +49,22 @@ module ApplicationTests
db_create_and_drop database_url_db_name
end
+ test "db:create and db:drop respect environment setting" do
+ app_file "config/database.yml", <<-YAML
+ development:
+ database: <%= Rails.application.config.database %>
+ adapter: sqlite3
+ YAML
+
+ app_file "config/environments/development.rb", <<-RUBY
+ Rails.application.configure do
+ config.database = "db/development.sqlite3"
+ end
+ RUBY
+
+ db_create_and_drop "db/development.sqlite3", environment_loaded: false
+ end
+
def with_database_existing
Dir.chdir(app_path) do
set_database_url
@@ -82,6 +98,20 @@ module ApplicationTests
end
end
+ test "db:create works when schema cache exists and database does not exist" do
+ use_postgresql
+
+ begin
+ rails %w(db:create db:migrate db:schema:cache:dump)
+
+ rails "db:drop"
+ rails "db:create"
+ assert_equal 0, $?.exitstatus
+ ensure
+ rails "db:drop" rescue nil
+ end
+ end
+
test "db:drop failure because database does not exist" do
output = rails("db:drop:_unsafe", "--trace")
assert_match(/does not exist/, output)
@@ -173,7 +203,7 @@ module ApplicationTests
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
+ # if structure is not loaded correctly, exception would be raised
assert_equal 0, Book.count
end
end
@@ -268,7 +298,7 @@ module ApplicationTests
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
ActiveRecord::Base.establish_connection :test
require "#{app_path}/app/models/book"
- #if structure is not loaded correctly, exception would be raised
+ # if structure is not loaded correctly, exception would be raised
assert_equal 0, Book.count
assert_match ActiveRecord::Base.configurations["test"]["database"],
ActiveRecord::Base.connection_config[:database]
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
index 788f9160d6..bf5b07afbd 100644
--- a/railties/test/application/rake/migrations_test.rb
+++ b/railties/test/application/rake/migrations_test.rb
@@ -29,12 +29,32 @@ module ApplicationTests
assert_match(/AMigration: migrated/, output)
+ # run all the migrations to test scope for down
+ output = rails("db:migrate")
+ assert_match(/CreateUsers: migrated/, 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)
+
+ output = rails("db:migrate", "VERSION=0")
+
+ assert_match(/CreateUsers: reverted/, output)
+ end
+
+ test "version outputs current version" do
+ app_file "db/migrate/01_one_migration.rb", <<-MIGRATION
+ class OneMigration < ActiveRecord::Migration::Current
+ end
+ MIGRATION
+
+ rails "db:migrate"
+
+ output = rails("db:version")
+ assert_match(/Current version: 1/, output)
end
test "migrate with specified VERSION in different formats" do
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index bf89098645..5a6404bd0a 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -132,8 +132,14 @@ module ApplicationTests
output = rails("routes")
assert_equal <<-MESSAGE.strip_heredoc, output
- Prefix Verb URI Pattern Controller#Action
- cart GET /cart(.:format) cart#show
+ 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_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#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
MESSAGE
end
@@ -168,14 +174,19 @@ module ApplicationTests
output = rails("routes", "-g", "show")
assert_equal <<-MESSAGE.strip_heredoc, output
- Prefix Verb URI Pattern Controller#Action
- cart GET /cart(.:format) cart#show
+ 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_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#show
+ rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show
MESSAGE
output = rails("routes", "-g", "POST")
assert_equal <<-MESSAGE.strip_heredoc, output
- Prefix Verb URI Pattern Controller#Action
- POST /cart(.:format) cart#create
+ 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
MESSAGE
output = rails("routes", "-g", "basketballs")
@@ -232,11 +243,13 @@ module ApplicationTests
RUBY
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.
+ 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_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#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
MESSAGE
end
@@ -250,8 +263,14 @@ 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
+ 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_blob_preview GET /rails/active_storage/previews/:signed_blob_id/:variation_key/*filename(.:format) active_storage/previews#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
MESSAGE
end
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index aa5d495c97..8f5f48c281 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -107,13 +107,13 @@ module ApplicationTests
def test_runner_detects_syntax_errors
output = rails("runner", "puts 'hello world", allow_failure: true)
- assert_not $?.success?
+ assert_not_predicate $?, :success?
assert_match "unterminated string meets end of file", output
end
def test_runner_detects_bad_script_name
output = rails("runner", "iuiqwiourowe", allow_failure: true)
- assert_not $?.success?
+ assert_not_predicate $?, :success?
assert_match "undefined local variable or method `iuiqwiourowe' for", output
end
diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb
index 2238f4f63a..f3a7e00a4d 100644
--- a/railties/test/application/server_test.rb
+++ b/railties/test/application/server_test.rb
@@ -29,12 +29,17 @@ module ApplicationTests
server.app
log = File.read(Rails.application.config.paths["log"].first)
- assert_match(/DEPRECATION WARNING: Use `Rails::Application` subclass to start the server is deprecated/, log)
+ assert_match(/DEPRECATION WARNING: Using `Rails::Application` subclass to start the server is deprecated/, log)
end
test "restart rails server with custom pid file path" do
skip "PTY unavailable" unless available_pty?
+ File.open("#{app_path}/config/boot.rb", "w") do |f|
+ f.puts "ENV['BUNDLE_GEMFILE'] = '#{Bundler.default_gemfile.to_s}'"
+ f.puts "require 'bundler/setup'"
+ end
+
master, slave = PTY.open
pid = nil
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index e92a0466dd..a01325fdb8 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -569,6 +569,40 @@ module ApplicationTests
assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test"
end
+ def test_running_with_ruby_gets_test_env_by_default
+ # Subshells inherit `ENV`, so we need to ensure `RAILS_ENV` is set to
+ # nil before we run the tests in the test app.
+ re, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], nil
+
+ file = create_test_for_env("test")
+ results = Dir.chdir(app_path) {
+ `ruby -Ilib:test #{file}`.each_line.map { |line| JSON.parse line }
+ }
+ assert_equal 1, results.length
+ failures = results.first["failures"]
+ flunk(failures.first) unless failures.empty?
+
+ ensure
+ ENV["RAILS_ENV"] = re
+ end
+
+ def test_running_with_ruby_can_set_env_via_cmdline
+ # Subshells inherit `ENV`, so we need to ensure `RAILS_ENV` is set to
+ # nil before we run the tests in the test app.
+ re, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], nil
+
+ file = create_test_for_env("development")
+ results = Dir.chdir(app_path) {
+ `RAILS_ENV=development ruby -Ilib:test #{file}`.each_line.map { |line| JSON.parse line }
+ }
+ assert_equal 1, results.length
+ failures = results.first["failures"]
+ flunk(failures.first) unless failures.empty?
+
+ ensure
+ ENV["RAILS_ENV"] = re
+ end
+
def test_rake_passes_multiple_TESTOPTS_to_minitest
create_test_file :models, "account"
output = Dir.chdir(app_path) { `bin/rake test TESTOPTS='-v --seed=1234'` }
@@ -727,6 +761,45 @@ module ApplicationTests
app_file "db/schema.rb", ""
end
+ def create_test_for_env(env)
+ app_file "test/models/environment_test.rb", <<-RUBY
+ require 'test_helper'
+ class JSONReporter < Minitest::AbstractReporter
+ def record(result)
+ puts JSON.dump(klass: result.class.name,
+ name: result.name,
+ failures: result.failures,
+ assertions: result.assertions,
+ time: result.time)
+ end
+ end
+
+ def Minitest.plugin_json_reporter_init(opts)
+ Minitest.reporter.reporters.clear
+ Minitest.reporter.reporters << JSONReporter.new
+ end
+
+ Minitest.extensions << "rails"
+ Minitest.extensions << "json_reporter"
+
+ # Minitest uses RubyGems to find plugins, and since RubyGems
+ # doesn't know about the Rails installation we're pointing at,
+ # Minitest won't require the Rails minitest plugin when we run
+ # these integration tests. So we have to manually require the
+ # Minitest plugin here.
+ require 'minitest/rails_plugin'
+
+ class EnvironmentTest < ActiveSupport::TestCase
+ def test_environment
+ test_db = ActiveRecord::Base.configurations[#{env.dump}]["database"]
+ db_file = ActiveRecord::Base.connection_config[:database]
+ assert_match(test_db, db_file)
+ assert_equal #{env.dump}, ENV["RAILS_ENV"]
+ end
+ end
+ RUBY
+ end
+
def create_test_file(path = :unit, name = "test", pass: true)
app_file "test/#{path}/#{name}_test.rb", <<-RUBY
require 'test_helper'
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
index 45ab8d87ff..b7cdb8229e 100644
--- a/railties/test/commands/console_test.rb
+++ b/railties/test/commands/console_test.rb
@@ -20,30 +20,30 @@ class Rails::ConsoleTest < ActiveSupport::TestCase
def test_sandbox_option
console = Rails::Console.new(app, parse_arguments(["--sandbox"]))
- assert console.sandbox?
+ assert_predicate console, :sandbox?
end
def test_short_version_of_sandbox_option
console = Rails::Console.new(app, parse_arguments(["-s"]))
- assert console.sandbox?
+ assert_predicate console, :sandbox?
end
def test_no_options
console = Rails::Console.new(app, parse_arguments([]))
- assert !console.sandbox?
+ assert_not_predicate console, :sandbox?
end
def test_start
start
- assert app.console.started?
+ assert_predicate app.console, :started?
assert_match(/Loading \w+ environment \(Rails/, output)
end
def test_start_with_sandbox
start ["--sandbox"]
- assert app.console.started?
+ assert_predicate app.console, :started?
assert app.sandbox
assert_match(/Loading \w+ environment in sandbox \(Rails/, output)
end
diff --git a/railties/test/commands/credentials_test.rb b/railties/test/commands/credentials_test.rb
index 743fb5f788..663ee73bcd 100644
--- a/railties/test/commands/credentials_test.rb
+++ b/railties/test/commands/credentials_test.rb
@@ -13,7 +13,10 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
teardown { teardown_app }
test "edit without editor gives hint" do
- assert_match "No $EDITOR to open credentials in", run_edit_command(editor: "")
+ run_edit_command(editor: "").tap do |output|
+ assert_match "No $EDITOR to open file in", output
+ assert_match "bin/rails credentials:edit", output
+ end
end
test "edit credentials" do
@@ -23,10 +26,6 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
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
@@ -36,6 +35,44 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
end
end
+ test "edit command does not overwrite by default if credentials already exists" do
+ run_edit_command(editor: "eval echo api_key: abc >")
+ assert_match(/api_key: abc/, run_show_command)
+
+ run_edit_command
+ assert_match(/api_key: abc/, run_show_command)
+ end
+
+ test "edit command does not add master key when `RAILS_MASTER_KEY` env specified" do
+ Dir.chdir(app_path) do
+ key = IO.binread("config/master.key").strip
+ FileUtils.rm("config/master.key")
+
+ switch_env("RAILS_MASTER_KEY", key) do
+ run_edit_command
+ assert_not File.exist?("config/master.key")
+ end
+ end
+ end
+
+ test "show credentials" do
+ assert_match(/access_key_id: 123/, run_show_command)
+ end
+
+ test "show command raise error when require_master_key is specified and key does not exist" do
+ remove_file "config/master.key"
+ add_to_config "config.require_master_key = true"
+
+ assert_match(/Missing encryption key to decrypt file with/, run_show_command(allow_failure: true))
+ end
+
+ test "show command does not raise error when require_master_key is false and master key does not exist" do
+ remove_file "config/master.key"
+ add_to_config "config.require_master_key = false"
+
+ assert_match(/Missing master key to decrypt credentials/, run_show_command)
+ end
+
private
def run_edit_command(editor: "cat")
switch_env("EDITOR", editor) do
@@ -43,7 +80,7 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
end
end
- def run_show_command
- rails "credentials:show"
+ def run_show_command(**options)
+ rails "credentials:show", **options
end
end
diff --git a/railties/test/commands/encrypted_test.rb b/railties/test/commands/encrypted_test.rb
new file mode 100644
index 0000000000..9fc73d5f18
--- /dev/null
+++ b/railties/test/commands/encrypted_test.rb
@@ -0,0 +1,106 @@
+# frozen_string_literal: true
+
+require "isolation/abstract_unit"
+require "env_helpers"
+require "rails/command"
+require "rails/commands/encrypted/encrypted_command"
+
+class Rails::Command::EncryptedCommandTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation, EnvHelpers
+
+ setup :build_app
+ teardown :teardown_app
+
+ test "edit without editor gives hint" do
+ run_edit_command("config/tokens.yml.enc", editor: "").tap do |output|
+ assert_match "No $EDITOR to open file in", output
+ assert_match "bin/rails encrypted:edit", output
+ end
+ end
+
+ test "edit encrypted file" do
+ # Run twice to ensure file can be reread after first edit pass.
+ 2.times do
+ assert_match(/access_key_id: 123/, run_edit_command("config/tokens.yml.enc"))
+ end
+ end
+
+ test "edit command does not add master key to gitignore when already exist" do
+ run_edit_command("config/tokens.yml.enc")
+
+ Dir.chdir(app_path) do
+ assert_match "/config/master.key", File.read(".gitignore")
+ end
+ end
+
+ test "edit command does not add master key when `RAILS_MASTER_KEY` env specified" do
+ Dir.chdir(app_path) do
+ key = IO.binread("config/master.key").strip
+ FileUtils.rm("config/master.key")
+
+ switch_env("RAILS_MASTER_KEY", key) do
+ run_edit_command("config/tokens.yml.enc")
+ assert_not File.exist?("config/master.key")
+ end
+ end
+ end
+
+ test "edit encrypts file with custom key" do
+ run_edit_command("config/tokens.yml.enc", key: "config/tokens.key")
+
+ Dir.chdir(app_path) do
+ assert File.exist?("config/tokens.yml.enc")
+ assert File.exist?("config/tokens.key")
+
+ assert_match "/config/tokens.key", File.read(".gitignore")
+ end
+
+ assert_match(/access_key_id: 123/, run_edit_command("config/tokens.yml.enc", key: "config/tokens.key"))
+ end
+
+ test "show encrypted file with custom key" do
+ run_edit_command("config/tokens.yml.enc", key: "config/tokens.key")
+
+ assert_match(/access_key_id: 123/, run_show_command("config/tokens.yml.enc", key: "config/tokens.key"))
+ end
+
+ test "show command raise error when require_master_key is specified and key does not exist" do
+ add_to_config "config.require_master_key = true"
+
+ assert_match(/Missing encryption key to decrypt file with/,
+ run_show_command("config/tokens.yml.enc", key: "unexist.key", allow_failure: true))
+ end
+
+ test "show command does not raise error when require_master_key is false and master key does not exist" do
+ remove_file "config/master.key"
+ add_to_config "config.require_master_key = false"
+
+ assert_match(/Missing 'config\/master\.key' to decrypt data/, run_show_command("config/tokens.yml.enc"))
+ end
+
+ test "won't corrupt encrypted file when passed wrong key" do
+ run_edit_command("config/tokens.yml.enc", key: "config/tokens.key")
+
+ assert_match "passed the wrong key",
+ run_edit_command("config/tokens.yml.enc", allow_failure: true)
+
+ assert_match(/access_key_id: 123/, run_show_command("config/tokens.yml.enc", key: "config/tokens.key"))
+ end
+
+ private
+ def run_edit_command(file, key: nil, editor: "cat", **options)
+ switch_env("EDITOR", editor) do
+ rails "encrypted:edit", prepare_args(file, key), **options
+ end
+ end
+
+ def run_show_command(file, key: nil, **options)
+ rails "encrypted:show", prepare_args(file, key), **options
+ end
+
+ def prepare_args(file, key)
+ args = [ file ]
+ args.push("--key", key) if key
+ args
+ end
+end
diff --git a/railties/test/commands/secrets_test.rb b/railties/test/commands/secrets_test.rb
index 66a81826e3..6b9f284a0c 100644
--- a/railties/test/commands/secrets_test.rb
+++ b/railties/test/commands/secrets_test.rb
@@ -8,21 +8,38 @@ require "rails/commands/secrets/secrets_command"
class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation, EnvHelpers
- def setup
- build_app
+ setup :build_app
+ teardown :teardown_app
+
+ test "edit without editor gives hint" do
+ assert_match "No $EDITOR to open decrypted secrets in", run_edit_command(editor: "")
end
- def teardown
- teardown_app
+ test "encrypted secrets are deprecated when using credentials" do
+ assert_match "Encrypted secrets is deprecated", run_setup_command
+ assert_equal 1, $?.exitstatus
+ assert_not File.exist?("config/secrets.yml.enc")
end
- test "edit without editor gives hint" do
- assert_match "No $EDITOR to open decrypted secrets in", run_edit_command(editor: "")
+ test "encrypted secrets are deprecated when running edit without setup" do
+ assert_match "Encrypted secrets is deprecated", run_setup_command
+ assert_equal 1, $?.exitstatus
+ assert_not File.exist?("config/secrets.yml.enc")
+ end
+
+ test "encrypted secrets are deprecated for 5.1 config/secrets.yml apps" do
+ Dir.chdir(app_path) do
+ FileUtils.rm("config/credentials.yml.enc")
+ FileUtils.touch("config/secrets.yml")
+
+ assert_match "Encrypted secrets is deprecated", run_setup_command
+ assert_equal 1, $?.exitstatus
+ assert_not File.exist?("config/secrets.yml.enc")
+ end
end
test "edit secrets" do
- # Runs setup before first edit.
- assert_match(/Adding config\/secrets\.yml\.key to store the encryption key/, run_edit_command)
+ prevent_deprecation
# Run twice to ensure encrypted secrets can be reread after first edit pass.
2.times do
@@ -31,22 +48,30 @@ class Rails::Command::SecretsCommandTest < ActiveSupport::TestCase
end
test "show secrets" do
- run_setup_command
+ prevent_deprecation
+
assert_match(/external_api_key: 1466aac22e6a869134be3d09b9e89232fc2c2289/, run_show_command)
end
private
+ def prevent_deprecation
+ Dir.chdir(app_path) do
+ File.write("config/secrets.yml.key", "f731758c639da2604dfb6bf3d1025de8")
+ File.write("config/secrets.yml.enc", "sEB0mHxDbeP1/KdnMk00wyzPFACl9K6t0cZWn5/Mfx/YbTHvnI07vrneqHg9kaH3wOS7L6pIQteu1P077OtE4BSx/ZRc/sgQPHyWu/tXsrfHqnPNpayOF/XZqizE91JacSFItNMWpuPsp9ynbzz+7cGhoB1S4aPNIU6u0doMrzdngDbijsaAFJmsHIQh6t/QHoJx--8aMoE0PvUWmw1Iqz--ldFqnM/K0g9k17M8PKoN/Q==")
+ end
+ end
+
def run_edit_command(editor: "cat")
switch_env("EDITOR", editor) do
- rails "secrets:edit"
+ rails "secrets:edit", allow_failure: true
end
end
def run_show_command
- rails "secrets:show"
+ rails "secrets:show", allow_failure: true
end
def run_setup_command
- rails "secrets:setup"
+ rails "secrets:setup", allow_failure: true
end
end
diff --git a/railties/test/engine/test_test.rb b/railties/test/engine/test_test.rb
new file mode 100644
index 0000000000..18af85a0aa
--- /dev/null
+++ b/railties/test/engine/test_test.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+
+class Rails::Engine::TestTest < ActiveSupport::TestCase
+ setup do
+ @destination_root = Dir.mktmpdir("bukkits")
+ Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --mountable` }
+ end
+
+ teardown do
+ FileUtils.rm_rf(@destination_root)
+ end
+
+ test "automatically synchronize test schema" do
+ Dir.chdir(plugin_path) do
+ # In order to confirm that migration files are loaded, generate multiple migration files.
+ `bin/rails generate model user name:string;
+ bin/rails generate model todo name:string;
+ RAILS_ENV=development bin/rails db:migrate`
+
+ output = `bin/rails test test/models/bukkits/user_test.rb`
+ assert_includes(output, "0 runs, 0 assertions, 0 failures, 0 errors, 0 skips")
+ end
+ end
+
+ private
+ def plugin_path
+ "#{@destination_root}/bukkits"
+ end
+end
diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb
index 4bd8a07085..19379e200c 100644
--- a/railties/test/engine_test.rb
+++ b/railties/test/engine_test.rb
@@ -11,7 +11,7 @@ class EngineTest < ActiveSupport::TestCase
end
end
- assert !engine.routes?
+ assert_not_predicate engine, :routes?
end
def test_application_can_be_subclassed
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index 7791d472d8..4815cf6362 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -72,6 +72,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
assert_no_file "config/initializers/cookies_serializer.rb"
assert_no_file "config/initializers/assets.rb"
+ assert_no_file "config/initializers/content_security_policy.rb"
end
def test_app_update_does_not_generate_unnecessary_bin_files
@@ -149,6 +150,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
bin/yarn
config/initializers/assets.rb
config/initializers/cookies_serializer.rb
+ config/initializers/content_security_policy.rb
lib/assets
test/helpers
tmp/cache/assets
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 78962ee30b..60b9ff1317 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -56,6 +56,7 @@ DEFAULT_APP_FILES = %w(
config/initializers/assets.rb
config/initializers/backtrace_silencers.rb
config/initializers/cookies_serializer.rb
+ config/initializers/content_security_policy.rb
config/initializers/filter_parameter_logging.rb
config/initializers/inflections.rb
config/initializers/mime_types.rb
@@ -104,6 +105,15 @@ class AppGeneratorTest < Rails::Generators::TestCase
::DEFAULT_APP_FILES
end
+ def test_skip_bundle
+ assert_not_called(generator([destination_root], skip_bundle: true), :bundle_command) do
+ quietly { generator.invoke_all }
+ # skip_bundle is only about running bundle install, ensure the Gemfile is still
+ # generated.
+ assert_file "Gemfile"
+ end
+ end
+
def test_assets
run_generator
@@ -303,21 +313,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /^# gem 'mini_magick'/
end
- def test_active_storage_install
- command_check = -> command, _ do
- @binstub_called ||= 0
- case command
- when "active_storage:install"
- @binstub_called += 1
- assert_equal 1, @binstub_called, "active_storage:install expected to be called once, but was called #{@install_called} times."
- end
- end
-
- generator.stub :rails_command, command_check do
- quietly { generator.invoke_all }
- end
- end
-
def test_app_update_does_not_generate_active_storage_contents_when_skip_active_storage_is_given
app_root = File.join(destination_root, "myapp")
run_generator [app_root, "--skip-active-storage"]
@@ -403,7 +398,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
if defined?(JRUBY_VERSION)
assert_gem "activerecord-jdbcmysql-adapter"
else
- assert_gem "mysql2", "'>= 0.3.18', '< 0.5'"
+ assert_gem "mysql2", "'~> 0.4.4'"
end
end
@@ -418,7 +413,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
if defined?(JRUBY_VERSION)
assert_gem "activerecord-jdbcpostgresql-adapter"
else
- assert_gem "pg", "'~> 0.18'"
+ assert_gem "pg", "'>= 0.18', '< 2.0'"
end
end
@@ -457,7 +452,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_defaults_to_puma_version
run_generator [destination_root]
- assert_gem "puma", "'~> 3.7'"
+ assert_gem "puma", "'~> 3.11'"
end
def test_generator_if_skip_puma_is_given
@@ -742,6 +737,45 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_webpack_option
+ command_check = -> command, *_ do
+ @called ||= 0
+ if command == "webpacker:install"
+ @called += 1
+ assert_equal 1, @called, "webpacker:install expected to be called once, but was called #{@called} times."
+ end
+ end
+
+ generator([destination_root], webpack: "webpack").stub(:rails_command, command_check) do
+ generator.stub :bundle_command, nil do
+ quietly { generator.invoke_all }
+ end
+ end
+
+ assert_gem "webpacker"
+ end
+
+ def test_webpack_option_with_js_framework
+ command_check = -> command, *_ do
+ case command
+ when "webpacker:install"
+ @webpacker ||= 0
+ @webpacker += 1
+ assert_equal 1, @webpacker, "webpacker:install expected to be called once, but was called #{@webpacker} times."
+ when "webpacker:install:react"
+ @react ||= 0
+ @react += 1
+ assert_equal 1, @react, "webpacker:install:react expected to be called once, but was called #{@react} times."
+ end
+ end
+
+ generator([destination_root], webpack: "react").stub(:rails_command, command_check) do
+ generator.stub :bundle_command, nil do
+ quietly { generator.invoke_all }
+ end
+ end
+ end
+
def test_generator_if_skip_turbolinks_is_given
run_generator [destination_root, "--skip-turbolinks"]
@@ -756,6 +790,37 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_bootsnap
+ run_generator
+
+ assert_gem "bootsnap"
+ assert_file "config/boot.rb" do |content|
+ assert_match(/require 'bootsnap\/setup'/, content)
+ end
+ end
+
+ def test_skip_bootsnap
+ run_generator [destination_root, "--skip-bootsnap"]
+
+ assert_file "Gemfile" do |content|
+ assert_no_match(/bootsnap/, content)
+ end
+ assert_file "config/boot.rb" do |content|
+ assert_no_match(/require 'bootsnap\/setup'/, content)
+ end
+ end
+
+ def test_bootsnap_with_dev_option
+ run_generator [destination_root, "--dev"]
+
+ assert_file "Gemfile" do |content|
+ assert_no_match(/bootsnap/, content)
+ end
+ assert_file "config/boot.rb" do |content|
+ assert_no_match(/require 'bootsnap\/setup'/, content)
+ end
+ end
+
def test_inclusion_of_ruby_version
run_generator
@@ -818,7 +883,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
template
end
- sequence = ["git init", "install", "exec spring binstub --all", "active_storage:install", "echo ran after_bundle"]
+ sequence = ["git init", "install", "exec spring binstub --all", "echo ran after_bundle"]
@sequence_step ||= 0
ensure_bundler_first = -> command, options = nil do
assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}"
@@ -835,7 +900,15 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- assert_equal 5, @sequence_step
+ assert_equal 4, @sequence_step
+ end
+
+ def test_gitignore
+ run_generator
+
+ assert_file ".gitignore" do |content|
+ assert_match(/config\/master\.key/, content)
+ end
end
def test_system_tests_directory_generated
diff --git a/railties/test/generators/create_migration_test.rb b/railties/test/generators/create_migration_test.rb
index 3cb7fd6baa..2ae38045c5 100644
--- a/railties/test/generators/create_migration_test.rb
+++ b/railties/test/generators/create_migration_test.rb
@@ -70,7 +70,7 @@ class CreateMigrationTest < Rails::Generators::TestCase
create_migration
assert_match(/identical db\/migrate\/1_create_articles\.rb\n/, invoke!)
- assert @migration.identical?
+ assert_predicate @migration, :identical?
end
def test_invoke_when_exists_not_identical
diff --git a/railties/test/generators/encrypted_secrets_generator_test.rb b/railties/test/generators/encrypted_secrets_generator_test.rb
deleted file mode 100644
index eacb5166c0..0000000000
--- a/railties/test/generators/encrypted_secrets_generator_test.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-# frozen_string_literal: true
-
-require "generators/generators_test_helper"
-require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
-
-class EncryptedSecretsGeneratorTest < Rails::Generators::TestCase
- include GeneratorsTestHelper
-
- def setup
- super
- cd destination_root
- end
-
- def test_generates_key_file_and_encrypted_secrets_file
- run_generator
-
- assert_file "config/secrets.yml.key", /\w+/
-
- assert File.exist?("config/secrets.yml.enc")
- assert_no_match(/# production:\n# external_api_key: \w+/, IO.binread("config/secrets.yml.enc"))
- assert_match(/# production:\n# external_api_key: \w+/, Rails::Secrets.read)
- end
-
- def test_appends_to_gitignore
- FileUtils.touch(".gitignore")
-
- run_generator
-
- assert_file ".gitignore", /config\/secrets.yml.key/, /(?!config\/secrets.yml.enc)/
- end
-
- def test_warns_when_ignore_is_missing
- assert_match(/Add this to your ignore file/i, run_generator)
- end
-
- def test_doesnt_generate_a_new_key_file_if_already_opted_in_to_encrypted_secrets
- FileUtils.mkdir("config")
- File.open("config/secrets.yml.enc", "w") { |f| f.puts "already secrety" }
-
- run_generator
-
- assert_no_file "config/secrets.yml.key"
- end
-end
diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb
index c6f7bdeda1..772b4f6f0d 100644
--- a/railties/test/generators/generated_attribute_test.rb
+++ b/railties/test/generators/generated_attribute_test.rb
@@ -104,25 +104,25 @@ class GeneratedAttributeTest < Rails::Generators::TestCase
def test_reference_is_true
%w(references belongs_to).each do |attribute_type|
- assert create_generated_attribute(attribute_type).reference?
+ assert_predicate create_generated_attribute(attribute_type), :reference?
end
end
def test_reference_is_false
%w(foo bar baz).each do |attribute_type|
- assert !create_generated_attribute(attribute_type).reference?
+ assert_not_predicate create_generated_attribute(attribute_type), :reference?
end
end
def test_polymorphic_reference_is_true
%w(references belongs_to).each do |attribute_type|
- assert create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic?
+ assert_predicate create_generated_attribute("#{attribute_type}{polymorphic}"), :polymorphic?
end
end
def test_polymorphic_reference_is_false
%w(foo bar baz).each do |attribute_type|
- assert !create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic?
+ assert_not_predicate create_generated_attribute("#{attribute_type}{polymorphic}"), :polymorphic?
end
end
@@ -148,7 +148,7 @@ class GeneratedAttributeTest < Rails::Generators::TestCase
att = Rails::Generators::GeneratedAttribute.parse("supplier:references{required}:index")
assert_equal "supplier", att.name
assert_equal :references, att.type
- assert att.has_index?
- assert att.required?
+ assert_predicate att, :has_index?
+ assert_predicate att, :required?
end
end
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index cb5d8da7b1..ad2a55f496 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -43,9 +43,9 @@ module GeneratorsTestHelper
end
def copy_routes
- routes = File.expand_path("../../lib/rails/generators/rails/app/templates/config/routes.rb", __dir__)
+ routes = File.expand_path("../../lib/rails/generators/rails/app/templates/config/routes.rb.tt", __dir__)
destination = File.join(destination_root, "config")
FileUtils.mkdir_p(destination)
- FileUtils.cp routes, destination
+ FileUtils.cp routes, File.join(destination, "routes.rb")
end
end
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 49256883d6..fc7584c175 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -474,6 +474,8 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_file "test/dummy/Gemfile"
assert_no_file "test/dummy/public/robots.txt"
assert_no_file "test/dummy/README.md"
+ assert_no_file "test/dummy/config/master.key"
+ assert_no_file "test/dummy/config/credentials.yml.enc"
assert_no_directory "test/dummy/lib/tasks"
assert_no_directory "test/dummy/test"
assert_no_directory "test/dummy/vendor"
@@ -513,10 +515,11 @@ class PluginGeneratorTest < Rails::Generators::TestCase
gemfile_path = "#{Rails.root}/Gemfile"
Object.const_set("APP_PATH", Rails.root)
FileUtils.touch gemfile_path
+ File.write(gemfile_path, "#foo")
run_generator
- assert_file gemfile_path, /gem 'bukkits', path: 'tmp\/bukkits'/
+ assert_file gemfile_path, /^gem 'bukkits', path: 'tmp\/bukkits'/
ensure
Object.send(:remove_const, "APP_PATH")
FileUtils.rm gemfile_path
@@ -718,6 +721,38 @@ class PluginGeneratorTest < Rails::Generators::TestCase
Object.send(:remove_const, "ENGINE_ROOT")
end
+ def test_after_bundle_callback
+ path = "http://example.org/rails_template"
+ template = %{ after_bundle { run "echo ran after_bundle" } }.dup
+ template.instance_eval "def read; self; end" # Make the string respond to read
+
+ check_open = -> *args do
+ assert_equal [ path, "Accept" => "application/x-thor-template" ], args
+ template
+ end
+
+ sequence = ["echo ran after_bundle"]
+ @sequence_step ||= 0
+ ensure_bundler_first = -> command do
+ assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}"
+ @sequence_step += 1
+ end
+
+ content = nil
+ generator([destination_root], template: path).stub(:open, check_open, template) do
+ generator.stub(:bundle_command, ensure_bundler_first) do
+ generator.stub(:run, ensure_bundler_first) do
+ silence_stream($stdout) do
+ content = capture(:stderr) { generator.invoke_all }
+ end
+ end
+ end
+ end
+
+ assert_equal 1, @sequence_step
+ assert_match(/DEPRECATION WARNING: `after_bundle` is deprecated/, content)
+ end
+
private
def action(*args, &block)
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index b6294c3b94..29426cd99f 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -471,8 +471,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
assert_file "app/views/accounts/_form.html.erb" do |content|
- assert_match(/^\W{4}<%= form\.text_field :name, id: :account_name %>/, content)
- assert_match(/^\W{4}<%= form\.text_field :currency_id, id: :account_currency_id %>/, content)
+ assert_match(/^\W{4}<%= form\.text_field :name %>/, content)
+ assert_match(/^\W{4}<%= form\.text_field :currency_id %>/, content)
end
end
@@ -495,8 +495,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
assert_file "app/views/users/_form.html.erb" do |content|
- assert_match(/<%= form\.password_field :password, id: :user_password %>/, content)
- assert_match(/<%= form\.password_field :password_confirmation, id: :user_password_confirmation %>/, content)
+ assert_match(/<%= form\.password_field :password %>/, content)
+ assert_match(/<%= form\.password_field :password_confirmation %>/, content)
end
assert_file "app/views/users/index.html.erb" do |content|
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 6b746f3fed..97d43af60a 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -92,7 +92,9 @@ module SharedGeneratorTests
end
generator([destination_root], template: path).stub(:open, check_open, template) do
- quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
+ generator.stub :bundle_command, nil do
+ quietly { assert_match(/It works!/, capture(:stdout) { generator.invoke_all }) }
+ end
end
end
@@ -103,15 +105,6 @@ module SharedGeneratorTests
end
end
- def test_skip_bundle
- assert_not_called(generator([destination_root], skip_bundle: true), :bundle_command) do
- quietly { generator.invoke_all }
- # skip_bundle is only about running bundle install, ensure the Gemfile is still
- # generated.
- assert_file "Gemfile"
- end
- end
-
def test_skip_git
run_generator [destination_root, "--skip-git", "--full"]
assert_no_file(".gitignore")
@@ -252,7 +245,6 @@ module SharedGeneratorTests
end
assert_no_file "#{application_path}/config/storage.yml"
- assert_no_directory "#{application_path}/db/migrate"
assert_no_directory "#{application_path}/storage"
assert_no_directory "#{application_path}/tmp/storage"
@@ -283,7 +275,6 @@ module SharedGeneratorTests
end
assert_no_file "#{application_path}/config/storage.yml"
- assert_no_directory "#{application_path}/db/migrate"
assert_no_directory "#{application_path}/storage"
assert_no_directory "#{application_path}/tmp/storage"
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 28e7617d7f..9f1f2a2289 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -36,6 +36,19 @@ class GeneratorsTest < Rails::Generators::TestCase
assert_match "Maybe you meant 'migration'", output
end
+ def test_generator_suggestions_except_en_locale
+ orig_available_locales = I18n.available_locales
+ orig_default_locale = I18n.default_locale
+ I18n.available_locales = :ja
+ I18n.default_locale = :ja
+ name = :tas
+ output = capture(:stdout) { Rails::Generators.invoke name }
+ assert_match "Maybe you meant 'task', 'job' or", output
+ ensure
+ I18n.available_locales = orig_available_locales
+ I18n.default_locale = orig_default_locale
+ end
+
def test_generator_multiple_suggestions
name = :tas
output = capture(:stdout) { Rails::Generators.invoke name }
@@ -51,7 +64,7 @@ class GeneratorsTest < Rails::Generators::TestCase
assert File.exist?(File.join(@path, "generators", "model_generator.rb"))
assert_called_with(Rails::Generators::ModelGenerator, :start, [["Account"], {}]) do
warnings = capture(:stderr) { Rails::Generators.invoke :model, ["Account"] }
- assert warnings.empty?
+ assert_empty warnings
end
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 7522237a38..6568a356d6 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -82,22 +82,6 @@ module TestHelpers
assert_match "charset=utf-8", resp[1]["Content-Type"]
assert extract_body(resp).match(/Yay! You.*re on Rails!/)
end
-
- def assert_success(resp)
- assert_equal 202, resp[0]
- end
-
- def assert_missing(resp)
- assert_equal 404, resp[0]
- end
-
- def assert_header(key, value, resp)
- assert_equal value, resp[1][key.to_s]
- end
-
- def assert_body(expected, resp)
- assert_equal expected, extract_body(resp)
- end
end
module Generation
@@ -358,10 +342,12 @@ module TestHelpers
end
def app_file(path, contents, mode = "w")
- FileUtils.mkdir_p File.dirname("#{app_path}/#{path}")
- File.open("#{app_path}/#{path}", mode) do |f|
+ file_name = "#{app_path}/#{path}"
+ FileUtils.mkdir_p File.dirname(file_name)
+ File.open(file_name, mode) do |f|
f.puts contents
end
+ file_name
end
def remove_file(path)
@@ -381,6 +367,21 @@ module TestHelpers
$:.reject! { |path| path =~ %r'/(#{to_remove.join('|')})/' }
end
+
+ def use_postgresql
+ File.open("#{app_path}/config/database.yml", "w") do |f|
+ f.puts <<-YAML
+ default: &default
+ adapter: postgresql
+ pool: 5
+ database: railties_test
+ development:
+ <<: *default
+ test:
+ <<: *default
+ YAML
+ end
+ end
end
end
@@ -389,6 +390,10 @@ class ActiveSupport::TestCase
include TestHelpers::Rack
include TestHelpers::Generation
include ActiveSupport::Testing::Stream
+
+ def frozen_error_class
+ Object.const_defined?(:FrozenError) ? FrozenError : RuntimeError
+ end
end
# Create a scope and build a fixture rails app
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 854b4448a4..9f5bb37c20 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -104,7 +104,7 @@ class PathsTest < ActiveSupport::TestCase
File.stub(:exist?, true) do
@root.add "app", with: "/app"
@root["app"].autoload_once!
- assert @root["app"].autoload_once?
+ assert_predicate @root["app"], :autoload_once?
assert_includes @root.autoload_once, @root["app"].expanded.first
end
end
@@ -112,17 +112,17 @@ class PathsTest < ActiveSupport::TestCase
test "it is possible to remove a path that should be autoloaded only once" do
@root["app"] = "/app"
@root["app"].autoload_once!
- assert @root["app"].autoload_once?
+ assert_predicate @root["app"], :autoload_once?
@root["app"].skip_autoload_once!
- assert !@root["app"].autoload_once?
+ assert_not_predicate @root["app"], :autoload_once?
assert_not_includes @root.autoload_once, @root["app"].expanded.first
end
test "it is possible to add a path without assignment and specify it should be loaded only once" do
File.stub(:exist?, true) do
@root.add "app", with: "/app", autoload_once: true
- assert @root["app"].autoload_once?
+ assert_predicate @root["app"], :autoload_once?
assert_includes @root.autoload_once, "/app"
end
end
@@ -130,7 +130,7 @@ class PathsTest < ActiveSupport::TestCase
test "it is possible to add multiple paths without assignment and specify it should be loaded only once" do
File.stub(:exist?, true) do
@root.add "app", with: ["/app", "/app2"], autoload_once: true
- assert @root["app"].autoload_once?
+ assert_predicate @root["app"], :autoload_once?
assert_includes @root.autoload_once, "/app"
assert_includes @root.autoload_once, "/app2"
end
@@ -158,7 +158,7 @@ class PathsTest < ActiveSupport::TestCase
File.stub(:exist?, true) do
@root["app"] = "/app"
@root["app"].eager_load!
- assert @root["app"].eager_load?
+ assert_predicate @root["app"], :eager_load?
assert_includes @root.eager_load, @root["app"].to_a.first
end
end
@@ -166,17 +166,17 @@ class PathsTest < ActiveSupport::TestCase
test "it is possible to skip a path from eager loading" do
@root["app"] = "/app"
@root["app"].eager_load!
- assert @root["app"].eager_load?
+ assert_predicate @root["app"], :eager_load?
@root["app"].skip_eager_load!
- assert !@root["app"].eager_load?
+ assert_not_predicate @root["app"], :eager_load?
assert_not_includes @root.eager_load, @root["app"].to_a.first
end
test "it is possible to add a path without assignment and mark it as eager" do
File.stub(:exist?, true) do
@root.add "app", with: "/app", eager_load: true
- assert @root["app"].eager_load?
+ assert_predicate @root["app"], :eager_load?
assert_includes @root.eager_load, "/app"
end
end
@@ -184,7 +184,7 @@ class PathsTest < ActiveSupport::TestCase
test "it is possible to add multiple paths without assignment and mark them as eager" do
File.stub(:exist?, true) do
@root.add "app", with: ["/app", "/app2"], eager_load: true
- assert @root["app"].eager_load?
+ assert_predicate @root["app"], :eager_load?
assert_includes @root.eager_load, "/app"
assert_includes @root.eager_load, "/app2"
end
@@ -193,8 +193,8 @@ class PathsTest < ActiveSupport::TestCase
test "it is possible to create a path without assignment and mark it both as eager and load once" do
File.stub(:exist?, true) do
@root.add "app", with: "/app", eager_load: true, autoload_once: true
- assert @root["app"].eager_load?
- assert @root["app"].autoload_once?
+ assert_predicate @root["app"], :eager_load?
+ assert_predicate @root["app"], :autoload_once?
assert_includes @root.eager_load, "/app"
assert_includes @root.autoload_once, "/app"
end
@@ -254,7 +254,7 @@ class PathsTest < ActiveSupport::TestCase
test "a path can be added to the load path on creation" do
File.stub(:exist?, true) do
@root.add "app", with: "/app", load_path: true
- assert @root["app"].load_path?
+ assert_predicate @root["app"], :load_path?
assert_equal ["/app"], @root.load_paths
end
end
@@ -271,7 +271,7 @@ class PathsTest < ActiveSupport::TestCase
test "a path can be marked as autoload on creation" do
File.stub(:exist?, true) do
@root.add "app", with: "/app", autoload: true
- assert @root["app"].autoload?
+ assert_predicate @root["app"], :autoload?
assert_equal ["/app"], @root.autoload_paths
end
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index fc710feb63..a59c63f343 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -34,7 +34,7 @@ module RailtiesTest
def migrations
migration_root = File.expand_path(ActiveRecord::Migrator.migrations_paths.first, app_path)
- ActiveRecord::Migrator.migrations(migration_root)
+ ActiveRecord::MigrationContext.new(migration_root).migrations
end
test "serving sprocket's assets" do
@@ -980,14 +980,14 @@ YAML
boot_rails
app_generators = Rails.application.config.generators.options[:rails]
- assert_equal :mongoid , app_generators[:orm]
- assert_equal :liquid , app_generators[:template_engine]
+ assert_equal :mongoid, app_generators[:orm]
+ assert_equal :liquid, app_generators[:template_engine]
assert_equal :test_unit, app_generators[:test_framework]
generators = Bukkits::Engine.config.generators.options[:rails]
assert_equal :data_mapper, generators[:orm]
- assert_equal :haml , generators[:template_engine]
- assert_equal :rspec , generators[:test_framework]
+ assert_equal :haml, generators[:template_engine]
+ assert_equal :rspec, generators[:test_framework]
end
test "engine should get default generators with ability to overwrite them" do
@@ -1003,10 +1003,10 @@ YAML
generators = Bukkits::Engine.config.generators.options[:rails]
assert_equal :active_record, generators[:orm]
- assert_equal :rspec , generators[:test_framework]
+ assert_equal :rspec, generators[:test_framework]
app_generators = Rails.application.config.generators.options[:rails]
- assert_equal :test_unit , app_generators[:test_framework]
+ assert_equal :test_unit, app_generators[:test_framework]
end
test "do not create table_name_prefix method if it already exists" do
@@ -1479,6 +1479,21 @@ YAML
assert_equal "/fruits/2/bukkits/posts", last_response.body
end
+ test "active_storage:install task works within engine" do
+ @plugin.write "Rakefile", <<-RUBY
+ APP_RAKEFILE = '#{app_path}/Rakefile'
+ load 'rails/tasks/engine.rake'
+ RUBY
+
+ Dir.chdir(@plugin.path) do
+ output = `bundle exec rake app:active_storage:install`
+ assert $?.success?, output
+
+ active_storage_migration = migrations.detect { |migration| migration.name == "CreateActiveStorageTables" }
+ assert active_storage_migration
+ end
+ end
+
private
def app
Rails.application
diff --git a/railties/test/secrets_test.rb b/railties/test/secrets_test.rb
index 888fee173a..06877bc76a 100644
--- a/railties/test/secrets_test.rb
+++ b/railties/test/secrets_test.rb
@@ -1,20 +1,13 @@
# frozen_string_literal: true
require "isolation/abstract_unit"
-require "rails/generators"
-require "rails/generators/rails/encrypted_secrets/encrypted_secrets_generator"
require "rails/secrets"
class Rails::SecretsTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
- def setup
- build_app
- end
-
- def teardown
- teardown_app
- end
+ setup :build_app
+ teardown :teardown_app
test "setting read to false skips parsing" do
run_secrets_generator do
@@ -172,9 +165,8 @@ class Rails::SecretsTest < ActiveSupport::TestCase
private
def run_secrets_generator
Dir.chdir(app_path) do
- capture(:stdout) do
- Rails::Generators::EncryptedSecretsGenerator.start
- end
+ File.write("config/secrets.yml.key", "f731758c639da2604dfb6bf3d1025de8")
+ File.write("config/secrets.yml.enc", "sEB0mHxDbeP1/KdnMk00wyzPFACl9K6t0cZWn5/Mfx/YbTHvnI07vrneqHg9kaH3wOS7L6pIQteu1P077OtE4BSx/ZRc/sgQPHyWu/tXsrfHqnPNpayOF/XZqizE91JacSFItNMWpuPsp9ynbzz+7cGhoB1S4aPNIU6u0doMrzdngDbijsaAFJmsHIQh6t/QHoJx--8aMoE0PvUWmw1Iqz--ldFqnM/K0g9k17M8PKoN/Q==")
add_to_config <<-RUBY
config.read_encrypted_secrets = true
diff --git a/railties/test/test_unit/reporter_test.rb b/railties/test/test_unit/reporter_test.rb
index ad852d0f35..91cb47779b 100644
--- a/railties/test/test_unit/reporter_test.rb
+++ b/railties/test/test_unit/reporter_test.rb
@@ -163,7 +163,7 @@ class TestUnitReporterTest < ActiveSupport::TestCase
end
def failed_test
- ft = ExampleTest.new(:woot)
+ ft = Minitest::Result.from(ExampleTest.new(:woot))
ft.failures << begin
raise Minitest::Assertion, "boo"
rescue Minitest::Assertion => e
@@ -176,17 +176,17 @@ class TestUnitReporterTest < ActiveSupport::TestCase
error = ArgumentError.new("wups")
error.set_backtrace([ "some_test.rb:4" ])
- et = ExampleTest.new(:woot)
+ et = Minitest::Result.from(ExampleTest.new(:woot))
et.failures << Minitest::UnexpectedError.new(error)
et
end
def passing_test
- ExampleTest.new(:woot)
+ Minitest::Result.from(ExampleTest.new(:woot))
end
def skipped_test
- st = ExampleTest.new(:woot)
+ st = Minitest::Result.from(ExampleTest.new(:woot))
st.failures << begin
raise Minitest::Skip, "skipchurches, misstemples"
rescue Minitest::Assertion => e
diff --git a/version.rb b/version.rb
index 92b5e0392a..54bfbdd516 100644
--- a/version.rb
+++ b/version.rb
@@ -7,8 +7,8 @@ module Rails
end
module VERSION
- MAJOR = 5
- MINOR = 2
+ MAJOR = 6
+ MINOR = 0
TINY = 0
PRE = "alpha"