aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.rubocop.yml6
-rw-r--r--.travis.yml16
-rw-r--r--Gemfile15
-rw-r--r--Gemfile.lock121
-rw-r--r--actioncable/CHANGELOG.md6
-rw-r--r--actioncable/README.md9
-rw-r--r--actioncable/app/assets/javascripts/action_cable/connection.coffee2
-rwxr-xr-xactioncable/bin/test4
-rw-r--r--actioncable/lib/action_cable/channel/base.rb2
-rw-r--r--actioncable/lib/action_cable/connection/client_socket.rb4
-rw-r--r--actioncable/lib/action_cable/connection/subscriptions.rb2
-rw-r--r--actioncable/lib/action_cable/engine.rb2
-rw-r--r--actioncable/lib/action_cable/server/broadcasting.rb2
-rw-r--r--actioncable/lib/action_cable/subscription_adapter.rb1
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb26
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/evented_redis.rb2
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/redis.rb2
-rw-r--r--actioncable/lib/rails/generators/channel/channel_generator.rb4
-rw-r--r--actioncable/test/channel/periodic_timers_test.rb9
-rw-r--r--actioncable/test/javascript/src/unit/consumer_test.coffee7
-rw-r--r--actioncable/test/subscription_adapter/channel_prefix.rb36
-rw-r--r--actioncable/test/subscription_adapter/evented_redis_test.rb2
-rw-r--r--actioncable/test/subscription_adapter/redis_test.rb2
-rw-r--r--actionmailer/CHANGELOG.md25
-rw-r--r--actionmailer/lib/action_mailer.rb1
-rw-r--r--actionmailer/lib/action_mailer/base.rb47
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb2
-rw-r--r--actionmailer/lib/action_mailer/inline_preview_interceptor.rb6
-rw-r--r--actionmailer/lib/action_mailer/parameterized.rb152
-rw-r--r--actionmailer/lib/action_mailer/preview.rb2
-rw-r--r--actionmailer/lib/action_mailer/test_case.rb6
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb4
-rw-r--r--actionmailer/lib/rails/generators/mailer/mailer_generator.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb22
-rw-r--r--actionmailer/test/base_test.rb24
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb4
-rw-r--r--actionmailer/test/mail_helper_test.rb10
-rw-r--r--actionmailer/test/mailers/params_mailer.rb11
-rw-r--r--actionmailer/test/parameterized_test.rb55
-rw-r--r--actionmailer/test/test_helper_test.rb18
-rw-r--r--actionpack/CHANGELOG.md71
-rw-r--r--actionpack/lib/abstract_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/caching.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb13
-rw-r--r--actionpack/lib/action_controller/metal/head.rb2
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb2
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb6
-rw-r--r--actionpack/lib/action_controller/renderer.rb3
-rw-r--r--actionpack/lib/action_controller/test_case.rb3
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb3
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb12
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/parser.rb2
-rw-r--r--actionpack/lib/action_dispatch/journey/path/pattern.rb7
-rw-r--r--actionpack/lib/action_dispatch/journey/route.rb8
-rw-r--r--actionpack/lib/action_dispatch/journey/router.rb7
-rw-r--r--actionpack/lib/action_dispatch/journey/visitors.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb50
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb2
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb131
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb34
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb133
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb119
-rw-r--r--actionpack/lib/action_dispatch/system_testing/browser.rb28
-rw-r--r--actionpack/lib/action_dispatch/system_testing/driver.rb18
-rw-r--r--actionpack/lib/action_dispatch/system_testing/server.rb32
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb57
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb20
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb5
-rw-r--r--actionpack/test/abstract_unit.rb28
-rw-r--r--actionpack/test/controller/filters_test.rb4
-rw-r--r--actionpack/test/controller/integration_test.rb6
-rw-r--r--actionpack/test/controller/live_stream_test.rb1
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb2
-rw-r--r--actionpack/test/controller/parameters/nested_parameters_permit_test.rb5
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb17
-rw-r--r--actionpack/test/controller/redirect_test.rb4
-rw-r--r--actionpack/test/controller/render_test.rb1
-rw-r--r--actionpack/test/controller/request/test_request_test.rb20
-rw-r--r--actionpack/test/controller/test_case_test.rb21
-rw-r--r--actionpack/test/controller/url_for_test.rb2
-rw-r--r--actionpack/test/dispatch/callbacks_test.rb18
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb86
-rw-r--r--actionpack/test/dispatch/reloader_test.rb128
-rw-r--r--actionpack/test/dispatch/request_test.rb8
-rw-r--r--actionpack/test/dispatch/routing/custom_url_helpers_test.rb291
-rw-r--r--actionpack/test/dispatch/routing_test.rb2
-rw-r--r--actionpack/test/dispatch/runner_test.rb1
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb2
-rw-r--r--actionpack/test/dispatch/ssl_test.rb8
-rw-r--r--actionpack/test/dispatch/static_test.rb2
-rw-r--r--actionpack/test/dispatch/system_testing/browser_test.rb10
-rw-r--r--actionpack/test/dispatch/system_testing/driver_test.rb9
-rw-r--r--actionpack/test/dispatch/system_testing/screenshot_helper_test.rb18
-rw-r--r--actionpack/test/dispatch/system_testing/server_test.rb17
-rw-r--r--actionpack/test/dispatch/system_testing/system_test_case_test.rb21
-rw-r--r--actionpack/test/journey/path/pattern_test.rb6
-rw-r--r--actionpack/test/lib/controller/fake_models.rb4
-rw-r--r--actionview/CHANGELOG.md24
-rw-r--r--actionview/actionview.gemspec4
-rw-r--r--actionview/app/assets/javascripts/config.coffee13
-rw-r--r--actionview/app/assets/javascripts/features/remote.coffee12
-rw-r--r--actionview/app/assets/javascripts/rails-ujs.coffee9
-rw-r--r--actionview/app/assets/javascripts/utils/event.coffee4
-rw-r--r--actionview/app/assets/javascripts/utils/form.coffee27
-rw-r--r--actionview/lib/action_view/base.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb10
-rw-r--r--actionview/lib/action_view/helpers/tags/date_select.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/text_field.rb2
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb11
-rw-r--r--actionview/lib/action_view/layouts.rb2
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb10
-rw-r--r--actionview/lib/action_view/renderer/streaming_template_renderer.rb2
-rw-r--r--actionview/lib/action_view/rendering.rb6
-rw-r--r--actionview/lib/action_view/template.rb4
-rw-r--r--actionview/lib/action_view/template/handlers/builder.rb2
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb77
-rw-r--r--actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb9
-rw-r--r--actionview/lib/action_view/template/handlers/erb/erubi.rb81
-rw-r--r--actionview/lib/action_view/template/handlers/erb/erubis.rb81
-rw-r--r--actionview/lib/action_view/template/resolver.rb8
-rw-r--r--actionview/lib/action_view/test_case.rb12
-rw-r--r--actionview/lib/action_view/testing/resolvers.rb50
-rw-r--r--actionview/lib/action_view/view_paths.rb2
-rw-r--r--actionview/test/abstract_unit.rb22
-rw-r--r--actionview/test/actionpack/controller/render_test.rb38
-rw-r--r--actionview/test/activerecord/form_helper_activerecord_test.rb2
-rw-r--r--actionview/test/activerecord/polymorphic_routes_test.rb11
-rw-r--r--actionview/test/activerecord/render_partial_with_record_identification_test.rb2
-rw-r--r--actionview/test/fixtures/test/_partial_iteration_1.erb1
-rw-r--r--actionview/test/fixtures/test/_partial_iteration_2.erb1
-rw-r--r--actionview/test/fixtures/test/render_file_instance_variable.erb1
-rw-r--r--actionview/test/lib/controller/fake_models.rb17
-rw-r--r--actionview/test/template/active_model_helper_test.rb2
-rw-r--r--actionview/test/template/atom_feed_helper_test.rb2
-rw-r--r--actionview/test/template/compiled_templates_test.rb4
-rw-r--r--actionview/test/template/date_helper_test.rb1
-rw-r--r--actionview/test/template/erb/deprecated_erubis_implementation_test.rb13
-rw-r--r--actionview/test/template/erb/helper.rb2
-rw-r--r--actionview/test/template/erb/tag_helper_test.rb4
-rw-r--r--actionview/test/template/erb_util_test.rb7
-rw-r--r--actionview/test/template/form_collections_helper_test.rb3
-rw-r--r--actionview/test/template/form_helper/form_with_test.rb382
-rw-r--r--actionview/test/template/form_helper_test.rb419
-rw-r--r--actionview/test/template/form_options_helper_test.rb236
-rw-r--r--actionview/test/template/output_safety_helper_test.rb27
-rw-r--r--actionview/test/template/render_test.rb35
-rw-r--r--actionview/test/template/text_test.rb16
-rw-r--r--actionview/test/template/url_helper_test.rb15
-rw-r--r--actionview/test/ujs/public/test/call-remote-callbacks.js196
-rw-r--r--actionview/test/ujs/public/test/data-remote.js17
-rw-r--r--actionview/test/ujs/public/test/override.js2
-rw-r--r--actionview/test/ujs/public/test/settings.js10
-rw-r--r--actionview/test/ujs/public/vendor/jquery.metadata.js2
-rw-r--r--actionview/test/ujs/views/layouts/application.html.erb2
-rw-r--r--actionview/test/ujs/views/tests/index.html.erb2
-rw-r--r--activejob/CHANGELOG.md10
-rwxr-xr-xactivejob/bin/test4
-rw-r--r--activejob/lib/active_job/queue_adapter.rb1
-rw-r--r--activejob/lib/active_job/queue_adapters/test_adapter.rb10
-rw-r--r--activejob/lib/active_job/test_helper.rb81
-rw-r--r--activejob/lib/rails/generators/job/job_generator.rb2
-rw-r--r--activejob/test/cases/queue_adapter_test.rb1
-rw-r--r--activejob/test/cases/queue_naming_test.rb2
-rw-r--r--activejob/test/cases/queue_priority_test.rb2
-rw-r--r--activejob/test/cases/test_helper_test.rb49
-rw-r--r--activejob/test/helper.rb1
-rw-r--r--activejob/test/jobs/queue_adapter_job.rb3
-rw-r--r--activejob/test/support/integration/adapters/backburner.rb4
-rw-r--r--activejob/test/support/sneakers/inline.rb2
-rw-r--r--activemodel/CHANGELOG.md8
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb2
-rw-r--r--activemodel/lib/active_model/callbacks.rb1
-rw-r--r--activemodel/lib/active_model/naming.rb2
-rw-r--r--activemodel/lib/active_model/test_case.rb4
-rw-r--r--activemodel/lib/active_model/translation.rb2
-rw-r--r--activemodel/lib/active_model/type/decimal.rb3
-rw-r--r--activemodel/lib/active_model/type/integer.rb2
-rw-r--r--activemodel/lib/active_model/validations/absence.rb2
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb2
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb1
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb2
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/format.rb2
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb4
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb12
-rw-r--r--activemodel/lib/active_model/validations/presence.rb2
-rw-r--r--activemodel/lib/active_model/validations/with.rb2
-rw-r--r--activemodel/lib/active_model/validator.rb4
-rw-r--r--activemodel/test/cases/callbacks_test.rb11
-rw-r--r--activemodel/test/cases/errors_test.rb1
-rw-r--r--activemodel/test/cases/helper.rb20
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb14
-rw-r--r--activemodel/test/cases/validations/conditional_validation_test.rb20
-rw-r--r--activemodel/test/cases/validations/with_validation_test.rb20
-rw-r--r--activerecord/CHANGELOG.md122
-rw-r--r--activerecord/lib/active_record.rb3
-rw-r--r--activerecord/lib/active_record/associations.rb16
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb11
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb14
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb37
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb21
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb12
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/attribute_mutation_tracker.rb9
-rw-r--r--activerecord/lib/active_record/autosave_association.rb4
-rw-r--r--activerecord/lib/active_record/callbacks.rb49
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb64
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb70
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb45
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/column.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb37
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb39
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb13
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb60
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb66
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb59
-rw-r--r--activerecord/lib/active_record/core.rb12
-rw-r--r--activerecord/lib/active_record/counter_cache.rb3
-rw-r--r--activerecord/lib/active_record/fixtures.rb5
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb11
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb16
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb51
-rw-r--r--activerecord/lib/active_record/model_schema.rb1
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb6
-rw-r--r--activerecord/lib/active_record/persistence.rb30
-rw-r--r--activerecord/lib/active_record/railtie.rb4
-rw-r--r--activerecord/lib/active_record/railties/databases.rake24
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb105
-rw-r--r--activerecord/lib/active_record/relation.rb3
-rw-r--r--activerecord/lib/active_record/relation/batches.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb3
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb4
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb3
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb40
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb46
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/secure_token.rb2
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/lib/active_record/store.rb6
-rw-r--r--activerecord/lib/active_record/table_metadata.rb2
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/transactions.rb1
-rw-r--r--activerecord/lib/active_record/type/serialized.rb2
-rw-r--r--activerecord/lib/active_record/validations/presence.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb32
-rw-r--r--activerecord/lib/rails/generators/active_record/migration.rb8
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb4
-rw-r--r--activerecord/test/cases/adapter_test.rb101
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb5
-rw-r--r--activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb23
-rw-r--r--activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb60
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/sql_types_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/virtual_column_test.rb59
-rw-r--r--activerecord/test/cases/adapters/postgresql/bytea_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb10
-rw-r--r--activerecord/test/cases/adapters/postgresql/infinity_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb54
-rw-r--r--activerecord/test/cases/adapters/postgresql/numbers_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb13
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb22
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb56
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb7
-rw-r--r--activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb59
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb1
-rw-r--r--activerecord/test/cases/associations/eager_test.rb29
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb10
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb5
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb23
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb21
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations_test.rb5
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb2
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb7
-rw-r--r--activerecord/test/cases/autosave_association_test.rb2
-rw-r--r--activerecord/test/cases/base_test.rb7
-rw-r--r--activerecord/test/cases/calculations_test.rb6
-rw-r--r--activerecord/test/cases/callbacks_test.rb161
-rw-r--r--activerecord/test/cases/coders/yaml_column_test.rb24
-rw-r--r--activerecord/test/cases/column_definition_test.rb58
-rw-r--r--activerecord/test/cases/comment_test.rb2
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb95
-rw-r--r--activerecord/test/cases/connection_adapters/type_lookup_test.rb20
-rw-r--r--activerecord/test/cases/connection_pool_test.rb12
-rw-r--r--activerecord/test/cases/counter_cache_test.rb9
-rw-r--r--activerecord/test/cases/date_time_precision_test.rb2
-rw-r--r--activerecord/test/cases/dirty_test.rb9
-rw-r--r--activerecord/test/cases/finder_test.rb17
-rw-r--r--activerecord/test/cases/fixtures_test.rb10
-rw-r--r--activerecord/test/cases/helper.rb1
-rw-r--r--activerecord/test/cases/json_serialization_test.rb11
-rw-r--r--activerecord/test/cases/locking_test.rb7
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb7
-rw-r--r--activerecord/test/cases/migration/compatibility_test.rb113
-rw-r--r--activerecord/test/cases/migration/foreign_key_test.rb65
-rw-r--r--activerecord/test/cases/migration/index_test.rb16
-rw-r--r--activerecord/test/cases/migration/references_foreign_key_test.rb41
-rw-r--r--activerecord/test/cases/migration_test.rb20
-rw-r--r--activerecord/test/cases/migrator_test.rb24
-rw-r--r--activerecord/test/cases/nested_attributes_with_callbacks_test.rb4
-rw-r--r--activerecord/test/cases/persistence_test.rb16
-rw-r--r--activerecord/test/cases/primary_keys_test.rb175
-rw-r--r--activerecord/test/cases/query_cache_test.rb221
-rw-r--r--activerecord/test/cases/reflection_test.rb35
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb3
-rw-r--r--activerecord/test/cases/relation/mutation_test.rb4
-rw-r--r--activerecord/test/cases/relation_test.rb2
-rw-r--r--activerecord/test/cases/relations_test.rb16
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb47
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb2
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb14
-rw-r--r--activerecord/test/cases/statement_cache_test.rb26
-rw-r--r--activerecord/test/cases/tasks/database_tasks_test.rb2
-rw-r--r--activerecord/test/cases/time_precision_test.rb2
-rw-r--r--activerecord/test/cases/timestamp_test.rb2
-rw-r--r--activerecord/test/cases/transactions_test.rb16
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb4
-rw-r--r--activerecord/test/cases/view_test.rb4
-rw-r--r--activerecord/test/fixtures/subscribers.yml2
-rw-r--r--activerecord/test/models/company.rb4
-rw-r--r--activerecord/test/models/customer.rb2
-rw-r--r--activerecord/test/models/developer.rb6
-rw-r--r--activerecord/test/models/family.rb4
-rw-r--r--activerecord/test/models/family_tree.rb4
-rw-r--r--activerecord/test/models/non_primary_key.rb2
-rw-r--r--activerecord/test/models/project.rb2
-rw-r--r--activerecord/test/models/user.rb6
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb33
-rw-r--r--activerecord/test/schema/schema.rb39
-rw-r--r--activerecord/test/schema/sqlite_specific_schema.rb18
-rw-r--r--activesupport/CHANGELOG.md108
-rwxr-xr-xactivesupport/bin/generate_tables4
-rw-r--r--activesupport/lib/active_support.rb8
-rw-r--r--activesupport/lib/active_support/array_inquirer.rb4
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb5
-rw-r--r--activesupport/lib/active_support/callbacks.rb66
-rw-r--r--activesupport/lib/active_support/core_ext.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/integer/time.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb31
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb18
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb5
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb6
-rw-r--r--activesupport/lib/active_support/duration.rb99
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb6
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb4
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb4
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb4
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb2
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb8
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb2
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb8
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb9
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb26
-rw-r--r--activesupport/lib/active_support/number_helper.rb4
-rw-r--r--activesupport/lib/active_support/string_inquirer.rb2
-rw-r--r--activesupport/lib/active_support/testing/autorun.rb4
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb4
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb10
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb3
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin1068675 -> 1116857 bytes
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/libxmlsax.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogirisax.rb2
-rw-r--r--activesupport/test/abstract_unit.rb24
-rw-r--r--activesupport/test/array_inquirer_test.rb20
-rw-r--r--activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb2
-rw-r--r--activesupport/test/callbacks_test.rb85
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb4
-rw-r--r--activesupport/test/core_ext/duration_test.rb38
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb10
-rw-r--r--activesupport/test/core_ext/kernel_test.rb16
-rw-r--r--activesupport/test/core_ext/module/attribute_aliasing_test.rb4
-rw-r--r--activesupport/test/core_ext/module/introspection_test.rb37
-rw-r--r--activesupport/test/core_ext/module_test.rb36
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb14
-rw-r--r--activesupport/test/core_ext/object/blank_test.rb2
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb9
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb3
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb5
-rw-r--r--activesupport/test/dependencies_test.rb21
-rw-r--r--activesupport/test/deprecation_test.rb10
-rw-r--r--activesupport/test/evented_file_update_checker_test.rb8
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb6
-rw-r--r--activesupport/test/inflector_test.rb10
-rw-r--r--activesupport/test/json/decoding_test.rb9
-rw-r--r--activesupport/test/json/encoding_test_cases.rb6
-rw-r--r--activesupport/test/multibyte_chars_test.rb3
-rw-r--r--activesupport/test/multibyte_test_helpers.rb2
-rw-r--r--activesupport/test/number_helper_i18n_test.rb1
-rw-r--r--activesupport/test/reloader_test.rb14
-rw-r--r--activesupport/test/security_utils_test.rb7
-rw-r--r--activesupport/test/string_inquirer_test.rb21
-rw-r--r--activesupport/test/time_travel_test.rb22
-rw-r--r--activesupport/test/transliterate_test.rb18
-rwxr-xr-xci/travis.rb1
-rw-r--r--guides/Rakefile22
-rw-r--r--guides/bug_report_templates/action_controller_gem.rb2
-rw-r--r--guides/bug_report_templates/active_job_gem.rb2
-rw-r--r--guides/bug_report_templates/active_job_master.rb1
-rw-r--r--guides/bug_report_templates/active_record_gem.rb2
-rw-r--r--guides/bug_report_templates/active_record_master.rb1
-rw-r--r--guides/bug_report_templates/active_record_migrations_gem.rb2
-rw-r--r--guides/bug_report_templates/active_record_migrations_master.rb1
-rw-r--r--guides/bug_report_templates/benchmark.rb1
-rw-r--r--guides/bug_report_templates/generic_gem.rb2
-rw-r--r--guides/bug_report_templates/generic_master.rb1
-rw-r--r--guides/rails_guides.rb36
-rw-r--r--guides/rails_guides/generator.rb167
-rw-r--r--guides/rails_guides/markdown.rb15
-rw-r--r--guides/rails_guides/markdown/renderer.rb45
-rw-r--r--guides/source/5_0_release_notes.md7
-rw-r--r--guides/source/_welcome.html.erb4
-rw-r--r--guides/source/action_cable_overview.md27
-rw-r--r--guides/source/action_mailer_basics.md8
-rw-r--r--guides/source/active_record_callbacks.md14
-rw-r--r--guides/source/active_record_validations.md12
-rw-r--r--guides/source/asset_pipeline.md52
-rw-r--r--guides/source/configuring.md11
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md12
-rw-r--r--guides/source/debugging_rails_applications.md2
-rw-r--r--guides/source/development_dependencies_install.md2
-rw-r--r--guides/source/engines.md2
-rw-r--r--guides/source/getting_started.md8
-rw-r--r--guides/source/i18n.md46
-rw-r--r--guides/source/initialization.md2
-rw-r--r--guides/source/kindle/rails_guides.opf.erb2
-rw-r--r--guides/source/layouts_and_rendering.md4
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.md44
-rw-r--r--guides/source/testing.md189
-rw-r--r--guides/source/upgrading_ruby_on_rails.md12
-rw-r--r--railties/CHANGELOG.md58
-rw-r--r--railties/lib/rails.rb3
-rw-r--r--railties/lib/rails/api/generator.rb28
-rw-r--r--railties/lib/rails/api/task.rb23
-rw-r--r--railties/lib/rails/application.rb8
-rw-r--r--railties/lib/rails/application/bootstrap.rb4
-rw-r--r--railties/lib/rails/application/configuration.rb38
-rw-r--r--railties/lib/rails/application/default_middleware_stack.rb1
-rw-r--r--railties/lib/rails/application/finisher.rb1
-rw-r--r--railties/lib/rails/application/routes_reloader.rb17
-rw-r--r--railties/lib/rails/command.rb6
-rw-r--r--railties/lib/rails/command/actions.rb16
-rw-r--r--railties/lib/rails/command/base.rb2
-rw-r--r--railties/lib/rails/commands/help/USAGE16
-rw-r--r--railties/lib/rails/commands/server/server_command.rb8
-rw-r--r--railties/lib/rails/commands/test/test_command.rb2
-rw-r--r--railties/lib/rails/engine.rb13
-rw-r--r--railties/lib/rails/engine/commands.rb17
-rw-r--r--railties/lib/rails/generators.rb4
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/actions/create_migration.rb4
-rw-r--r--railties/lib/rails/generators/app_base.rb6
-rw-r--r--railties/lib/rails/generators/base.rb6
-rw-r--r--railties/lib/rails/generators/erb/mailer/mailer_generator.rb4
-rw-r--r--railties/lib/rails/generators/migration.rb2
-rw-r--r--railties/lib/rails/generators/named_base.rb10
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb25
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/yarn2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/cable.yml1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml25
-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/assets.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/locales/en.yml10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb5
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb9
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt1
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/bin/test.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb5
-rw-r--r--railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb6
-rw-r--r--railties/lib/rails/generators/rails/system_test/USAGE10
-rw-r--r--railties/lib/rails/generators/rails/system_test/system_test_generator.rb7
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/system/system_generator.rb17
-rw-r--r--railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb5
-rw-r--r--railties/lib/rails/generators/test_unit/system/templates/system_test.rb9
-rw-r--r--railties/lib/rails/info.rb6
-rw-r--r--railties/lib/rails/paths.rb4
-rw-r--r--railties/lib/rails/rack/debugger.rb3
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb2
-rw-r--r--railties/lib/rails/tasks.rb1
-rw-r--r--railties/lib/rails/tasks/framework.rake14
-rw-r--r--railties/lib/rails/tasks/routes.rake9
-rw-r--r--railties/lib/rails/tasks/statistics.rake1
-rw-r--r--railties/lib/rails/tasks/yarn.rake11
-rw-r--r--railties/lib/rails/test_help.rb25
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb47
-rw-r--r--railties/lib/rails/test_unit/railtie.rb1
-rw-r--r--railties/lib/rails/test_unit/testing.rake5
-rw-r--r--railties/test/abstract_unit.rb18
-rw-r--r--railties/test/application/bin_setup_test.rb5
-rw-r--r--railties/test/application/configuration/custom_test.rb17
-rw-r--r--railties/test/application/configuration_test.rb44
-rw-r--r--railties/test/application/mailer_previews_test.rb34
-rw-r--r--railties/test/application/rake/dbs_test.rb5
-rw-r--r--railties/test/application/rake/framework_test.rb1
-rw-r--r--railties/test/application/rake_test.rb76
-rw-r--r--railties/test/application/routing_test.rb187
-rw-r--r--railties/test/application/test_runner_test.rb36
-rw-r--r--railties/test/commands/server_test.rb6
-rw-r--r--railties/test/engine/commands_tasks_test.rb24
-rw-r--r--railties/test/engine/commands_test.rb96
-rw-r--r--railties/test/generators/api_app_generator_test.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb45
-rw-r--r--railties/test/generators/integration_test_generator_test.rb8
-rw-r--r--railties/test/generators/migration_generator_test.rb11
-rw-r--r--railties/test/generators/model_generator_test.rb11
-rw-r--r--railties/test/generators/plugin_generator_test.rb2
-rw-r--r--railties/test/generators/plugin_test_runner_test.rb11
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb6
-rw-r--r--railties/test/generators/scaffold_generator_test.rb25
-rw-r--r--railties/test/generators/shared_generator_tests.rb1
-rw-r--r--railties/test/generators/system_test_generator_test.rb12
-rw-r--r--railties/test/rack_logger_test.rb2
-rw-r--r--tools/test.rb2
597 files changed, 7226 insertions, 4024 deletions
diff --git a/.gitignore b/.gitignore
index 9268977c2f..4961ad588f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
.Gemfile
.ruby-version
+.byebug_history
debug.log
pkg
/.bundle
diff --git a/.rubocop.yml b/.rubocop.yml
index 629f5d86ea..0d1d0c36ce 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -33,6 +33,10 @@ Style/EmptyLines:
Style/EmptyLinesAroundClassBody:
Enabled: true
+# In a regular method definition, no empty lines around the body.
+Style/EmptyLinesAroundMethodBody:
+ Enabled: true
+
# In a regular module definition, no empty lines around the body.
Style/EmptyLinesAroundModuleBody:
Enabled: true
@@ -113,7 +117,7 @@ Style/UnneededPercentQ:
# assignments, where it should be aligned with the LHS.
Lint/EndAlignment:
Enabled: true
- AlignWith: variable
+ EnforcedStyleAlignWith: variable
# 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 ad65de48aa..eafa06e44f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,6 +17,7 @@ addons:
bundler_args: --without test --jobs 3 --retry 3
before_install:
- "rm ${BUNDLE_GEMFILE}.lock"
+ - "gem update --system"
- "gem update bundler"
- "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"
- "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd"
@@ -63,6 +64,12 @@ matrix:
- memcached
- redis
- rabbitmq
+ - rvm: 2.4.0
+ env: "GEM=aj:integration"
+ services:
+ - memcached
+ - redis
+ - rabbitmq
- rvm: ruby-head
env: "GEM=aj:integration"
services:
@@ -74,17 +81,20 @@ matrix:
- "GEM=ar:mysql2 MYSQL=mariadb"
addons:
mariadb: 10.0
- - rvm: jruby-9.1.5.0
+ - rvm: 2.4.0
+ env:
+ - "GEM=ar:sqlite3_mem"
+ - rvm: jruby-9.1.7.0
jdk: oraclejdk8
env:
- "GEM=ap"
- - rvm: jruby-9.1.5.0
+ - rvm: jruby-9.1.7.0
jdk: oraclejdk8
env:
- "GEM=am,aj"
allow_failures:
- rvm: ruby-head
- - rvm: jruby-9.1.5.0
+ - rvm: jruby-9.1.7.0
- env: "GEM=ac:integration"
fast_finish: true
diff --git a/Gemfile b/Gemfile
index f529677fbb..a0da9d391f 100644
--- a/Gemfile
+++ b/Gemfile
@@ -7,8 +7,6 @@ end
gemspec
-gem "arel", github: "rails/arel"
-
# We need a newish Rake since Active Job sets its test tasks' descriptions.
gem "rake", ">= 11.1"
@@ -16,6 +14,8 @@ gem "rake", ">= 11.1"
# be loaded after loading the test library.
gem "mocha", "~> 0.14", require: false
+gem "capybara", "~> 2.7.0"
+
gem "rack-cache", "~> 1.2"
gem "jquery-rails"
gem "coffee-rails"
@@ -37,7 +37,7 @@ gem "rb-inotify", github: "matthewd/rb-inotify", branch: "close-handling", requi
# Explicitly avoid 1.x that doesn't support Ruby 2.4+
gem "json", ">= 2.0.0"
-gem "rubocop", require: false
+gem "rubocop", ">= 0.47", require: false
group :doc do
gem "sdoc", "1.0.0.rc1"
@@ -51,13 +51,16 @@ gem "dalli", ">= 2.2.1"
gem "listen", ">= 3.0.5", "< 3.2", require: false
gem "libxml-ruby", platforms: :ruby
+# Action View. For testing Erubis handler deprecation.
+gem "erubis", "~> 2.7.0", require: false
+
# Active Job.
group :job do
- gem "resque", github: "resque/resque", require: false
+ gem "resque", require: false
gem "resque-scheduler", require: false
gem "sidekiq", require: false
gem "sucker_punch", require: false
- gem "delayed_job", require: false, github: "collectiveidea/delayed_job"
+ gem "delayed_job", require: false
gem "queue_classic", github: "QueueClassic/queue_classic", branch: "master", require: false, platforms: :ruby
gem "sneakers", require: false
gem "que", require: false
@@ -65,7 +68,7 @@ group :job do
#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, github: "collectiveidea/delayed_job_active_record"
+ gem "delayed_job_active_record", require: false
gem "sequel", require: false
end
diff --git a/Gemfile.lock b/Gemfile.lock
index f256186e29..ffecf4c519 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -7,21 +7,6 @@ GIT
pg (>= 0.17, < 0.20)
GIT
- remote: https://github.com/collectiveidea/delayed_job.git
- revision: e3772d4f0c8470d0fcba00c86ca3bc4f5e876830
- specs:
- delayed_job (4.1.2)
- activesupport (>= 3.0, < 5.1)
-
-GIT
- remote: https://github.com/collectiveidea/delayed_job_active_record.git
- revision: 36f434c4fd660e8f11ce932be117e9c71dde7212
- specs:
- delayed_job_active_record (4.1.1)
- activerecord (>= 3.0, < 5.1)
- delayed_job (>= 3.0, < 5)
-
-GIT
remote: https://github.com/matthewd/rb-inotify.git
revision: 90553518d1fb79aedc98a3036c59bd2b6731ac40
branch: close-handling
@@ -38,23 +23,6 @@ GIT
event_emitter
websocket
-GIT
- remote: https://github.com/rails/arel.git
- revision: b26638ef041953992010590b31615c519fa0ea7d
- specs:
- arel (8.0.0)
-
-GIT
- remote: https://github.com/resque/resque.git
- revision: 835a4a7e61a2e33832dbf11975ecfab54af293c6
- specs:
- resque (1.27.0)
- mono_logger (~> 1.0)
- multi_json (~> 1.0)
- redis-namespace (~> 1.3)
- sinatra (>= 0.9.2)
- vegas (~> 0.1.2)
-
PATH
remote: .
specs:
@@ -78,9 +46,9 @@ PATH
actionview (5.1.0.alpha)
activesupport (= 5.1.0.alpha)
builder (~> 3.1)
- erubis (~> 2.7.0)
+ erubi (~> 1.4)
rails-dom-testing (~> 2.0)
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
activejob (5.1.0.alpha)
activesupport (= 5.1.0.alpha)
globalid (>= 0.3.6)
@@ -119,7 +87,8 @@ GEM
specs:
addressable (2.5.0)
public_suffix (~> 2.0, >= 2.0.2)
- amq-protocol (2.0.1)
+ amq-protocol (2.1.0)
+ arel (8.0.0)
ast (2.3.0)
backburner (1.3.1)
beaneater (~> 1.0)
@@ -147,10 +116,17 @@ GEM
childprocess
faraday
selenium-webdriver
- builder (3.2.2)
+ builder (3.2.3)
bunny (2.6.2)
amq-protocol (>= 2.0.1)
byebug (9.0.6)
+ capybara (2.7.1)
+ addressable
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ xpath (~> 2.0)
childprocess (0.5.9)
ffi (~> 1.0, >= 1.0.11)
coffee-rails (4.2.1)
@@ -167,6 +143,11 @@ GEM
daemons (1.2.4)
dalli (2.7.6)
dante (0.2.0)
+ delayed_job (4.1.2)
+ activesupport (>= 3.0, < 5.1)
+ delayed_job_active_record (4.1.1)
+ activerecord (>= 3.0, < 5.1)
+ delayed_job (>= 3.0, < 5)
em-hiredis (0.3.1)
eventmachine (~> 1.0)
hiredis (~> 0.6.0)
@@ -178,13 +159,14 @@ GEM
http_parser.rb (>= 0.6.0)
em-socksify (0.3.1)
eventmachine (>= 1.0.0.beta.4)
+ erubi (1.4.0)
erubis (2.7.0)
event_emitter (0.2.5)
eventmachine (1.2.1)
eventmachine (1.2.1-x64-mingw32)
eventmachine (1.2.1-x86-mingw32)
execjs (2.7.0)
- faraday (0.10.0)
+ faraday (0.11.0)
multipart-post (>= 1.2, < 3)
faye (1.2.3)
cookiejar (>= 0.3.0)
@@ -197,19 +179,19 @@ GEM
faye-websocket (0.10.5)
eventmachine (>= 0.12.0)
websocket-driver (>= 0.5.1)
- ffi (1.9.14)
- ffi (1.9.14-x64-mingw32)
- ffi (1.9.14-x86-mingw32)
+ ffi (1.9.17)
+ ffi (1.9.17-x64-mingw32)
+ ffi (1.9.17-x86-mingw32)
globalid (0.3.7)
activesupport (>= 4.1.0)
hiredis (0.6.1)
http_parser.rb (0.6.0)
- i18n (0.7.0)
- jquery-rails (4.2.1)
+ i18n (0.8.0)
+ jquery-rails (4.2.2)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
- json (2.0.2)
+ json (2.0.3)
kindlerb (1.2.0)
mustache
nokogiri
@@ -239,21 +221,21 @@ GEM
mysql2 (0.4.5-x64-mingw32)
mysql2 (0.4.5-x86-mingw32)
nio4r (2.0.0)
- nokogiri (1.7.0)
+ nokogiri (1.7.0.1)
mini_portile2 (~> 2.1.0)
- nokogiri (1.7.0-x64-mingw32)
+ nokogiri (1.7.0.1-x64-mingw32)
mini_portile2 (~> 2.1.0)
- nokogiri (1.7.0-x86-mingw32)
+ nokogiri (1.7.0.1-x86-mingw32)
mini_portile2 (~> 2.1.0)
- parser (2.3.3.1)
+ parser (2.4.0.0)
ast (~> 2.2)
pg (0.19.0)
pg (0.19.0-x64-mingw32)
pg (0.19.0-x86-mingw32)
powerpack (0.1.1)
psych (2.2.2)
- public_suffix (2.0.4)
- puma (3.6.2)
+ public_suffix (2.0.5)
+ puma (3.7.0)
qu (0.2.0)
multi_json
qu-redis (0.2.0)
@@ -274,7 +256,7 @@ GEM
nokogiri (~> 1.6)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- rainbow (2.1.0)
+ rainbow (2.2.1)
rake (12.0.0)
rb-fsevent (0.9.8)
rdoc (5.0.0)
@@ -282,13 +264,19 @@ GEM
redis (3.3.2)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
+ resque (1.27.0)
+ mono_logger (~> 1.0)
+ multi_json (~> 1.0)
+ redis-namespace (~> 1.3)
+ sinatra (>= 0.9.2)
+ vegas (~> 0.1.2)
resque-scheduler (4.3.0)
mono_logger (~> 1.0)
redis (~> 3.3)
resque (~> 1.26)
rufus-scheduler (~> 3.2)
- rubocop (0.46.0)
- parser (>= 2.3.1.1, < 3.0)
+ rubocop (0.47.1)
+ parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
@@ -296,7 +284,7 @@ GEM
ruby-progressbar (1.8.1)
ruby_dep (1.5.0)
rubyzip (1.2.0)
- rufus-scheduler (3.3.1)
+ rufus-scheduler (3.3.2)
tzinfo
sass (3.4.23)
sass-rails (5.0.6)
@@ -311,10 +299,10 @@ GEM
childprocess (~> 0.5)
rubyzip (~> 1.0)
websocket (~> 1.0)
- sequel (4.41.0)
+ sequel (4.42.1)
serverengine (1.5.11)
sigdump (~> 0.2.2)
- sidekiq (4.2.7)
+ sidekiq (4.2.9)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
@@ -335,9 +323,9 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
- sqlite3 (1.3.12)
- sqlite3 (1.3.12-x64-mingw32)
- sqlite3 (1.3.12-x86-mingw32)
+ sqlite3 (1.3.13)
+ sqlite3 (1.3.13-x64-mingw32)
+ sqlite3 (1.3.13-x86-mingw32)
stackprof (0.2.10)
sucker_punch (2.0.2)
concurrent-ruby (~> 1.0.0)
@@ -358,7 +346,7 @@ GEM
tzinfo (>= 1.0.0)
uglifier (3.0.4)
execjs (>= 0.3.0, < 3)
- unicode-display_width (1.1.2)
+ unicode-display_width (1.1.3)
useragent (0.16.8)
vegas (0.1.11)
rack (>= 1.0.0)
@@ -370,6 +358,8 @@ GEM
websocket-driver (0.6.4)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
+ xpath (2.0.0)
+ nokogiri (~> 1.3)
PLATFORMS
ruby
@@ -380,18 +370,19 @@ DEPENDENCIES
activerecord-jdbcmysql-adapter (>= 1.3.0)
activerecord-jdbcpostgresql-adapter (>= 1.3.0)
activerecord-jdbcsqlite3-adapter (>= 1.3.0)
- arel!
backburner
bcrypt (~> 3.1.11)
benchmark-ips
blade
blade-sauce_labs_plugin
byebug
+ capybara (~> 2.7.0)
coffee-rails
dalli (>= 2.2.1)
- delayed_job!
- delayed_job_active_record!
+ delayed_job
+ delayed_job_active_record
em-hiredis
+ erubis (~> 2.7.0)
hiredis
jquery-rails
json (>= 2.0.0)
@@ -415,9 +406,9 @@ DEPENDENCIES
rb-inotify!
redcarpet (~> 3.2.3)
redis
- resque!
+ resque
resque-scheduler
- rubocop
+ rubocop (>= 0.47)
sass-rails
sdoc (= 1.0.0.rc1)
sequel
@@ -434,4 +425,4 @@ DEPENDENCIES
websocket-client-simple!
BUNDLED WITH
- 1.13.7
+ 1.14.4
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index 4f17274a01..7657a05077 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,3 +1,9 @@
+* Redis subscription adapters now support `channel_prefix` option in `cable.yml`
+
+ Avoids channel name collisions when multiple apps use the same Redis server.
+
+ *Chad Ingram*
+
* Permit same-origin connections by default.
Added new option `config.action_cable.allow_same_origin_as_host = false`
diff --git a/actioncable/README.md b/actioncable/README.md
index cccb55a196..c55b7dc57b 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -536,6 +536,15 @@ cable.subscriptions.create 'AppearanceChannel',
# normal channel code goes here...
```
+## Download and Installation
+
+The latest version of Action Cable can be installed with [RubyGems](#gem-usage),
+or with [npm](#npm-usage).
+
+Source code can be downloaded as part of the Rails project on GitHub
+
+* https://github.com/rails/rails/tree/master/actioncable
+
## License
Action Cable is released under the MIT license:
diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee
index 29ad676290..7fd68cad2f 100644
--- a/actioncable/app/assets/javascripts/action_cable/connection.coffee
+++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee
@@ -23,7 +23,7 @@ class ActionCable.Connection
open: =>
if @isActive()
ActionCable.log("Attempted to open WebSocket, but existing socket is #{@getState()}")
- throw new Error("Existing connection must be closed before opening")
+ false
else
ActionCable.log("Opening WebSocket, current state is #{@getState()}, subprotocols: #{protocols}")
@uninstallEventHandlers() if @webSocket?
diff --git a/actioncable/bin/test b/actioncable/bin/test
new file mode 100755
index 0000000000..a7beb14b27
--- /dev/null
+++ b/actioncable/bin/test
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+COMPONENT_ROOT = File.expand_path("..", __dir__)
+require File.expand_path("../tools/test", COMPONENT_ROOT)
diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb
index 6739a62ba0..718f630f58 100644
--- a/actioncable/lib/action_cable/channel/base.rb
+++ b/actioncable/lib/action_cable/channel/base.rb
@@ -205,7 +205,7 @@ module ActionCable
# Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with
# the proper channel identifier marked as the recipient.
def transmit(data, via: nil) # :doc:
- logger.info "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via }
+ logger.debug "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via }
payload = { channel_class: self.class.name, data: data, via: via }
ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do
diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb
index 70a2bbecb1..c7e30e78c8 100644
--- a/actioncable/lib/action_cable/connection/client_socket.rb
+++ b/actioncable/lib/action_cable/connection/client_socket.rb
@@ -90,8 +90,8 @@ module ActionCable
reason ||= ""
unless code == 1000 || (code >= 3000 && code <= 4999)
- raise ArgumentError, "Failed to execute 'close' on WebSocket: " +
- "The code must be either 1000, or between 3000 and 4999. " +
+ raise ArgumentError, "Failed to execute 'close' on WebSocket: " \
+ "The code must be either 1000, or between 3000 and 4999. " \
"#{code} is neither."
end
diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb
index abf42c99d5..44bce1e195 100644
--- a/actioncable/lib/action_cable/connection/subscriptions.rb
+++ b/actioncable/lib/action_cable/connection/subscriptions.rb
@@ -19,7 +19,7 @@ module ActionCable
logger.error "Received unrecognized command in #{data.inspect}"
end
rescue Exception => e
- logger.error "Could not execute command from #{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"
+ logger.error "Could not execute command from (#{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"
end
def add(data)
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
index e23527b84e..63a26636a0 100644
--- a/actioncable/lib/action_cable/engine.rb
+++ b/actioncable/lib/action_cable/engine.rb
@@ -31,7 +31,7 @@ module ActionCable
self.cable = Rails.application.config_for(config_path).with_indifferent_access
end
- previous_connection_class = self.connection_class
+ previous_connection_class = connection_class
self.connection_class = -> { "ApplicationCable::Connection".safe_constantize || previous_connection_class.call }
options.each { |k, v| send("#{k}=", v) }
diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb
index 1fc58baa3e..7fcd6c6587 100644
--- a/actioncable/lib/action_cable/server/broadcasting.rb
+++ b/actioncable/lib/action_cable/server/broadcasting.rb
@@ -38,7 +38,7 @@ module ActionCable
end
def broadcast(message)
- server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}"
+ server.logger.debug "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}"
payload = { broadcasting: broadcasting, message: message, coder: coder }
ActiveSupport::Notifications.instrument("broadcast.action_cable", payload) do
diff --git a/actioncable/lib/action_cable/subscription_adapter.rb b/actioncable/lib/action_cable/subscription_adapter.rb
index 72e62f3daf..596269ab9b 100644
--- a/actioncable/lib/action_cable/subscription_adapter.rb
+++ b/actioncable/lib/action_cable/subscription_adapter.rb
@@ -4,5 +4,6 @@ module ActionCable
autoload :Base
autoload :SubscriberMap
+ autoload :ChannelPrefix
end
end
diff --git a/actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb b/actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb
new file mode 100644
index 0000000000..8b293cc785
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb
@@ -0,0 +1,26 @@
+module ActionCable
+ module SubscriptionAdapter
+ module ChannelPrefix # :nodoc:
+ def broadcast(channel, payload)
+ channel = channel_with_prefix(channel)
+ super
+ end
+
+ def subscribe(channel, callback, success_callback = nil)
+ channel = channel_with_prefix(channel)
+ super
+ end
+
+ def unsubscribe(channel, callback)
+ channel = channel_with_prefix(channel)
+ super
+ end
+
+ private
+ # Returns the channel name, including channel_prefix specified in cable.yml
+ def channel_with_prefix(channel)
+ [@server.config.cable[:channel_prefix], channel].compact.join(":")
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
index c3018c5281..56b068976b 100644
--- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb
@@ -11,6 +11,8 @@ EventMachine.kqueue if EventMachine.kqueue?
module ActionCable
module SubscriptionAdapter
class EventedRedis < Base # :nodoc:
+ prepend ChannelPrefix
+
@@mutex = Mutex.new
# Overwrite this factory method for EventMachine Redis connections if you want to use a different Redis connection library than EM::Hiredis.
diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb
index 62bd284a6b..41a6e55822 100644
--- a/actioncable/lib/action_cable/subscription_adapter/redis.rb
+++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb
@@ -6,6 +6,8 @@ require "redis"
module ActionCable
module SubscriptionAdapter
class Redis < Base # :nodoc:
+ prepend ChannelPrefix
+
# Overwrite this factory method for redis connections if you want to use a different Redis library than Redis.
# This is needed, for example, when using Makara proxies for distributed Redis.
cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } }
diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb
index 04b787c3a4..984b78bc9c 100644
--- a/actioncable/lib/rails/generators/channel/channel_generator.rb
+++ b/actioncable/lib/rails/generators/channel/channel_generator.rb
@@ -13,7 +13,7 @@ module Rails
template "channel.rb", File.join("app/channels", class_path, "#{file_name}_channel.rb")
if options[:assets]
- if self.behavior == :invoke
+ if behavior == :invoke
template "assets/cable.js", "app/assets/javascripts/cable.js"
end
@@ -30,7 +30,7 @@ module Rails
# FIXME: Change these files to symlinks once RubyGems 2.5.0 is required.
def generate_application_cable_files
- return if self.behavior != :invoke
+ return if behavior != :invoke
files = [
"application_cable/channel.rb",
diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb
index 0cc4992ef6..17a8e45a35 100644
--- a/actioncable/test/channel/periodic_timers_test.rb
+++ b/actioncable/test/channel/periodic_timers_test.rb
@@ -38,23 +38,26 @@ class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase
test "disallow negative and zero periods" do
[ 0, 0.0, 0.seconds, -1, -1.seconds, "foo", :foo, Object.new ].each do |invalid|
- assert_raise ArgumentError, /Expected every:/ do
+ e = assert_raise ArgumentError do
ChatChannel.periodically :send_updates, every: invalid
end
+ assert_match(/Expected every:/, e.message)
end
end
test "disallow block and arg together" do
- assert_raise ArgumentError, /not both/ do
+ e = assert_raise ArgumentError do
ChatChannel.periodically(:send_updates, every: 1) { ping }
end
+ assert_match(/not both/, e.message)
end
test "disallow unknown args" do
[ "send_updates", Object.new, nil ].each do |invalid|
- assert_raise ArgumentError, /Expected a Symbol/ do
+ e = assert_raise ArgumentError do
ChatChannel.periodically invalid, every: 1
end
+ assert_match(/Expected a Symbol/, e.message)
end
end
diff --git a/actioncable/test/javascript/src/unit/consumer_test.coffee b/actioncable/test/javascript/src/unit/consumer_test.coffee
index cf8a592255..41445274eb 100644
--- a/actioncable/test/javascript/src/unit/consumer_test.coffee
+++ b/actioncable/test/javascript/src/unit/consumer_test.coffee
@@ -2,8 +2,11 @@
{consumerTest} = ActionCable.TestHelpers
module "ActionCable.Consumer", ->
- consumerTest "#connect", connect: false, ({consumer, server, done}) ->
- server.on("connection", done)
+ consumerTest "#connect", connect: false, ({consumer, server, assert, done}) ->
+ server.on "connection", ->
+ assert.equal consumer.connect(), false
+ done()
+
consumer.connect()
consumerTest "#disconnect", ({consumer, client, done}) ->
diff --git a/actioncable/test/subscription_adapter/channel_prefix.rb b/actioncable/test/subscription_adapter/channel_prefix.rb
new file mode 100644
index 0000000000..9ad659912e
--- /dev/null
+++ b/actioncable/test/subscription_adapter/channel_prefix.rb
@@ -0,0 +1,36 @@
+require "test_helper"
+
+class ActionCable::Server::WithIndependentConfig < ActionCable::Server::Base
+ # ActionCable::Server::Base defines config as a class variable.
+ # Need config to be an instance variable here as we're testing 2 separate configs
+ def config
+ @config ||= ActionCable::Server::Configuration.new
+ end
+end
+
+module ChannelPrefixTest
+ def test_channel_prefix
+ server2 = ActionCable::Server::WithIndependentConfig.new
+ server2.config.cable = alt_cable_config
+ server2.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
+
+ adapter_klass = server2.config.pubsub_adapter
+
+ rx_adapter2 = adapter_klass.new(server2)
+ tx_adapter2 = adapter_klass.new(server2)
+
+ subscribe_as_queue("channel") do |queue|
+ subscribe_as_queue("channel", rx_adapter2) do |queue2|
+ @tx_adapter.broadcast("channel", "hello world")
+ tx_adapter2.broadcast("channel", "hello world 2")
+
+ assert_equal "hello world", queue.pop
+ assert_equal "hello world 2", queue2.pop
+ end
+ end
+ end
+
+ def alt_cable_config
+ cable_config.merge(channel_prefix: "foo")
+ end
+end
diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb
index 2401950aa7..c55d35848e 100644
--- a/actioncable/test/subscription_adapter/evented_redis_test.rb
+++ b/actioncable/test/subscription_adapter/evented_redis_test.rb
@@ -1,8 +1,10 @@
require "test_helper"
require_relative "./common"
+require_relative "./channel_prefix"
class EventedRedisAdapterTest < ActionCable::TestCase
include CommonSubscriptionAdapterTest
+ include ChannelPrefixTest
def setup
super
diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb
index 2ba5636656..4df5e0cbcd 100644
--- a/actioncable/test/subscription_adapter/redis_test.rb
+++ b/actioncable/test/subscription_adapter/redis_test.rb
@@ -1,8 +1,10 @@
require "test_helper"
require_relative "./common"
+require_relative "./channel_prefix"
class RedisAdapterTest < ActionCable::TestCase
include CommonSubscriptionAdapterTest
+ include ChannelPrefixTest
def cable_config
{ adapter: "redis", driver: "ruby", url: "redis://127.0.0.1:6379/12" }
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 3b9f503a0b..4f99bb1b7a 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,28 @@
+* Add `:args` to `process.action_mailer` event.
+
+ *Yuji Yaginuma*
+
+* Add parameterized invocation of mailers as a way to share before filters and defaults between actions.
+ See `ActionMailer::Parameterized` for a full example of the benefit.
+
+ *DHH*
+
+* Allow lambdas to be used as lazy defaults in addition to procs.
+
+ *DHH*
+
+* Mime type: allow to custom content type when setting body in headers
+ and attachments.
+
+ Example:
+
+ def test_emails
+ attachments["invoice.pdf"] = "This is test File content"
+ mail(body: "Hello there", content_type: "text/html")
+ end
+
+ *Minh Quy*
+
* Exception handling: use `rescue_from` to handle exceptions raised by
mailer actions, by message delivery, and by deferred delivery jobs.
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index cf2c0f37e3..211190560a 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -42,6 +42,7 @@ module ActionMailer
autoload :DeliveryMethods
autoload :InlinePreviewInterceptor
autoload :MailHelper
+ autoload :Parameterized
autoload :Preview
autoload :Previews, "action_mailer/preview"
autoload :TestCase
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 19408f2a48..9b5d39faea 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -208,6 +208,19 @@ module ActionMailer
# end
# end
#
+ # You can also send attachments with html template, in this case you need to add body, attachments,
+ # and custom content type like this:
+ #
+ # class NotifierMailer < ApplicationMailer
+ # def welcome(recipient)
+ # attachments["free_book.pdf"] = File.read("path/to/file.pdf")
+ # mail(to: recipient,
+ # subject: "New account information",
+ # content_type: "text/html",
+ # body: "<html><body>Hello there</body></html>")
+ # end
+ # end
+ #
# = Inline Attachments
#
# You can also specify that a file should be displayed inline with other HTML. This is useful
@@ -275,20 +288,19 @@ module ActionMailer
# content_description: 'This is a description'
# end
#
- # Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you
- # can define methods that evaluate as the message is being generated:
+ # Finally, Action Mailer also supports passing <tt>Proc</tt> and <tt>Lambda</tt> objects into the default hash,
+ # so you can define methods that evaluate as the message is being generated:
#
# class NotifierMailer < ApplicationMailer
- # default 'X-Special-Header' => Proc.new { my_method }
+ # default 'X-Special-Header' => Proc.new { my_method }, to: -> { @inviter.email_address }
#
# private
- #
# def my_method
# 'some complex call'
# end
# end
#
- # Note that the proc is evaluated right at the start of the mail message generation, so if you
+ # Note that the proc/lambda is evaluated right at the start of the mail message generation, so if you
# set something in the default hash using a proc, and then set the same thing inside of your
# mailer method, it will get overwritten by the mailer method.
#
@@ -311,7 +323,6 @@ module ActionMailer
# end
#
# private
- #
# def add_inline_attachment!
# attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
# end
@@ -417,10 +428,11 @@ module ActionMailer
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with
# <tt>delivery_method :test</tt>. Most useful for unit and functional testing.
#
- # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>.
+ # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>. Defaults to +mailers+.
class Base < AbstractController::Base
include DeliveryMethods
include Rescuable
+ include Parameterized
include Previews
abstract!
@@ -567,7 +579,7 @@ module ActionMailer
end
def respond_to_missing?(method, include_all = false)
- action_methods.include?(method.to_s)
+ action_methods.include?(method.to_s) || super
end
end
@@ -586,7 +598,8 @@ module ActionMailer
def process(method_name, *args) #:nodoc:
payload = {
mailer: self.class.name,
- action: method_name
+ action: method_name,
+ args: args
}
ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
@@ -875,7 +888,7 @@ module ActionMailer
default_values = self.class.default.map do |key, value|
[
key,
- value.is_a?(Proc) ? instance_eval(&value) : value
+ value.is_a?(Proc) ? instance_exec(&value) : value
]
end.to_h
@@ -896,15 +909,19 @@ module ActionMailer
yield(collector)
collector.responses
elsif headers[:body]
- [{
- body: headers.delete(:body),
- content_type: self.class.default[:content_type] || "text/plain"
- }]
+ collect_responses_from_text(headers)
else
collect_responses_from_templates(headers)
end
end
+ def collect_responses_from_text(headers)
+ [{
+ body: headers.delete(:body),
+ content_type: headers[:content_type] || "text/plain"
+ }]
+ end
+
def collect_responses_from_templates(headers)
templates_path = headers[:template_path] || self.class.mailer_name
templates_name = headers[:template_name] || action_name
@@ -955,7 +972,7 @@ module ActionMailer
end
def instrument_name
- "action_mailer"
+ "action_mailer".freeze
end
ActiveSupport.run_load_hooks(:action_mailer, self)
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index be98f4c65e..bcc4ef03cf 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -59,7 +59,7 @@ module ActionMailer
end
def wrap_delivery_behavior(mail, method = nil, options = nil) # :nodoc:
- method ||= self.delivery_method
+ method ||= delivery_method
mail.delivery_handler = self
case method
diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
index 9087d335fa..980415afe0 100644
--- a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
+++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb
@@ -26,7 +26,7 @@ module ActionMailer
def transform! #:nodoc:
return message if html_part.blank?
- html_source.gsub!(PATTERN) do |match|
+ html_part.body = html_part.decoded.gsub(PATTERN) do |match|
if part = find_part(match[9..-2])
%[src="#{data_url(part)}"]
else
@@ -46,10 +46,6 @@ module ActionMailer
@html_part ||= message.html_part
end
- def html_source
- html_part.body.raw_source
- end
-
def data_url(part)
"data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"
end
diff --git a/actionmailer/lib/action_mailer/parameterized.rb b/actionmailer/lib/action_mailer/parameterized.rb
new file mode 100644
index 0000000000..3acacc1f14
--- /dev/null
+++ b/actionmailer/lib/action_mailer/parameterized.rb
@@ -0,0 +1,152 @@
+module ActionMailer
+ # Provides the option to parameterize mailers in order to share instance variable
+ # setup, processing, and common headers.
+ #
+ # Consider this example that does not use parameterization:
+ #
+ # class InvitationsMailer < ApplicationMailer
+ # def account_invitation(inviter, invitee)
+ # @account = inviter.account
+ # @inviter = inviter
+ # @invitee = invitee
+ #
+ # subject = "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
+ #
+ # mail \
+ # subject: subject,
+ # to: invitee.email_address,
+ # from: common_address(inviter),
+ # reply_to: inviter.email_address_with_name
+ # end
+ #
+ # def project_invitation(project, inviter, invitee)
+ # @account = inviter.account
+ # @project = project
+ # @inviter = inviter
+ # @invitee = invitee
+ # @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
+ #
+ # subject = "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
+ #
+ # mail \
+ # subject: subject,
+ # to: invitee.email_address,
+ # from: common_address(inviter),
+ # reply_to: inviter.email_address_with_name
+ # end
+ #
+ # def bulk_project_invitation(projects, inviter, invitee)
+ # @account = inviter.account
+ # @projects = projects.sort_by(&:name)
+ # @inviter = inviter
+ # @invitee = invitee
+ #
+ # subject = "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
+ #
+ # mail \
+ # subject: subject,
+ # to: invitee.email_address,
+ # from: common_address(inviter),
+ # reply_to: inviter.email_address_with_name
+ # end
+ # end
+ #
+ # InvitationsMailer.account_invitation(person_a, person_b).deliver_later
+ #
+ # Using parameterized mailers, this can be rewritten as:
+ #
+ # class InvitationsMailer < ApplicationMailer
+ # before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
+ # before_action { @account = params[:inviter].account }
+ #
+ # default to: -> { @invitee.email_address },
+ # from: -> { common_address(@inviter) },
+ # reply_to: -> { @inviter.email_address_with_name }
+ #
+ # def account_invitation
+ # mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})"
+ # end
+ #
+ # def project_invitation
+ # @project = params[:project]
+ # @summarizer = ProjectInvitationSummarizer.new(@project.bucket)
+ #
+ # mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})"
+ # end
+ #
+ # def bulk_project_invitation
+ # @projects = params[:projects].sort_by(&:name)
+ #
+ # mail subject: "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})"
+ # end
+ # end
+ #
+ # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
+ module Parameterized
+ extend ActiveSupport::Concern
+
+ included do
+ attr_accessor :params
+ end
+
+ module ClassMethods
+ # Provide the parameters to the mailer in order to use them in the instance methods and callbacks.
+ #
+ # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later
+ #
+ # See Parameterized documentation for full example.
+ def with(params)
+ ActionMailer::Parameterized::Mailer.new(self, params)
+ end
+ end
+
+ class Mailer # :nodoc:
+ def initialize(mailer, params)
+ @mailer, @params = mailer, params
+ end
+
+ private
+ def method_missing(method_name, *args)
+ if @mailer.action_methods.include?(method_name.to_s)
+ ActionMailer::Parameterized::MessageDelivery.new(@mailer, method_name, @params, *args)
+ else
+ super
+ end
+ end
+
+ def respond_to_missing?(method, include_all = false)
+ @mailer.respond_to?(method, include_all)
+ end
+ end
+
+ class MessageDelivery < ActionMailer::MessageDelivery # :nodoc:
+ def initialize(mailer_class, action, params, *args)
+ super(mailer_class, action, *args)
+ @params = params
+ end
+
+ private
+ def processed_mailer
+ @processed_mailer ||= @mailer_class.new.tap do |mailer|
+ mailer.params = @params
+ mailer.process @action, *@args
+ end
+ end
+
+ def enqueue_delivery(delivery_method, options = {})
+ if processed?
+ super
+ else
+ args = @mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args
+ ActionMailer::Parameterized::DeliveryJob.set(options).perform_later(*args)
+ end
+ end
+ end
+
+ class DeliveryJob < ActionMailer::DeliveryJob # :nodoc:
+ def perform(mailer, mail_method, delivery_method, params, *args)
+ mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method)
+ end
+ end
+ end
+end
diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb
index c147ca78d0..b0152aff03 100644
--- a/actionmailer/lib/action_mailer/preview.rb
+++ b/actionmailer/lib/action_mailer/preview.rb
@@ -63,7 +63,7 @@ module ActionMailer
# interceptors will be informed so that they can transform the message
# as they would if the mail was actually being delivered.
def call(email)
- preview = self.new
+ preview = new
message = preview.public_send(email)
inform_preview_interceptors(message)
message
diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb
index ebc7b37961..9ead03a40c 100644
--- a/actionmailer/lib/action_mailer/test_case.rb
+++ b/actionmailer/lib/action_mailer/test_case.rb
@@ -4,8 +4,8 @@ require "rails-dom-testing"
module ActionMailer
class NonInferrableMailerError < ::StandardError
def initialize(name)
- super "Unable to determine the mailer to test from #{name}. " +
- "You'll need to specify it using tests YourMailer in your " +
+ super "Unable to determine the mailer to test from #{name}. " \
+ "You'll need to specify it using tests YourMailer in your " \
"test case definition"
end
end
@@ -57,7 +57,7 @@ module ActionMailer
end
def mailer_class
- if mailer = self._mailer_class
+ if mailer = _mailer_class
mailer
else
tests determine_default_mailer(name)
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index c17ecad4c6..c30fb1fc18 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -88,7 +88,7 @@ module ActionMailer
# end
# end
def assert_enqueued_emails(number, &block)
- assert_enqueued_jobs number, only: ActionMailer::DeliveryJob, &block
+ assert_enqueued_jobs number, only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block
end
# Asserts that no emails are enqueued for later delivery.
@@ -107,7 +107,7 @@ module ActionMailer
# end
# end
def assert_no_enqueued_emails(&block)
- assert_no_enqueued_jobs only: ActionMailer::DeliveryJob, &block
+ assert_no_enqueued_jobs only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block
end
end
end
diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
index 4a8d0178de..99fe4544f1 100644
--- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
+++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb
@@ -11,7 +11,7 @@ module Rails
template "mailer.rb", File.join("app/mailers", class_path, "#{file_name}_mailer.rb")
in_root do
- if self.behavior == :invoke && !File.exist?(application_mailer_file_name)
+ if behavior == :invoke && !File.exist?(application_mailer_file_name)
template "application_mailer.rb", application_mailer_file_name
end
end
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 82f03f5cba..a646cbd581 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -3,8 +3,8 @@ require "active_support/core_ext/kernel/reporting"
# These are the normal settings that will be set up by Railties
# TODO: Have these tests support other combinations of these values
silence_warnings do
- Encoding.default_internal = "UTF-8"
- Encoding.default_external = "UTF-8"
+ Encoding.default_internal = Encoding::UTF_8
+ Encoding.default_external = Encoding::UTF_8
end
module Rails
@@ -31,15 +31,15 @@ I18n.enforce_available_locales = false
FIXTURE_LOAD_PATH = File.expand_path("fixtures", File.dirname(__FILE__))
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
-# Skips the current run on Rubinius using Minitest::Assertions#skip
-def rubinius_skip(message = "")
- skip message if RUBY_ENGINE == "rbx"
-end
-# Skips the current run on JRuby using Minitest::Assertions#skip
-def jruby_skip(message = "")
- skip message if defined?(JRUBY_VERSION)
-end
-
class ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
+
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
+ private def rubinius_skip(message = "")
+ skip message if RUBY_ENGINE == "rbx"
+ end
+ # Skips the current run on JRuby using Minitest::Assertions#skip
+ private def jruby_skip(message = "")
+ skip message if defined?(JRUBY_VERSION)
+ end
end
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 91049e33f9..61960d411d 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -140,6 +140,11 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("multipart/mixed", email.mime_type)
end
+ test "set mime type to text/html when attachment is included and body is set" do
+ email = BaseMailer.attachment_with_content(body: "Hello there", content_type: "text/html")
+ assert_equal("text/html", email.mime_type)
+ end
+
test "adds the rendered template as part" do
email = BaseMailer.attachment_with_content
assert_equal(2, email.parts.length)
@@ -829,6 +834,25 @@ class BaseTest < ActiveSupport::TestCase
assert_equal "special indeed!", mail["X-Special-Header"].to_s
end
+ test "notification for process" do
+ begin
+ events = []
+ ActiveSupport::Notifications.subscribe("process.action_mailer") do |*args|
+ events << ActiveSupport::Notifications::Event.new(*args)
+ end
+
+ BaseMailer.welcome(body: "Hello there").deliver_now
+
+ assert_equal 1, events.length
+ assert_equal "process.action_mailer", events[0].name
+ assert_equal "BaseMailer", events[0].payload[:mailer]
+ assert_equal :welcome, events[0].payload[:action]
+ assert_equal [{ body: "Hello there" }], events[0].payload[:args]
+ ensure
+ ActiveSupport::Notifications.unsubscribe "process.action_mailer"
+ end
+ end
+
private
# Execute the block setting the given values and restoring old values after
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index a87a9c9861..4f09339800 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -57,9 +57,7 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
stub_any_instance(Mail::SMTP, instance: Mail::SMTP.new({})) do |instance|
assert_called(instance, :deliver!) do
with_translation "de", email_subject: "[Anmeldung] Willkommen" do
- ActiveSupport::Deprecation.silence do
- get "/test/send_mail"
- end
+ get "/test/send_mail"
assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body
end
end
diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb
index a2e04b5f48..6042548aef 100644
--- a/actionmailer/test/mail_helper_test.rb
+++ b/actionmailer/test/mail_helper_test.rb
@@ -2,11 +2,11 @@ require "abstract_unit"
class HelperMailer < ActionMailer::Base
def use_mail_helper
- @text = "But soft! What light through yonder window breaks? It is the east, " +
- "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " +
- "which is sick and pale with grief that thou, her maid, art far more " +
- "fair than she. Be not her maid, for she is envious! Her vestal " +
- "livery is but sick and green, and none but fools do wear it. Cast " +
+ @text = "But soft! What light through yonder window breaks? It is the east, " \
+ "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " \
+ "which is sick and pale with grief that thou, her maid, art far more " \
+ "fair than she. Be not her maid, for she is envious! Her vestal " \
+ "livery is but sick and green, and none but fools do wear it. Cast " \
"it off!"
mail_with_defaults do |format|
diff --git a/actionmailer/test/mailers/params_mailer.rb b/actionmailer/test/mailers/params_mailer.rb
new file mode 100644
index 0000000000..4c0fae6d91
--- /dev/null
+++ b/actionmailer/test/mailers/params_mailer.rb
@@ -0,0 +1,11 @@
+class ParamsMailer < ActionMailer::Base
+ before_action { @inviter, @invitee = params[:inviter], params[:invitee] }
+
+ default to: Proc.new { @invitee }, from: -> { @inviter }
+
+ def invitation
+ mail(subject: "Welcome to the project!") do |format|
+ format.text { render plain: "So says #{@inviter}" }
+ end
+ end
+end
diff --git a/actionmailer/test/parameterized_test.rb b/actionmailer/test/parameterized_test.rb
new file mode 100644
index 0000000000..914ed12312
--- /dev/null
+++ b/actionmailer/test/parameterized_test.rb
@@ -0,0 +1,55 @@
+require "abstract_unit"
+require "active_job"
+require "mailers/params_mailer"
+
+class ParameterizedTest < ActiveSupport::TestCase
+ include ActiveJob::TestHelper
+
+ setup do
+ @previous_logger = ActiveJob::Base.logger
+ ActiveJob::Base.logger = Logger.new(nil)
+
+ @previous_delivery_method = ActionMailer::Base.delivery_method
+ ActionMailer::Base.delivery_method = :test
+
+ @previous_deliver_later_queue_name = ActionMailer::Base.deliver_later_queue_name
+ ActionMailer::Base.deliver_later_queue_name = :test_queue
+ ActionMailer::Base.delivery_method = :test
+
+ @mail = ParamsMailer.with(inviter: "david@basecamp.com", invitee: "jason@basecamp.com").invitation
+ end
+
+ teardown do
+ ActiveJob::Base.logger = @previous_logger
+ ParamsMailer.deliveries.clear
+
+ ActionMailer::Base.delivery_method = @previous_delivery_method
+ ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name
+ end
+
+ test "parameterized headers" do
+ assert_equal(["jason@basecamp.com"], @mail.to)
+ assert_equal(["david@basecamp.com"], @mail.from)
+ assert_equal("So says david@basecamp.com", @mail.body.encoded)
+ end
+
+ test "enqueue the email with params" do
+ assert_performed_with(job: ActionMailer::Parameterized::DeliveryJob, args: ["ParamsMailer", "invitation", "deliver_now", { inviter: "david@basecamp.com", invitee: "jason@basecamp.com" } ]) do
+ @mail.deliver_later
+ end
+ end
+
+ test "respond_to?" do
+ mailer = ParamsMailer.with(inviter: "david@basecamp.com", invitee: "jason@basecamp.com")
+
+ assert_respond_to mailer, :invitation
+ assert_not_respond_to mailer, :anything
+
+ invitation = mailer.method(:invitation)
+ assert_equal Method, invitation.class
+
+ assert_raises(NameError) do
+ invitation = mailer.method(:anything)
+ end
+ end
+end
diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb
index 31ac5a5211..876e9b0634 100644
--- a/actionmailer/test/test_helper_test.rb
+++ b/actionmailer/test/test_helper_test.rb
@@ -143,6 +143,16 @@ class TestHelperMailerTest < ActionMailer::TestCase
end
end
+ def test_assert_enqueued_parameterized_emails
+ assert_nothing_raised do
+ assert_enqueued_emails 1 do
+ silence_stream($stdout) do
+ TestHelperMailer.with(a: 1).test.deliver_later
+ end
+ end
+ end
+ end
+
def test_assert_enqueued_emails_too_few_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_emails 2 do
@@ -176,6 +186,14 @@ class TestHelperMailerTest < ActionMailer::TestCase
end
end
+ def test_assert_no_enqueued_parameterized_emails
+ assert_nothing_raised do
+ assert_no_enqueued_emails do
+ TestHelperMailer.with(a: 1).test.deliver_now
+ end
+ end
+ end
+
def test_assert_no_enqueued_emails_failure
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_no_enqueued_emails do
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index a5f6426f21..0167dcbf96 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,72 @@
+* Prefer `remove_method` over `undef_method` when reloading routes
+
+ When `undef_method` is used it prevents access to other implementations of that
+ url helper in the ancestor chain so use `remove_method` instead to restore access.
+
+ *Andrew White*
+
+* Add the `resolve` method to the routing DSL
+
+ This new method allows customization of the polymorphic mapping of models:
+
+ ``` ruby
+ resource :basket
+ resolve(class: "Basket") { [:basket] }
+ ```
+
+ ``` erb
+ <%= form_for @basket do |form| %>
+ <!-- basket form -->
+ <% end %>
+ ```
+
+ This generates the correct singular URL for the form instead of the default
+ resources member url, e.g. `/basket` vs. `/basket/:id`.
+
+ Fixes #1769.
+
+ *Andrew White*
+
+* Add the `direct` method to the routing DSL
+
+ This new method allows creation of custom url helpers, e.g:
+
+ ``` ruby
+ direct(:apple) { "http://www.apple.com" }
+
+ >> apple_url
+ => "http://www.apple.com"
+ ```
+
+ This has the advantage of being available everywhere url helpers are available
+ unlike custom url helpers defined in helper modules, etc.
+
+ *Andrew White*
+
+* Add `ActionDispatch::SystemTestCase` to Action Pack
+
+ Adds Capybara integration directly into Rails through Action Pack!
+
+ See PR [#26703](https://github.com/rails/rails/pull/26703)
+
+ *Eileen M. Uchitelle*
+
+* Remove deprecated `.to_prepare`, `.to_cleanup`, `.prepare!` and `.cleanup!` from `ActionDispatch::Reloader`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `ActionDispatch::Callbacks.to_prepare` and `ActionDispatch::Callbacks.to_cleanup`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `ActionController::Metal.call`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `ActionController::Metal#env`.
+
+ *Rafael Mendonça França*
+
* Make `with_routing` test helper work when testing controllers inheriting from `ActionController::API`
*Julia López*
@@ -19,7 +88,7 @@
*Tawan Sierek*
-* Fixes incorrect output from rails routes when using singular resources.
+* Fixes incorrect output from `rails routes` when using singular resources.
Fixes #26606.
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 603c2e9ea7..e7cb6347a2 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,4 +1,3 @@
-require "erubis"
require "abstract_controller/error"
require "active_support/configurable"
require "active_support/descendants_tracker"
@@ -22,7 +21,6 @@ module AbstractController
include ActiveSupport::Configurable
extend ActiveSupport::DescendantsTracker
- undef_method :not_implemented
class << self
attr_reader :abstract
alias_method :abstract?, :abstract
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index a9a8508abc..954265ad97 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -38,7 +38,7 @@ module ActionController
end
def instrument_name
- "action_controller"
+ "action_controller".freeze
end
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 9dab7aeef4..337718afc0 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -118,11 +118,6 @@ module ActionController
class Metal < AbstractController::Base
abstract!
- def env
- @_request.env
- end
- deprecate :env
-
# Returns the last part of the controller's name, underscored, without the ending
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
# Namespaces are left out, so Admin::PostsController returns <tt>posts</tt> as well.
@@ -232,14 +227,6 @@ module ActionController
middleware_stack
end
- # Makes the controller a Rack endpoint that runs the action in the given
- # +env+'s +action_dispatch.request.path_parameters+ key.
- def self.call(env)
- req = ActionDispatch::Request.new env
- action(req.path_parameters[:action]).call(env)
- end
- class << self; deprecate :call; end
-
# Returns a Rack endpoint for the given action name.
def self.action(name)
if middleware_stack.any?
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 4dff23dd85..0c50894bce 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -37,7 +37,7 @@ module ActionController
if include_content?(response_code)
self.content_type = content_type || (Mime[formats.first] if formats)
- self.response.charset = false
+ response.charset = false
end
true
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 8615c16c6f..dde924e682 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/string/strip"
-
module ActionController
# Handles implicit rendering for a controller action that does not
# explicitly respond with +render+, +respond_to+, +redirect+, or +head+.
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index 30798c1d99..a349841082 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -50,13 +50,13 @@ module ActionController
# redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
# redirect_to({ action: 'atom' }, alert: "Something serious happened")
#
- def redirect_to(options = {}, response_status = {}) #:doc:
+ def redirect_to(options = {}, response_status = {})
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(request, options)
- self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(location)}\">redirected</a>.</body></html>"
+ self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
end
# Redirects the browser to the page that issued the request (the referrer)
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 4c01891d4c..6b17719381 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -114,7 +114,7 @@ module ActionController
self.status = status if status
self.content_type = content_type if content_type
- self.headers["Location"] = url_for(location) if location
+ headers["Location"] = url_for(location) if location
super
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index acfeca1fcb..d304dcf468 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -150,7 +150,7 @@ module ActionController
# permitted flag.
def ==(other)
if other.respond_to?(:permitted?)
- self.permitted? == other.permitted? && self.parameters == other.parameters
+ permitted? == other.permitted? && parameters == other.parameters
else
@parameters == other
end
@@ -398,8 +398,7 @@ module ActionController
unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
- params.permitted = true
- params
+ params.permit!
end
# Returns a parameter for the given +key+. If not found,
@@ -818,7 +817,6 @@ module ActionController
# Filter this one out.
end
end
- sanitized.permitted = true
end
end
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
index 3ff80e6a39..acb400cd15 100644
--- a/actionpack/lib/action_controller/renderer.rb
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -60,7 +60,8 @@ module ActionController
end
# Accepts a custom Rack environment to render templates in.
- # It will be merged with ActionController::Renderer.defaults
+ # It will be merged with the default Rack environment defined by
+ # +ActionController::Renderer::DEFAULTS+.
def initialize(controller, env, defaults)
@controller = controller
@defaults = defaults
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 441667e556..7229c67f30 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -354,7 +354,7 @@ module ActionController
end
def controller_class
- if current_controller_class = self._controller_class
+ if current_controller_class = _controller_class
current_controller_class
else
self.controller_class = determine_default_controller_class(name)
@@ -534,6 +534,7 @@ module ActionController
@request.delete_header "HTTP_ACCEPT"
end
@request.query_string = ""
+ @request.env.delete "PATH_INFO"
@response.sent!
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 028177ace2..303790e96d 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -97,6 +97,8 @@ module ActionDispatch
autoload :TestResponse
autoload :AssertionResponse
end
+
+ autoload :SystemTestCase, "action_dispatch/system_test_case"
end
autoload :Mime, "action_dispatch/http/mime_type"
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 6b718e3682..1583a8f87f 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,7 +1,6 @@
# -*- frozen-string-literal: true -*-
require "singleton"
-require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/string/starts_ends_with"
module Mime
@@ -298,7 +297,7 @@ module Mime
end
def respond_to_missing?(method, include_private = false)
- method.to_s.ends_with? "?"
+ (method.to_s.ends_with? "?") || super
end
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index ad4aadacf5..8f21eca440 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -22,6 +22,7 @@ module ActionDispatch
included do
class << self
+ # Returns the parameter parsers.
attr_reader :parameter_parsers
end
@@ -29,7 +30,16 @@ module ActionDispatch
end
module ClassMethods
- def parameter_parsers=(parsers) # :nodoc:
+ # Configure the parameter parser for a given mime type.
+ #
+ # It accepts a hash where the key is the symbol of the mime type
+ # and the value is a proc.
+ #
+ # original_parsers = ActionDispatch::Request.parameter_parsers
+ # xml_parser = -> (raw_post) { Hash.from_xml(raw_post) || {} }
+ # new_parsers = original_parsers.merge(xml: xml_parser)
+ # ActionDispatch::Request.parameter_parsers = new_parsers
+ def parameter_parsers=(parsers)
@parameter_parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key }
end
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 516a2af69a..dc159596c4 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -425,7 +425,7 @@ module ActionDispatch # :nodoc:
def set_content_type(content_type, charset)
type = (content_type || "").dup
- type << "; charset=#{charset}" if charset
+ type << "; charset=#{charset.to_s.downcase}" if charset
set_header CONTENT_TYPE, type
end
diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb
index db42b64c4b..e002755bcf 100644
--- a/actionpack/lib/action_dispatch/journey/parser.rb
+++ b/actionpack/lib/action_dispatch/journey/parser.rb
@@ -1,7 +1,7 @@
#
# DO NOT MODIFY!!!!
# This file is automatically generated by Racc 1.4.14
-# from Racc grammer file "".
+# from Racc grammar file "".
#
require 'racc/parser.rb'
diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb
index 0902b9233e..cf0108ec32 100644
--- a/actionpack/lib/action_dispatch/journey/path/pattern.rb
+++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb
@@ -31,6 +31,13 @@ module ActionDispatch
Visitors::FormatBuilder.new.accept(spec)
end
+ def eager_load!
+ required_names
+ offsets
+ to_regexp
+ nil
+ end
+
def ast
@spec.find_all(&:symbol?).each do |node|
re = @requirements[node.to_sym]
diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb
index f2ac4818d8..927fd369c4 100644
--- a/actionpack/lib/action_dispatch/journey/route.rb
+++ b/actionpack/lib/action_dispatch/journey/route.rb
@@ -73,6 +73,14 @@ module ActionDispatch
@internal = internal
end
+ def eager_load!
+ path.eager_load!
+ ast
+ parts
+ required_defaults
+ nil
+ end
+
def ast
@decorated_ast ||= begin
decorated_ast = path.ast
diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb
index 084ae9325e..d55e1399e4 100644
--- a/actionpack/lib/action_dispatch/journey/router.rb
+++ b/actionpack/lib/action_dispatch/journey/router.rb
@@ -22,6 +22,13 @@ module ActionDispatch
@routes = routes
end
+ def eager_load!
+ # Eagerly trigger the simulator's initialization so
+ # it doesn't happen during a request cycle.
+ simulator
+ nil
+ end
+
def serve(req)
find_routes(req).each do |match, parameters, route|
set_params = req.path_parameters
diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb
index cda859cba4..1c50192867 100644
--- a/actionpack/lib/action_dispatch/journey/visitors.rb
+++ b/actionpack/lib/action_dispatch/journey/visitors.rb
@@ -5,7 +5,7 @@ module ActionDispatch
ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) }
ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) }
- class Parameter < Struct.new(:name, :escaper)
+ Parameter = Struct.new(:name, :escaper) do
def escape(value); escaper.call value; end
end
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index fef246532b..ff129cf96a 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -1,4 +1,3 @@
-
module ActionDispatch
# Provides callbacks to be executed before and after dispatching the request.
class Callbacks
@@ -7,17 +6,6 @@ module ActionDispatch
define_callbacks :call
class << self
- def to_prepare(*args, &block)
- ActiveSupport::Reloader.to_prepare(*args, &block)
- end
-
- def to_cleanup(*args, &block)
- ActiveSupport::Reloader.to_complete(*args, &block)
- end
-
- deprecate to_prepare: "use ActiveSupport::Reloader.to_prepare instead",
- to_cleanup: "use ActiveSupport::Reloader.to_complete instead"
-
def before(*args, &block)
set_callback(:call, :before, *args, &block)
end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 956c53e813..c61cb3fd68 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -572,7 +572,7 @@ module ActionDispatch
super
if ActiveSupport::LegacyKeyGenerator === key_generator
- raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " +
+ raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " \
"Read the upgrade documentation to learn more about this new config option."
end
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 90c64037aa..6d64b1424b 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -1,54 +1,10 @@
module ActionDispatch
- # ActionDispatch::Reloader provides prepare and cleanup callbacks,
- # intended to assist with code reloading during development.
- #
- # Prepare callbacks are run before each request, and cleanup callbacks
- # after each request. In this respect they are analogs of ActionDispatch::Callback's
- # before and after callbacks. However, cleanup callbacks are not called until the
- # request is fully complete -- that is, after #close has been called on
- # the response body. This is important for streaming responses such as the
- # following:
- #
- # self.response_body = -> (response, output) do
- # # code here which refers to application models
- # end
- #
- # Cleanup callbacks will not be called until after the response_body lambda
- # is evaluated, ensuring that it can refer to application models and other
- # classes before they are unloaded.
+ # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader
+ # callbacks, intended to assist with code reloading during development.
#
# By default, ActionDispatch::Reloader is included in the middleware stack
# only in the development environment; specifically, when +config.cache_classes+
- # is false. Callbacks may be registered even when it is not included in the
- # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
- # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
- #
+ # is false.
class Reloader < Executor
- def self.to_prepare(*args, &block)
- ActiveSupport::Reloader.to_prepare(*args, &block)
- end
-
- def self.to_cleanup(*args, &block)
- ActiveSupport::Reloader.to_complete(*args, &block)
- end
-
- def self.prepare!
- default_reloader.prepare!
- end
-
- def self.cleanup!
- default_reloader.reload!
- end
-
- class << self
- attr_accessor :default_reloader # :nodoc:
-
- deprecate to_prepare: "use ActiveSupport::Reloader.to_prepare instead",
- to_cleanup: "use ActiveSupport::Reloader.to_complete instead",
- prepare!: "use Rails.application.reloader.prepare! instead",
- cleanup!: "use Rails.application.reloader.reload! instead of cleanup + prepare"
- end
-
- self.default_reloader = ActiveSupport::Reloader
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index 9f1ae80b97..8bae5bfeff 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -131,8 +131,8 @@ module ActionDispatch
should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
if should_check_ip && !forwarded_ips.include?(client_ips.last)
# We don't know which came from the proxy, and which from the user
- raise IpSpoofAttackError, "IP spoofing attack?! " +
- "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " +
+ raise IpSpoofAttackError, "IP spoofing attack?! " \
+ "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
"HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 97c937b0b1..d9f018c8ac 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -8,8 +8,8 @@ module ActionDispatch
module Session
class SessionRestoreError < StandardError #:nodoc:
def initialize
- super("Session contains objects whose class definition isn't available.\n" +
- "Remember to require the classes for all objects kept in the session.\n" +
+ super("Session contains objects whose class definition isn't available.\n" \
+ "Remember to require the classes for all objects kept in the session.\n" \
"(Original exception: #{$!.message} [#{$!.class}])\n")
set_backtrace $!.backtrace
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 5c71f0fc48..5d10129d21 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -33,7 +33,7 @@ module ActionDispatch
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
if match = paths.detect { |p|
- path = File.join(@root, p.force_encoding("UTF-8".freeze))
+ path = File.join(@root, p.force_encoding(Encoding::UTF_8))
begin
File.file?(path) && File.readable?(path)
rescue SystemCallError
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 48cc91bbfa..16a18a7f25 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -39,8 +39,6 @@ module ActionDispatch
config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
- ActionDispatch::Reloader.default_reloader = app.reloader
-
ActionDispatch.test_app = app
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 089aa9f78e..073dabd0a8 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,6 +1,7 @@
require "active_support/core_ext/hash/slice"
require "active_support/core_ext/enumerable"
require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/regexp"
require "action_dispatch/routing/redirection"
require "action_dispatch/routing/endpoint"
@@ -238,7 +239,7 @@ module ActionDispatch
options[:controller] ||= /.+?/
end
- if to.respond_to? :call
+ if to.respond_to?(:action) || to.respond_to?(:call)
options
else
to_endpoint = split_to to
@@ -290,16 +291,14 @@ module ActionDispatch
end
def app(blocks)
- if to.is_a?(Class) && to < ActionController::Metal
+ if to.respond_to?(:action)
Routing::RouteSet::StaticDispatcher.new to
+ elsif to.respond_to?(:call)
+ Constraints.new(to, blocks, Constraints::CALL)
+ elsif blocks.any?
+ Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
else
- if to.respond_to?(:call)
- Constraints.new(to, blocks, Constraints::CALL)
- elsif blocks.any?
- Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
- else
- dispatcher(defaults.key?(:controller))
- end
+ dispatcher(defaults.key?(:controller))
end
end
@@ -2021,6 +2020,111 @@ module ActionDispatch
end
end
+ module CustomUrls
+ # 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:
+ #
+ # direct :homepage do
+ # "http://www.rubyonrails.org"
+ # end
+ #
+ # direct :commentable do |model|
+ # [ model, anchor: model.dom_id ]
+ # end
+ #
+ # direct :main do
+ # { controller: 'pages', action: 'index', subdomain: 'www' }
+ # end
+ #
+ # The return value from the block passed to `direct` must be a valid set of
+ # 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
+ #
+ # 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
+ #
+ # You can also specify default options that will be passed through to
+ # your url helper definition, e.g:
+ #
+ # direct :browse, page: 1, size: 10 do |options|
+ # [ :products, options.merge(params.permit(:page, :size)) ]
+ # end
+ #
+ # NOTE: The `direct` method can't be used inside of a scope block such as
+ # `namespace` or `scope` and will raise an error if it detects that it is.
+ def direct(name, options = {}, &block)
+ unless @scope.root?
+ raise RuntimeError, "The direct method can't be used inside a routes scope block"
+ end
+
+ @set.add_url_helper(name, options, &block)
+ end
+
+ # Define custom polymorphic mappings of models to urls. This alters the
+ # behavior of `polymorphic_url` and consequently the behavior of
+ # `link_to` and `form_for` when passed a model instance, e.g:
+ #
+ # resource :basket
+ #
+ # resolve "Basket" do
+ # [:basket]
+ # end
+ #
+ # This will now generate "/basket" when a `Basket` instance is passed to
+ # `link_to` or `form_for` instead of the standard "/baskets/:id".
+ #
+ # NOTE: This custom behavior only applies to simple polymorphic urls where
+ # a single model instance is passed and not more complicated forms, e.g:
+ #
+ # # config/routes.rb
+ # resource :profile
+ # namespace :admin do
+ # resources :users
+ # end
+ #
+ # resolve("User") { [:profile] }
+ #
+ # # app/views/application/_menu.html.erb
+ # link_to "Profile", @current_user
+ # link_to "Profile", [:admin, @current_user]
+ #
+ # The first `link_to` will generate "/profile" but the second will generate
+ # the standard polymorphic url of "/admin/users/1".
+ #
+ # You can pass options to a polymorphic mapping - the arity for the block
+ # needs to be two as the instance is passed as the first argument, e.g:
+ #
+ # direct class: "Basket", anchor: "items" do |basket, options|
+ # [:basket, options]
+ # end
+ #
+ # This generates the url "/basket#items" because when the last item in an
+ # array passed to `polymorphic_url` is a hash then it's treated as options
+ # to the url helper that gets called.
+ #
+ # NOTE: The `resolve` method can't be used inside of a scope block such as
+ # `namespace` or `scope` and will raise an error if it detects that it is.
+ def resolve(*args, &block)
+ unless @scope.root?
+ raise RuntimeError, "The resolve method can't be used inside a routes scope block"
+ end
+
+ options = args.extract_options!
+ args = args.flatten(1)
+
+ args.each do |klass|
+ @set.add_polymorphic_mapping(klass, options, &block)
+ end
+ end
+ end
+
class Scope # :nodoc:
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
:controller, :action, :path_names, :constraints,
@@ -2041,6 +2145,14 @@ module ActionDispatch
scope_level == :nested
end
+ def null?
+ @hash.nil? && @parent.nil?
+ end
+
+ def root?
+ @parent.null?
+ end
+
def resources?
scope_level == :resources
end
@@ -2114,6 +2226,7 @@ module ActionDispatch
include Scoping
include Concerns
include Resources
+ include CustomUrls
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 432b9bf4c1..984ded1ff5 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -103,6 +103,10 @@ module ActionDispatch
return polymorphic_url record, options
end
+ if mapping = polymorphic_mapping(record_or_hash_or_array)
+ return mapping.call(self, [record_or_hash_or_array, options])
+ end
+
opts = options.dup
action = opts.delete :action
type = opts.delete(:routing_type) || :url
@@ -123,6 +127,10 @@ module ActionDispatch
return polymorphic_path record, options
end
+ if mapping = polymorphic_mapping(record_or_hash_or_array)
+ return mapping.call(self, [record_or_hash_or_array, options], only_path: true)
+ end
+
opts = options.dup
action = opts.delete :action
type = :path
@@ -156,6 +164,14 @@ module ActionDispatch
polymorphic_path(record_or_hash, options.merge(action: action))
end
+ def polymorphic_mapping(record)
+ if record.respond_to?(:to_model)
+ _routes.polymorphic_mappings[record.to_model.model_name.name]
+ else
+ _routes.polymorphic_mappings[record.class.name]
+ end
+ end
+
class HelperMethodBuilder # :nodoc:
CACHE = { "path" => {}, "url" => {} }
@@ -255,9 +271,13 @@ module ActionDispatch
[named_route, args]
end
- def handle_model_call(target, model)
- method, args = handle_model model
- target.send(method, *args)
+ def handle_model_call(target, record)
+ if mapping = polymorphic_mapping(target, record)
+ mapping.call(target, [record], only_path: suffix == "path")
+ else
+ method, args = handle_model(record)
+ target.send(method, *args)
+ end
end
def handle_list(list)
@@ -303,6 +323,14 @@ module ActionDispatch
private
+ def polymorphic_mapping(target, record)
+ if record.respond_to?(:to_model)
+ target._routes.polymorphic_mappings[record.to_model.model_name.name]
+ else
+ target._routes.polymorphic_mappings[record.class.name]
+ end
+ end
+
def get_method_for_class(klass)
name = @key_strategy.call klass.model_name
get_method_for_string name
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 4e2318a45e..dabc045007 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -137,6 +137,9 @@ module ActionDispatch
#
# get "/stories" => redirect("/posts")
#
+ # This will redirect the user, while ignoring certain parts of the request, including query string, etc.
+ # `/stories`, `/stories?foo=bar`, etc all redirect to `/posts`.
+ #
# You can also use interpolation in the supplied redirect argument:
#
# get 'docs/:article', to: redirect('/wiki/%{article}')
@@ -165,6 +168,11 @@ module ActionDispatch
#
# get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}')
# get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}')
+ # get '/stories', to: redirect(path: '/posts')
+ #
+ # This will redirect the user, while changing only the specified parts of the request,
+ # for example the `path` option in the last example.
+ # `/stories`, `/stories?foo=bar`, redirect to `/posts` and `/posts?foo=bar` respectively.
#
# Finally, an object which responds to call can be supplied to redirect, allowing you to reuse
# common redirect routes. The call method must accept two arguments, params and request, and return
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 5853adb110..2672cd24ed 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -73,6 +73,7 @@ module ActionDispatch
@routes = {}
@path_helpers = Set.new
@url_helpers = Set.new
+ @custom_helpers = Set.new
@url_helpers_module = Module.new
@path_helpers_module = Module.new
end
@@ -88,16 +89,30 @@ module ActionDispatch
def clear!
@path_helpers.each do |helper|
- @path_helpers_module.send :undef_method, helper
+ @path_helpers_module.send :remove_method, helper
end
@url_helpers.each do |helper|
- @url_helpers_module.send :undef_method, helper
+ @url_helpers_module.send :remove_method, helper
+ end
+
+ @custom_helpers.each do |helper|
+ path_name = :"#{helper}_path"
+ url_name = :"#{helper}_url"
+
+ if @path_helpers_module.method_defined?(path_name)
+ @path_helpers_module.send :remove_method, path_name
+ end
+
+ if @url_helpers_module.method_defined?(url_name)
+ @url_helpers_module.send :remove_method, url_name
+ end
end
@routes.clear
@path_helpers.clear
@url_helpers.clear
+ @custom_helpers.clear
end
def add(name, route)
@@ -143,6 +158,23 @@ module ActionDispatch
routes.length
end
+ def add_url_helper(name, defaults, &block)
+ @custom_helpers << name
+ helper = CustomUrlHelper.new(name, defaults, &block)
+
+ @path_helpers_module.module_eval do
+ define_method(:"#{name}_path") do |*args|
+ helper.call(self, args, only_path: true)
+ end
+ end
+
+ @url_helpers_module.module_eval do
+ define_method(:"#{name}_url") do |*args|
+ helper.call(self, args)
+ end
+ end
+ end
+
class UrlHelper
def self.create(route, options, route_name, url_strategy)
if optimize_helper?(route)
@@ -305,7 +337,7 @@ module ActionDispatch
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options
- attr_reader :env_key
+ attr_reader :env_key, :polymorphic_mappings
alias :routes :set
@@ -347,6 +379,13 @@ module ActionDispatch
@set = Journey::Routes.new
@router = Journey::Router.new @set
@formatter = Journey::Formatter.new self
+ @polymorphic_mappings = {}
+ end
+
+ def eager_load!
+ router.eager_load!
+ routes.each(&:eager_load!)
+ nil
end
def relative_url_root
@@ -402,6 +441,7 @@ module ActionDispatch
named_routes.clear
set.clear
formatter.clear
+ @polymorphic_mappings.clear
@prepend.each { |blk| eval_block(blk) }
end
@@ -446,17 +486,42 @@ module ActionDispatch
# Define url_for in the singleton level so one can do:
# Rails.application.routes.url_helpers.url_for(args)
- @_routes = routes
+ proxy_class = Class.new do
+ include UrlFor
+ include routes.named_routes.path_helpers_module
+ include routes.named_routes.url_helpers_module
+
+ attr_reader :_routes
+
+ def initialize(routes)
+ @_routes = routes
+ end
+
+ def optimize_routes_generation?
+ @_routes.optimize_routes_generation?
+ end
+ end
+
+ @_proxy = proxy_class.new(routes)
+
class << self
def url_for(options)
- @_routes.url_for(options)
+ @_proxy.url_for(options)
end
def optimize_routes_generation?
- @_routes.optimize_routes_generation?
+ @_proxy.optimize_routes_generation?
end
- attr_reader :_routes
+ def polymorphic_url(record_or_hash_or_array, options = {})
+ @_proxy.polymorphic_url(record_or_hash_or_array, options)
+ end
+
+ def polymorphic_path(record_or_hash_or_array, options = {})
+ @_proxy.polymorphic_path(record_or_hash_or_array, options)
+ end
+
+ def _routes; @_proxy._routes; end
def url_options; {}; end
end
@@ -517,20 +582,70 @@ module ActionDispatch
if route.segment_keys.include?(:controller)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Using a dynamic :controller segment in a route is deprecated and
- will be removed in Rails 5.1.
+ will be removed in Rails 5.2.
MSG
end
if route.segment_keys.include?(:action)
ActiveSupport::Deprecation.warn(<<-MSG.squish)
Using a dynamic :action segment in a route is deprecated and
- will be removed in Rails 5.1.
+ will be removed in Rails 5.2.
MSG
end
route
end
+ def add_polymorphic_mapping(klass, options, &block)
+ @polymorphic_mappings[klass] = CustomUrlHelper.new(klass, options, &block)
+ end
+
+ def add_url_helper(name, options, &block)
+ named_routes.add_url_helper(name, options, &block)
+ end
+
+ class CustomUrlHelper
+ attr_reader :name, :defaults, :block
+
+ def initialize(name, defaults, &block)
+ @name = name
+ @defaults = defaults
+ @block = block
+ end
+
+ def call(t, args, outer_options = {})
+ options = args.extract_options!
+ url_options = eval_block(t, args, options)
+
+ case url_options
+ when String
+ t.url_for(url_options)
+ when Hash
+ t.url_for(url_options.merge(outer_options))
+ when ActionController::Parameters
+ if url_options.permitted?
+ t.url_for(url_options.to_h.merge(outer_options))
+ else
+ raise ArgumentError, "Generating a URL from non sanitized request parameters is insecure!"
+ end
+ when Array
+ opts = url_options.extract_options!
+ t.url_for(url_options.push(opts.merge(outer_options)))
+ else
+ t.url_for([url_options, outer_options])
+ end
+ end
+
+ private
+ def eval_block(t, args, options)
+ t.instance_exec(*args, merge_defaults(options), &block)
+ end
+
+ def merge_defaults(options)
+ defaults ? defaults.merge(options) : options
+ end
+ end
+
class Generator
PARAMETERIZE = lambda do |name, value|
if name == :controller
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
new file mode 100644
index 0000000000..99c2be0a35
--- /dev/null
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -0,0 +1,119 @@
+require "capybara/dsl"
+require "action_controller"
+require "action_dispatch/system_testing/driver"
+require "action_dispatch/system_testing/server"
+require "action_dispatch/system_testing/browser"
+require "action_dispatch/system_testing/test_helpers/screenshot_helper"
+require "action_dispatch/system_testing/test_helpers/setup_and_teardown"
+
+module ActionDispatch
+ class SystemTestCase < IntegrationTest
+ # = System Testing
+ #
+ # System tests let you test applications in the browser. Because system
+ # tests use a real browser experience, you can test all of your JavaScript
+ # easily from your test suite.
+ #
+ # To create a system test in your application, extend your test class
+ # from <tt>ApplicationSystemTestCase</tt>. System tests use Capybara as a
+ # base and allow you to configure the settings through your
+ # <tt>application_system_test_case.rb</tt> file that is generated with a new
+ # application or scaffold.
+ #
+ # Here is an example system test:
+ #
+ # require 'application_system_test_case'
+ #
+ # class Users::CreateTest < ApplicationSystemTestCase
+ # test "adding a new user" do
+ # visit users_path
+ # click_on 'New User'
+ #
+ # fill_in 'Name', with: 'Arya'
+ # click_on 'Create User'
+ #
+ # assert_text 'Arya'
+ # end
+ # end
+ #
+ # When generating an application or scaffold, an +application_system_test_case.rb+
+ # file will also be generated containing the base class for system testing.
+ # This is where you can change the driver, add Capybara settings, and other
+ # configuration for your system tests.
+ #
+ # require "test_helper"
+ #
+ # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ # driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
+ # end
+ #
+ # By default, <tt>ActionDispatch::SystemTestCase</tt> is driven by the
+ # Selenium driver, with the Chrome browser, and a browser size of 1400x1400.
+ #
+ # Changing the driver configuration options are easy. Let's say you want to use
+ # the Firefox browser instead of Chrome. In your +application_system_test_case.rb+
+ # file add the following:
+ #
+ # require "test_helper"
+ #
+ # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ # driven_by :selenium, using: :firefox
+ # end
+ #
+ # +driven_by+ has a required argument for the driver name. The keyword
+ # arguments are +:using+ for the browser and +:screen_size+ to change the
+ # size of the browser screen. These two options are not applicable for
+ # headless drivers and will be silently ignored if passed.
+ #
+ # 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 the +:using+
+ # option because the driver is headless.
+ #
+ # require "test_helper"
+ # require "capybara/poltergeist"
+ #
+ # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ # driven_by :poltergeist
+ # end
+ #
+ # Because <tt>ActionDispatch::SystemTestCase</tt> is a shim between Capybara
+ # and Rails, any driver that is supported by Capybara is supported by system
+ # tests as long as you include the required gems and files.
+ include Capybara::DSL
+ include SystemTesting::TestHelpers::SetupAndTeardown
+ include SystemTesting::TestHelpers::ScreenshotHelper
+
+ def self.start_application # :nodoc:
+ Capybara.app = Rack::Builder.new do
+ map "/" do
+ run Rails.application
+ end
+ end
+ end
+
+ # System Test configuration options
+ #
+ # The default settings are Selenium, using Chrome, with a screen size
+ # of 1400x1400.
+ #
+ # Examples:
+ #
+ # driven_by :poltergeist
+ #
+ # driven_by :selenium, using: :firefox
+ #
+ # driven_by :selenium, screen_size: [800, 800]
+ def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400])
+ SystemTesting::Driver.new(driver).run
+ SystemTesting::Server.new.run
+ SystemTesting::Browser.new(using, screen_size).run if selenium?(driver)
+ end
+
+ def self.selenium?(driver) # :nodoc:
+ driver == :selenium
+ end
+ end
+
+ SystemTestCase.start_application
+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..c9a6628516
--- /dev/null
+++ b/actionpack/lib/action_dispatch/system_testing/browser.rb
@@ -0,0 +1,28 @@
+module ActionDispatch
+ module SystemTesting
+ class Browser # :nodoc:
+ def initialize(name, screen_size)
+ @name = name
+ @screen_size = screen_size
+ end
+
+ def run
+ register
+ setup
+ end
+
+ private
+ def register
+ Capybara.register_driver @name do |app|
+ Capybara::Selenium::Driver.new(app, browser: @name).tap do |driver|
+ driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size)
+ end
+ end
+ end
+
+ def setup
+ Capybara.default_driver = @name.to_sym
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/system_testing/driver.rb b/actionpack/lib/action_dispatch/system_testing/driver.rb
new file mode 100644
index 0000000000..7c2ad84e19
--- /dev/null
+++ b/actionpack/lib/action_dispatch/system_testing/driver.rb
@@ -0,0 +1,18 @@
+module ActionDispatch
+ module SystemTesting
+ class Driver # :nodoc:
+ def initialize(name)
+ @name = name
+ end
+
+ def run
+ register
+ end
+
+ private
+ def register
+ Capybara.default_driver = @name
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/system_testing/server.rb b/actionpack/lib/action_dispatch/system_testing/server.rb
new file mode 100644
index 0000000000..4a214ef713
--- /dev/null
+++ b/actionpack/lib/action_dispatch/system_testing/server.rb
@@ -0,0 +1,32 @@
+require "rack/handler/puma"
+
+module ActionDispatch
+ module SystemTesting
+ class Server # :nodoc:
+ def run
+ register
+ setup
+ end
+
+ private
+ def register
+ Capybara.register_server :rails_puma do |app, port, host|
+ Rack::Handler::Puma.run(app, Port: port, Threads: "0:1")
+ end
+ end
+
+ def setup
+ set_server
+ set_port
+ end
+
+ def set_server
+ Capybara.server = :rails_puma
+ end
+
+ def set_port
+ Capybara.always_include_port = true
+ end
+ end
+ end
+end
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
new file mode 100644
index 0000000000..784005cb93
--- /dev/null
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb
@@ -0,0 +1,57 @@
+module ActionDispatch
+ module SystemTesting
+ module TestHelpers
+ # Screenshot helper for system testing
+ module ScreenshotHelper
+ # Takes a screenshot of the current page in the browser.
+ #
+ # +take_screenshot+ can be used at any point in your system tests to take
+ # a screenshot of the current state. This can be useful for debugging or
+ # automating visual testing.
+ def take_screenshot
+ save_image
+ puts "[Screenshot]: #{image_path}"
+ puts display_image
+ end
+
+ # Takes a screenshot of the current page in the browser if the test
+ # failed.
+ #
+ # +take_failed_screenshot+ is included in <tt>application_system_test_case.rb</tt>
+ # that is generated with the application. To take screenshots when a test
+ # fails add +take_failed_screenshot+ to the teardown block before clearing
+ # sessions.
+ def take_failed_screenshot
+ take_screenshot unless passed?
+ end
+
+ private
+ def image_name
+ passed? ? method_name : "failures_#{method_name}"
+ end
+
+ def image_path
+ "tmp/screenshots/#{image_name}.png"
+ end
+
+ def save_image
+ page.save_screenshot(Rails.root.join(image_path))
+ end
+
+ def display_image
+ if ENV["CAPYBARA_INLINE_SCREENSHOT"] == "artifact"
+ "\e]1338;url=artifact://#{image_path}\a"
+ else
+ name = inline_base64(File.basename(image_path))
+ image = inline_base64(File.read(image_path))
+ "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a"
+ end
+ end
+
+ def inline_base64(path)
+ Base64.encode64(path).gsub("\n", "")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
new file mode 100644
index 0000000000..491559eedf
--- /dev/null
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
@@ -0,0 +1,20 @@
+module ActionDispatch
+ module SystemTesting
+ module TestHelpers
+ module SetupAndTeardown # :nodoc:
+ DEFAULT_HOST = "127.0.0.1"
+
+ def before_setup
+ host! DEFAULT_HOST
+ super
+ end
+
+ def after_teardown
+ super
+ take_failed_screenshot
+ Capybara.reset_sessions!
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb
index b362931ef7..4ea18d671d 100644
--- a/actionpack/lib/action_dispatch/testing/assertions.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions.rb
@@ -12,7 +12,7 @@ module ActionDispatch
include Rails::Dom::Testing::Assertions
def html_document
- @html_document ||= if @response.content_type.to_s =~ /xml\z/
+ @html_document ||= if @response.content_type.to_s.end_with?("xml")
Nokogiri::XML::Document.parse(@response.body)
else
Nokogiri::HTML::Document.parse(@response.body)
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 021ffec862..5fa0b727ab 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -2,7 +2,6 @@ require "stringio"
require "uri"
require "active_support/core_ext/kernel/singleton_class"
require "active_support/core_ext/object/try"
-require "active_support/core_ext/string/strip"
require "rack/test"
require "minitest"
@@ -145,8 +144,8 @@ module ActionDispatch
self.host = DEFAULT_HOST
self.remote_addr = "127.0.0.1"
- self.accept = "text/xml,application/xml,application/xhtml+xml," +
- "text/html;q=0.9,text/plain;q=0.8,image/png," +
+ self.accept = "text/xml,application/xml,application/xhtml+xml," \
+ "text/html;q=0.9,text/plain;q=0.8,image/png," \
"*/*;q=0.5"
unless defined? @named_routes_configured
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 11a9092527..459b0d6c54 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -7,8 +7,8 @@ require "active_support/core_ext/kernel/reporting"
# These are the normal settings that will be set up by Railties
# TODO: Have these tests support other combinations of these values
silence_warnings do
- Encoding.default_internal = "UTF-8"
- Encoding.default_external = "UTF-8"
+ Encoding.default_internal = Encoding::UTF_8
+ Encoding.default_external = Encoding::UTF_8
end
require "drb"
@@ -259,9 +259,9 @@ module ActionDispatch
host = uri_or_host.host unless path
path ||= uri_or_host.path
- params = { "PATH_INFO" => path,
- "REQUEST_METHOD" => method,
- "HTTP_HOST" => host }
+ params = { "PATH_INFO" => path,
+ "REQUEST_METHOD" => method,
+ "HTTP_HOST" => host }
routes.call(params)
end
@@ -354,15 +354,6 @@ class CommentsController < ResourcesController; end
class AccountsController < ResourcesController; end
class ImagesController < ResourcesController; end
-# Skips the current run on Rubinius using Minitest::Assertions#skip
-def rubinius_skip(message = "")
- skip message if RUBY_ENGINE == "rbx"
-end
-# Skips the current run on JRuby using Minitest::Assertions#skip
-def jruby_skip(message = "")
- skip message if defined?(JRUBY_VERSION)
-end
-
require "active_support/testing/method_call_assertions"
class ForkingExecutor
@@ -438,4 +429,13 @@ end
class ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
+
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
+ private def rubinius_skip(message = "")
+ skip message if RUBY_ENGINE == "rbx"
+ end
+ # Skips the current run on JRuby using Minitest::Assertions#skip
+ private def jruby_skip(message = "")
+ skip message if defined?(JRUBY_VERSION)
+ end
end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index f9701585a9..5f1463cfa8 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -584,7 +584,7 @@ class FilterTest < ActionController::TestCase
assert @controller.instance_variable_get(:@was_audited)
end
- def test_running_anomolous_yet_valid_condition_actions
+ def test_running_anomalous_yet_valid_condition_actions
test_process(AnomolousYetValidConditionController)
assert_equal %w( ensure_login ), @controller.instance_variable_get(:@ran_filter)
assert @controller.instance_variable_get(:@ran_class_action)
@@ -704,7 +704,7 @@ class FilterTest < ActionController::TestCase
def test_prepending_and_appending_around_action
test_process(MixedFilterController)
- assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
+ assert_equal " before aroundfilter before procfilter before appended aroundfilter " \
" after appended aroundfilter after procfilter after aroundfilter ",
MixedFilterController.execution_log
end
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 4fae4071b1..57f58fd835 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -269,8 +269,8 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
test "response cookies are added to the cookie jar for the next request" do
with_test_route_set do
- self.cookies["cookie_1"] = "sugar"
- self.cookies["cookie_2"] = "oatmeal"
+ cookies["cookie_1"] = "sugar"
+ cookies["cookie_2"] = "oatmeal"
get "/cookie_monster"
assert_equal "cookie_1=; path=/\ncookie_3=chocolate; path=/", headers["Set-Cookie"]
assert_equal({ "cookie_1" => "", "cookie_2" => "oatmeal", "cookie_3" => "chocolate" }, cookies.to_hash)
@@ -494,7 +494,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
assert_includes @response.headers, "c"
end
- def test_accept_not_overriden_when_xhr_true
+ def test_accept_not_overridden_when_xhr_true
with_test_route_set do
get "/get", headers: { "Accept" => "application/json" }, xhr: true
assert_equal "application/json", request.accept
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index e76628b936..581081dd07 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -1,4 +1,5 @@
require "abstract_unit"
+require "timeout"
require "concurrent/atomic/count_down_latch"
Thread.abort_on_exception = true
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
index 818dc119eb..61bd5c80c4 100644
--- a/actionpack/test/controller/mime/respond_to_test.rb
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -519,7 +519,7 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal "Whatever you ask for, I got it", @response.body
end
- def test_handle_any_any_unkown_format
+ def test_handle_any_any_unknown_format
get :handle_any_any, format: "php"
assert_equal "Whatever you ask for, I got it", @response.body
end
diff --git a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb
index 5f86901e30..00e591d5a7 100644
--- a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb
@@ -140,6 +140,11 @@ class NestedParametersPermitTest < ActiveSupport::TestCase
assert_equal "William Shakespeare", permitted[:book][:authors_attributes]["0"][:name]
assert_equal "Unattributed Assistant", permitted[:book][:authors_attributes]["1"][:name]
+ assert_equal(
+ { "book" => { "authors_attributes" => { "0" => { "name" => "William Shakespeare" }, "1" => { "name" => "Unattributed Assistant" }, "2" => {} } } },
+ permitted.to_h
+ )
+
assert_filtered_out permitted[:book][:authors_attributes]["0"], :age_of_death
end
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index b62a3d6d7b..8920914af1 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -66,12 +66,20 @@ class ParametersPermitTest < ActiveSupport::TestCase
values.each do |value|
params = ActionController::Parameters.new(id: value)
permitted = params.permit(:id)
- assert_equal value, permitted[:id]
+ if value.nil?
+ assert_nil permitted[:id]
+ else
+ assert_equal value, permitted[:id]
+ end
@struct_fields.each do |sf|
params = ActionController::Parameters.new(sf => value)
permitted = params.permit(:sf)
- assert_equal value, permitted[sf]
+ if value.nil?
+ assert_nil permitted[sf]
+ else
+ assert_equal value, permitted[sf]
+ end
end
end
end
@@ -187,11 +195,6 @@ class ParametersPermitTest < ActiveSupport::TestCase
permitted = params.permit(:username, preferences: {}, hacked: {})
- assert permitted.permitted?
- assert permitted[:preferences].permitted?
- assert permitted[:preferences][:font].permitted?
- assert permitted[:preferences][:dubious].all?(&:permitted?)
-
assert_equal "fxn", permitted[:username]
assert_equal "Marazul", permitted[:preferences][:scheme]
assert_equal "Source Code Pro", permitted[:preferences][:font][:name]
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index e4e968dfdb..f06a1f4d23 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -21,8 +21,8 @@ end
class RedirectController < ActionController::Base
# empty method not used anywhere to ensure methods like
# `status` and `location` aren't called on `redirect_to` calls
- def status; render plain: "called status"; end
- def location; render plain: "called location"; end
+ def status; raise "Should not be called!"; end
+ def location; raise "Should not be called!"; end
def simple_redirect
redirect_to action: "hello_world"
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 0c0f18f200..3a0a0a8bde 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -313,7 +313,6 @@ class ExpiresInRenderTest < ActionController::TestCase
end
def test_permitted_dynamic_render_file_hash
- skip "FIXME: this test passes on 4-2-stable but not master. Why?"
assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb"))
response = get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } }
assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")),
diff --git a/actionpack/test/controller/request/test_request_test.rb b/actionpack/test/controller/request/test_request_test.rb
index b67ae72c0a..1440db00f6 100644
--- a/actionpack/test/controller/request/test_request_test.rb
+++ b/actionpack/test/controller/request/test_request_test.rb
@@ -21,16 +21,20 @@ class ActionController::TestRequestTest < ActionController::TestCase
@request.get_header("CONTENT_LENGTH"))
end
- ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS.each_key do |option|
- test "rack default session options #{option} exists in session options and is default" do
- assert_equal(ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS[option],
- @request.session_options[option],
- "Missing rack session default option #{option} in request.session_options")
+ ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS.each_pair do |key, value|
+ test "rack default session options #{key} exists in session options and is default" do
+ if value.nil?
+ assert_nil(@request.session_options[key],
+ "Missing rack session default option #{key} in request.session_options")
+ else
+ assert_equal(value, @request.session_options[key],
+ "Missing rack session default option #{key} in request.session_options")
+ end
end
- test "rack default session options #{option} exists in session options" do
- assert(@request.session_options.has_key?(option),
- "Missing rack session option #{option} in request.session_options")
+ test "rack default session options #{key} exists in session options" do
+ assert(@request.session_options.has_key?(key),
+ "Missing rack session option #{key} in request.session_options")
end
end
end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
index 874f9c3c42..891ce0e905 100644
--- a/actionpack/test/controller/test_case_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -134,7 +134,7 @@ XML
end
def create
- head :created, location: "created resource"
+ head :created, location: "/resource"
end
def render_cookie
@@ -728,6 +728,20 @@ XML
assert_equal "text/html", @response.body
end
+ def test_request_path_info_and_format_reset
+ get :test_format, format: "json"
+ assert_equal "application/json", @response.body
+
+ get :test_uri, format: "json"
+ assert_equal "/test_case_test/test/test_uri.json", @response.body
+
+ get :test_format
+ assert_equal "text/html", @response.body
+
+ get :test_uri
+ assert_equal "/test_case_test/test/test_uri", @response.body
+ end
+
def test_request_format_kwarg_overrides_params
get :test_format, format: "json", params: { format: "html" }
assert_equal "application/json", @response.body
@@ -796,7 +810,6 @@ XML
new_content_type = "new content_type"
file.content_type = new_content_type
assert_equal new_content_type, file.content_type
-
end
def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case
@@ -880,12 +893,12 @@ XML
assert_response :created
# Redirect url doesn't care that it wasn't a :redirect response.
- assert_equal "created resource", @response.redirect_url
+ assert_equal "/resource", @response.redirect_url
assert_equal @response.redirect_url, redirect_to_url
# Must be a :redirect response.
assert_raise(ActiveSupport::TestCase::Assertion) do
- assert_redirected_to "created resource"
+ assert_redirected_to "/resource"
end
end
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 382b4e273d..862dcf01c3 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -26,7 +26,7 @@ module AbstractController
action: :index
}
}.url_helpers
- self.default_url_options[:host] = "example.com"
+ default_url_options[:host] = "example.com"
}
path = klass.new.fun_path(controller: :articles,
diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb
index 57e21a22c6..29a5dfc0ad 100644
--- a/actionpack/test/dispatch/callbacks_test.rb
+++ b/actionpack/test/dispatch/callbacks_test.rb
@@ -35,24 +35,6 @@ class DispatcherTest < ActiveSupport::TestCase
assert_equal 6, Foo.b
end
- def test_to_prepare_and_cleanup_delegation
- prepared = cleaned = false
- assert_deprecated do
- ActionDispatch::Callbacks.to_prepare { prepared = true }
- ActionDispatch::Callbacks.to_prepare { cleaned = true }
- end
-
- assert_deprecated do
- ActionDispatch::Reloader.prepare!
- end
- assert prepared
-
- assert_deprecated do
- ActionDispatch::Reloader.cleanup!
- end
- assert cleaned
- end
-
private
def dispatch(&block)
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index bb2fc53add..0e093d2188 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -22,8 +22,6 @@ module TestGenerationPrefix
end
class WithMountedEngine < ActionDispatch::IntegrationTest
- include Rack::Test::Methods
-
class BlogEngine < Rails::Engine
routes.draw do
get "/posts/:id", to: "inside_engine_generating#show", as: :post
@@ -153,114 +151,114 @@ module TestGenerationPrefix
# Inside Engine
test "[ENGINE] generating engine's url use SCRIPT_NAME from request" do
get "/pure-awesomeness/blog/posts/1"
- assert_equal "/pure-awesomeness/blog/posts/1", last_response.body
+ assert_equal "/pure-awesomeness/blog/posts/1", response.body
end
test "[ENGINE] generating application's url never uses SCRIPT_NAME from request" do
get "/pure-awesomeness/blog/url_to_application"
- assert_equal "/generate", last_response.body
+ assert_equal "/generate", response.body
end
test "[ENGINE] generating engine's url with polymorphic path" do
get "/pure-awesomeness/blog/polymorphic_path_for_engine"
- assert_equal "/pure-awesomeness/blog/posts/1", last_response.body
+ assert_equal "/pure-awesomeness/blog/posts/1", response.body
end
test "[ENGINE] url_helpers from engine have higher priority than application's url_helpers" do
get "/awesome/blog/conflicting_url"
- assert_equal "engine", last_response.body
+ assert_equal "engine", response.body
end
test "[ENGINE] relative path root uses SCRIPT_NAME from request" do
get "/awesome/blog/relative_path_root"
- verify_redirect "http://example.org/awesome/blog"
+ verify_redirect "http://www.example.com/awesome/blog"
end
test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do
get "/awesome/blog/relative_path_redirect"
- verify_redirect "http://example.org/awesome/blog/foo"
+ verify_redirect "http://www.example.com/awesome/blog/foo"
end
test "[ENGINE] relative option root uses SCRIPT_NAME from request" do
get "/awesome/blog/relative_option_root"
- verify_redirect "http://example.org/awesome/blog"
+ verify_redirect "http://www.example.com/awesome/blog"
end
test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do
get "/awesome/blog/relative_option_redirect"
- verify_redirect "http://example.org/awesome/blog/foo"
+ verify_redirect "http://www.example.com/awesome/blog/foo"
end
test "[ENGINE] relative custom root uses SCRIPT_NAME from request" do
get "/awesome/blog/relative_custom_root"
- verify_redirect "http://example.org/awesome/blog"
+ verify_redirect "http://www.example.com/awesome/blog"
end
test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do
get "/awesome/blog/relative_custom_redirect"
- verify_redirect "http://example.org/awesome/blog/foo"
+ verify_redirect "http://www.example.com/awesome/blog/foo"
end
test "[ENGINE] absolute path root doesn't use SCRIPT_NAME from request" do
get "/awesome/blog/absolute_path_root"
- verify_redirect "http://example.org/"
+ verify_redirect "http://www.example.com/"
end
test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do
get "/awesome/blog/absolute_path_redirect"
- verify_redirect "http://example.org/foo"
+ verify_redirect "http://www.example.com/foo"
end
test "[ENGINE] absolute option root doesn't use SCRIPT_NAME from request" do
get "/awesome/blog/absolute_option_root"
- verify_redirect "http://example.org/"
+ verify_redirect "http://www.example.com/"
end
test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do
get "/awesome/blog/absolute_option_redirect"
- verify_redirect "http://example.org/foo"
+ verify_redirect "http://www.example.com/foo"
end
test "[ENGINE] absolute custom root doesn't use SCRIPT_NAME from request" do
get "/awesome/blog/absolute_custom_root"
- verify_redirect "http://example.org/"
+ verify_redirect "http://www.example.com/"
end
test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do
get "/awesome/blog/absolute_custom_redirect"
- verify_redirect "http://example.org/foo"
+ verify_redirect "http://www.example.com/foo"
end
# Inside Application
test "[APP] generating engine's route includes prefix" do
get "/generate"
- assert_equal "/awesome/blog/posts/1", last_response.body
+ assert_equal "/awesome/blog/posts/1", response.body
end
test "[APP] generating engine's route includes default_url_options[:script_name]" do
RailsApplication.routes.default_url_options = { script_name: "/something" }
get "/generate"
- assert_equal "/something/awesome/blog/posts/1", last_response.body
+ assert_equal "/something/awesome/blog/posts/1", response.body
end
test "[APP] generating engine's url with polymorphic path" do
get "/polymorphic_path_for_engine"
- assert_equal "/awesome/blog/posts/1", last_response.body
+ assert_equal "/awesome/blog/posts/1", response.body
end
test "polymorphic_path_for_app" do
get "/polymorphic_path_for_app"
- assert_equal "/posts/1", last_response.body
+ assert_equal "/posts/1", response.body
end
test "[APP] generating engine's url with url_for(@post)" do
get "/polymorphic_with_url_for"
- assert_equal "http://example.org/awesome/blog/posts/1", last_response.body
+ assert_equal "http://www.example.com/awesome/blog/posts/1", response.body
end
test "[APP] instance variable with same name as engine" do
get "/ivar_usage"
- assert_equal "/awesome/blog/posts/1", last_response.body
+ assert_equal "/awesome/blog/posts/1", response.body
end
# Inside any Object
@@ -322,9 +320,9 @@ module TestGenerationPrefix
private
def verify_redirect(url, status = 301)
- assert_equal status, last_response.status
- assert_equal url, last_response.headers["Location"]
- assert_equal expected_redirect_body(url), last_response.body
+ assert_equal status, response.status
+ assert_equal url, response.headers["Location"]
+ assert_equal expected_redirect_body(url), response.body
end
def expected_redirect_body(url)
@@ -333,8 +331,6 @@ module TestGenerationPrefix
end
class EngineMountedAtRoot < ActionDispatch::IntegrationTest
- include Rack::Test::Methods
-
class BlogEngine
def self.routes
@routes ||= begin
@@ -388,74 +384,74 @@ module TestGenerationPrefix
test "generating path inside engine" do
get "/posts/1"
- assert_equal "/posts/1", last_response.body
+ assert_equal "/posts/1", response.body
end
test "[ENGINE] relative path root uses SCRIPT_NAME from request" do
get "/relative_path_root"
- verify_redirect "http://example.org/"
+ verify_redirect "http://www.example.com/"
end
test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do
get "/relative_path_redirect"
- verify_redirect "http://example.org/foo"
+ verify_redirect "http://www.example.com/foo"
end
test "[ENGINE] relative option root uses SCRIPT_NAME from request" do
get "/relative_option_root"
- verify_redirect "http://example.org/"
+ verify_redirect "http://www.example.com/"
end
test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do
get "/relative_option_redirect"
- verify_redirect "http://example.org/foo"
+ verify_redirect "http://www.example.com/foo"
end
test "[ENGINE] relative custom root uses SCRIPT_NAME from request" do
get "/relative_custom_root"
- verify_redirect "http://example.org/"
+ verify_redirect "http://www.example.com/"
end
test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do
get "/relative_custom_redirect"
- verify_redirect "http://example.org/foo"
+ verify_redirect "http://www.example.com/foo"
end
test "[ENGINE] absolute path root doesn't use SCRIPT_NAME from request" do
get "/absolute_path_root"
- verify_redirect "http://example.org/"
+ verify_redirect "http://www.example.com/"
end
test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do
get "/absolute_path_redirect"
- verify_redirect "http://example.org/foo"
+ verify_redirect "http://www.example.com/foo"
end
test "[ENGINE] absolute option root doesn't use SCRIPT_NAME from request" do
get "/absolute_option_root"
- verify_redirect "http://example.org/"
+ verify_redirect "http://www.example.com/"
end
test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do
get "/absolute_option_redirect"
- verify_redirect "http://example.org/foo"
+ verify_redirect "http://www.example.com/foo"
end
test "[ENGINE] absolute custom root doesn't use SCRIPT_NAME from request" do
get "/absolute_custom_root"
- verify_redirect "http://example.org/"
+ verify_redirect "http://www.example.com/"
end
test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do
get "/absolute_custom_redirect"
- verify_redirect "http://example.org/foo"
+ verify_redirect "http://www.example.com/foo"
end
private
def verify_redirect(url, status = 301)
- assert_equal status, last_response.status
- assert_equal url, last_response.headers["Location"]
- assert_equal expected_redirect_body(url), last_response.body
+ assert_equal status, response.status
+ assert_equal url, response.headers["Location"]
+ assert_equal expected_redirect_body(url), response.body
end
def expected_redirect_body(url)
diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb
index db68549b84..9eb78fe059 100644
--- a/actionpack/test/dispatch/reloader_test.rb
+++ b/actionpack/test/dispatch/reloader_test.rb
@@ -1,32 +1,11 @@
require "abstract_unit"
class ReloaderTest < ActiveSupport::TestCase
- Reloader = ActionDispatch::Reloader
-
teardown do
ActiveSupport::Reloader.reset_callbacks :prepare
ActiveSupport::Reloader.reset_callbacks :complete
end
- def test_prepare_callbacks
- a = b = c = nil
- assert_deprecated do
- Reloader.to_prepare { |*args| a = b = c = 1 }
- Reloader.to_prepare { |*args| b = c = 2 }
- Reloader.to_prepare { |*args| c = 3 }
- end
-
- # Ensure to_prepare callbacks are not run when defined
- assert_nil a || b || c
-
- # Run callbacks
- call_and_return_body
-
- assert_equal 1, a
- assert_equal 2, b
- assert_equal 3, c
- end
-
class MyBody < Array
def initialize(&block)
@on_close = block
@@ -45,6 +24,23 @@ class ReloaderTest < ActiveSupport::TestCase
end
end
+ def test_prepare_callbacks
+ a = b = c = nil
+ reloader.to_prepare { |*args| a = b = c = 1 }
+ reloader.to_prepare { |*args| b = c = 2 }
+ reloader.to_prepare { |*args| c = 3 }
+
+ # Ensure to_prepare callbacks are not run when defined
+ assert_nil a || b || c
+
+ # Run callbacks
+ call_and_return_body
+
+ assert_equal 1, a
+ assert_equal 2, b
+ assert_equal 3, c
+ end
+
def test_returned_body_object_always_responds_to_close
body = call_and_return_body
assert_respond_to body, :close
@@ -62,15 +58,12 @@ class ReloaderTest < ActiveSupport::TestCase
def test_condition_specifies_when_to_reload
i, j = 0, 0, 0, 0
- assert_deprecated do
- Reloader.to_prepare { |*args| i += 1 }
- Reloader.to_cleanup { |*args| j += 1 }
- end
- x = Class.new(ActiveSupport::Reloader)
- x.check = lambda { i < 3 }
+ reloader = reloader(lambda { i < 3 })
+ reloader.to_prepare { |*args| i += 1 }
+ reloader.to_complete { |*args| j += 1 }
- app = Reloader.new(lambda { |env| [200, {}, []] }, x)
+ app = middleware(lambda { |env| [200, {}, []] }, reloader)
5.times do
resp = app.call({})
resp[2].close
@@ -115,24 +108,20 @@ class ReloaderTest < ActiveSupport::TestCase
assert_respond_to body, :bar
end
- def test_cleanup_callbacks_are_called_when_body_is_closed
- cleaned = false
- assert_deprecated do
- Reloader.to_cleanup { cleaned = true }
- end
+ def test_complete_callbacks_are_called_when_body_is_closed
+ completed = false
+ reloader.to_complete { completed = true }
body = call_and_return_body
- assert !cleaned
+ assert !completed
body.close
- assert cleaned
+ assert completed
end
def test_prepare_callbacks_arent_called_when_body_is_closed
prepared = false
- assert_deprecated do
- Reloader.to_prepare { prepared = true }
- end
+ reloader.to_prepare { prepared = true }
body = call_and_return_body
prepared = false
@@ -141,45 +130,9 @@ class ReloaderTest < ActiveSupport::TestCase
assert !prepared
end
- def test_manual_reloading
- prepared = cleaned = false
- assert_deprecated do
- Reloader.to_prepare { prepared = true }
- Reloader.to_cleanup { cleaned = true }
- end
-
- assert_deprecated do
- Reloader.prepare!
- end
- assert prepared
- assert !cleaned
-
- prepared = cleaned = false
- assert_deprecated do
- Reloader.cleanup!
- end
- assert prepared
- assert cleaned
- end
-
- def test_prepend_prepare_callback
- i = 10
- assert_deprecated do
- Reloader.to_prepare { i += 1 }
- Reloader.to_prepare(prepend: true) { i = 0 }
- end
-
- assert_deprecated do
- Reloader.prepare!
- end
- assert_equal 1, i
- end
-
- def test_cleanup_callbacks_are_called_on_exceptions
- cleaned = false
- assert_deprecated do
- Reloader.to_cleanup { cleaned = true }
- end
+ def test_complete_callbacks_are_called_on_exceptions
+ completed = false
+ reloader.to_complete { completed = true }
begin
call_and_return_body do
@@ -188,16 +141,25 @@ class ReloaderTest < ActiveSupport::TestCase
rescue
end
- assert cleaned
+ assert completed
end
private
def call_and_return_body(&block)
- x = Class.new(ActiveSupport::Reloader)
- x.check = lambda { true }
+ app = middleware(block || proc { [200, {}, "response"] })
+ _, _, body = app.call("rack.input" => StringIO.new(""))
+ body
+ end
+
+ def middleware(inner_app, reloader = reloader())
+ ActionDispatch::Reloader.new(inner_app, reloader)
+ end
- @response ||= "response"
- @reloader ||= Reloader.new(block || proc { [200, {}, @response] }, x)
- @reloader.call("rack.input" => StringIO.new(""))[2]
+ def reloader(check = lambda { true })
+ @reloader ||= begin
+ reloader = Class.new(ActiveSupport::Reloader)
+ reloader.check = check
+ reloader
+ end
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index e11b93b4f0..2f9228a62d 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -760,8 +760,8 @@ class RequestMethod < BaseRequestTest
end
test "post uneffected by local inflections" do
- existing_acrnoyms = ActiveSupport::Inflector.inflections.acronyms.dup
- existing_acrnoym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup
+ existing_acronyms = ActiveSupport::Inflector.inflections.acronyms.dup
+ existing_acronym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup
begin
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym "POS"
@@ -774,8 +774,8 @@ class RequestMethod < BaseRequestTest
ensure
# Reset original acronym set
ActiveSupport::Inflector.inflections do |inflect|
- inflect.send(:instance_variable_set, "@acronyms", existing_acrnoyms)
- inflect.send(:instance_variable_set, "@acronym_regex", existing_acrnoym_regex)
+ inflect.send(:instance_variable_set, "@acronyms", existing_acronyms)
+ inflect.send(:instance_variable_set, "@acronym_regex", existing_acronym_regex)
end
end
end
diff --git a/actionpack/test/dispatch/routing/custom_url_helpers_test.rb b/actionpack/test/dispatch/routing/custom_url_helpers_test.rb
new file mode 100644
index 0000000000..6d230a2557
--- /dev/null
+++ b/actionpack/test/dispatch/routing/custom_url_helpers_test.rb
@@ -0,0 +1,291 @@
+require "abstract_unit"
+
+class TestCustomUrlHelpers < ActionDispatch::IntegrationTest
+ class Linkable
+ attr_reader :id
+
+ def initialize(id)
+ @id = id
+ end
+
+ def linkable_type
+ self.class.name.demodulize.underscore
+ end
+ end
+
+ class Category < Linkable; end
+ class Collection < Linkable; end
+ class Product < Linkable; end
+
+ class Model
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ attr_reader :id
+
+ def initialize(id = nil)
+ @id = id
+ end
+
+ remove_method :model_name
+ def model_name
+ @_model_name ||= ActiveModel::Name.new(self.class, nil, self.class.name.demodulize)
+ end
+
+ def persisted?
+ false
+ end
+ end
+
+ class Basket < Model; end
+ class User < Model; end
+ class Video < Model; end
+
+ class Article
+ attr_reader :id
+
+ def self.name
+ "Article"
+ end
+
+ def initialize(id)
+ @id = id
+ end
+ end
+
+ class Page
+ attr_reader :id
+
+ def self.name
+ super.demodulize
+ end
+
+ def initialize(id)
+ @id = id
+ end
+ end
+
+ class CategoryPage < Page; end
+ class ProductPage < Page; end
+
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw do
+ default_url_options host: "www.example.com"
+
+ root to: "pages#index"
+ get "/basket", to: "basket#show", as: :basket
+ get "/posts/:id", to: "posts#show", as: :post
+ get "/profile", to: "users#profile", as: :profile
+ get "/media/:id", to: "media#show", as: :media
+ get "/pages/:id", to: "pages#show", as: :page
+
+ resources :categories, :collections, :products
+
+ namespace :admin do
+ get "/dashboard", to: "dashboard#index"
+ end
+
+ direct(:website) { "http://www.rubyonrails.org" }
+ direct("string") { "http://www.rubyonrails.org" }
+ direct(:helper) { basket_url }
+ direct(:linkable) { |linkable| [:"#{linkable.linkable_type}", { id: linkable.id }] }
+ direct(:params) { |params| params }
+ direct(:symbol) { :basket }
+ direct(:hash) { { controller: "basket", action: "show" } }
+ direct(:array) { [:admin, :dashboard] }
+ direct(:options) { |options| [:products, options] }
+ direct(:defaults, size: 10) { |options| [:products, options] }
+
+ resolve("Article") { |article| [:post, { id: article.id }] }
+ resolve("Basket") { |basket| [:basket] }
+ resolve("User", anchor: "details") { |user, options| [:profile, options] }
+ resolve("Video") { |video| [:media, { id: video.id }] }
+ resolve(%w[Page CategoryPage ProductPage]) { |page| [:page, { id: page.id }] }
+ end
+
+ APP = build_app Routes
+
+ def app
+ APP
+ end
+
+ include Routes.url_helpers
+
+ def setup
+ @category = Category.new("1")
+ @collection = Collection.new("2")
+ @product = Product.new("3")
+ @basket = Basket.new
+ @user = User.new
+ @video = Video.new("4")
+ @article = Article.new("5")
+ @page = Page.new("6")
+ @category_page = CategoryPage.new("7")
+ @product_page = ProductPage.new("8")
+ @path_params = { "controller" => "pages", "action" => "index" }
+ @unsafe_params = ActionController::Parameters.new(@path_params)
+ @safe_params = ActionController::Parameters.new(@path_params).permit(:controller, :action)
+ end
+
+ def test_direct_paths
+ assert_equal "http://www.rubyonrails.org", website_path
+ assert_equal "http://www.rubyonrails.org", Routes.url_helpers.website_path
+
+ assert_equal "http://www.rubyonrails.org", string_path
+ assert_equal "http://www.rubyonrails.org", Routes.url_helpers.string_path
+
+ assert_equal "http://www.example.com/basket", helper_url
+ assert_equal "http://www.example.com/basket", Routes.url_helpers.helper_url
+
+ assert_equal "/categories/1", linkable_path(@category)
+ assert_equal "/categories/1", Routes.url_helpers.linkable_path(@category)
+ assert_equal "/collections/2", linkable_path(@collection)
+ assert_equal "/collections/2", Routes.url_helpers.linkable_path(@collection)
+ assert_equal "/products/3", linkable_path(@product)
+ assert_equal "/products/3", Routes.url_helpers.linkable_path(@product)
+
+ assert_equal "/", params_path(@safe_params)
+ assert_equal "/", Routes.url_helpers.params_path(@safe_params)
+ assert_raises(ArgumentError) { params_path(@unsafe_params) }
+ assert_raises(ArgumentError) { Routes.url_helpers.params_path(@unsafe_params) }
+
+ assert_equal "/basket", symbol_path
+ assert_equal "/basket", Routes.url_helpers.symbol_path
+ assert_equal "/basket", hash_path
+ assert_equal "/basket", Routes.url_helpers.hash_path
+ assert_equal "/admin/dashboard", array_path
+ assert_equal "/admin/dashboard", Routes.url_helpers.array_path
+
+ assert_equal "/products?page=2", options_path(page: 2)
+ assert_equal "/products?page=2", Routes.url_helpers.options_path(page: 2)
+ assert_equal "/products?size=10", defaults_path
+ assert_equal "/products?size=10", Routes.url_helpers.defaults_path
+ assert_equal "/products?size=20", defaults_path(size: 20)
+ assert_equal "/products?size=20", Routes.url_helpers.defaults_path(size: 20)
+ end
+
+ def test_direct_urls
+ assert_equal "http://www.rubyonrails.org", website_url
+ assert_equal "http://www.rubyonrails.org", Routes.url_helpers.website_url
+
+ assert_equal "http://www.rubyonrails.org", string_url
+ assert_equal "http://www.rubyonrails.org", Routes.url_helpers.string_url
+
+ assert_equal "http://www.example.com/basket", helper_url
+ assert_equal "http://www.example.com/basket", Routes.url_helpers.helper_url
+
+ assert_equal "http://www.example.com/categories/1", linkable_url(@category)
+ assert_equal "http://www.example.com/categories/1", Routes.url_helpers.linkable_url(@category)
+ assert_equal "http://www.example.com/collections/2", linkable_url(@collection)
+ assert_equal "http://www.example.com/collections/2", Routes.url_helpers.linkable_url(@collection)
+ assert_equal "http://www.example.com/products/3", linkable_url(@product)
+ assert_equal "http://www.example.com/products/3", Routes.url_helpers.linkable_url(@product)
+
+ assert_equal "http://www.example.com/", params_url(@safe_params)
+ assert_equal "http://www.example.com/", Routes.url_helpers.params_url(@safe_params)
+ assert_raises(ArgumentError) { params_url(@unsafe_params) }
+ assert_raises(ArgumentError) { Routes.url_helpers.params_url(@unsafe_params) }
+
+ assert_equal "http://www.example.com/basket", symbol_url
+ assert_equal "http://www.example.com/basket", Routes.url_helpers.symbol_url
+ assert_equal "http://www.example.com/basket", hash_url
+ assert_equal "http://www.example.com/basket", Routes.url_helpers.hash_url
+ assert_equal "http://www.example.com/admin/dashboard", array_url
+ assert_equal "http://www.example.com/admin/dashboard", Routes.url_helpers.array_url
+
+ assert_equal "http://www.example.com/products?page=2", options_url(page: 2)
+ assert_equal "http://www.example.com/products?page=2", Routes.url_helpers.options_url(page: 2)
+ assert_equal "http://www.example.com/products?size=10", defaults_url
+ assert_equal "http://www.example.com/products?size=10", Routes.url_helpers.defaults_url
+ assert_equal "http://www.example.com/products?size=20", defaults_url(size: 20)
+ assert_equal "http://www.example.com/products?size=20", Routes.url_helpers.defaults_url(size: 20)
+ end
+
+ def test_resolve_paths
+ assert_equal "/basket", polymorphic_path(@basket)
+ assert_equal "/basket", Routes.url_helpers.polymorphic_path(@basket)
+
+ assert_equal "/profile#details", polymorphic_path(@user)
+ assert_equal "/profile#details", Routes.url_helpers.polymorphic_path(@user)
+
+ assert_equal "/profile#password", polymorphic_path(@user, anchor: "password")
+ assert_equal "/profile#password", Routes.url_helpers.polymorphic_path(@user, anchor: "password")
+
+ assert_equal "/media/4", polymorphic_path(@video)
+ assert_equal "/media/4", Routes.url_helpers.polymorphic_path(@video)
+ assert_equal "/media/4", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @video)
+
+ assert_equal "/posts/5", polymorphic_path(@article)
+ assert_equal "/posts/5", Routes.url_helpers.polymorphic_path(@article)
+ assert_equal "/posts/5", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @article)
+
+ assert_equal "/pages/6", polymorphic_path(@page)
+ assert_equal "/pages/6", Routes.url_helpers.polymorphic_path(@page)
+ assert_equal "/pages/6", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @page)
+
+ assert_equal "/pages/7", polymorphic_path(@category_page)
+ assert_equal "/pages/7", Routes.url_helpers.polymorphic_path(@category_page)
+ assert_equal "/pages/7", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @category_page)
+
+ assert_equal "/pages/8", polymorphic_path(@product_page)
+ assert_equal "/pages/8", Routes.url_helpers.polymorphic_path(@product_page)
+ assert_equal "/pages/8", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.path.handle_model_call(self, @product_page)
+ end
+
+ def test_resolve_urls
+ assert_equal "http://www.example.com/basket", polymorphic_url(@basket)
+ assert_equal "http://www.example.com/basket", Routes.url_helpers.polymorphic_url(@basket)
+ assert_equal "http://www.example.com/basket", polymorphic_url(@basket)
+ assert_equal "http://www.example.com/basket", Routes.url_helpers.polymorphic_url(@basket)
+
+ assert_equal "http://www.example.com/profile#details", polymorphic_url(@user)
+ assert_equal "http://www.example.com/profile#details", Routes.url_helpers.polymorphic_url(@user)
+
+ assert_equal "http://www.example.com/profile#password", polymorphic_url(@user, anchor: "password")
+ assert_equal "http://www.example.com/profile#password", Routes.url_helpers.polymorphic_url(@user, anchor: "password")
+
+ assert_equal "http://www.example.com/media/4", polymorphic_url(@video)
+ assert_equal "http://www.example.com/media/4", Routes.url_helpers.polymorphic_url(@video)
+ assert_equal "http://www.example.com/media/4", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @video)
+
+ assert_equal "http://www.example.com/posts/5", polymorphic_url(@article)
+ assert_equal "http://www.example.com/posts/5", Routes.url_helpers.polymorphic_url(@article)
+ assert_equal "http://www.example.com/posts/5", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @article)
+
+ assert_equal "http://www.example.com/pages/6", polymorphic_url(@page)
+ assert_equal "http://www.example.com/pages/6", Routes.url_helpers.polymorphic_url(@page)
+ assert_equal "http://www.example.com/pages/6", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @page)
+
+ assert_equal "http://www.example.com/pages/7", polymorphic_url(@category_page)
+ assert_equal "http://www.example.com/pages/7", Routes.url_helpers.polymorphic_url(@category_page)
+ assert_equal "http://www.example.com/pages/7", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @category_page)
+
+ assert_equal "http://www.example.com/pages/8", polymorphic_url(@product_page)
+ assert_equal "http://www.example.com/pages/8", Routes.url_helpers.polymorphic_url(@product_page)
+ assert_equal "http://www.example.com/pages/8", ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.url.handle_model_call(self, @product_page)
+ end
+
+ def test_defining_direct_inside_a_scope_raises_runtime_error
+ routes = ActionDispatch::Routing::RouteSet.new
+
+ assert_raises RuntimeError do
+ routes.draw do
+ namespace :admin do
+ direct(:rubyonrails) { "http://www.rubyonrails.org" }
+ end
+ end
+ end
+ end
+
+ def test_defining_resolve_inside_a_scope_raises_runtime_error
+ routes = ActionDispatch::Routing::RouteSet.new
+
+ assert_raises RuntimeError do
+ routes.draw do
+ namespace :admin do
+ resolve("User") { "/profile" }
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 474d053af6..53758a4fbc 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1603,7 +1603,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "account/login", to: redirect("/login")
end
- previous_host, self.host = self.host, "www.example.com:3000"
+ previous_host, self.host = host, "www.example.com:3000"
get "/account/login"
verify_redirect "http://www.example.com:3000/login"
diff --git a/actionpack/test/dispatch/runner_test.rb b/actionpack/test/dispatch/runner_test.rb
index 969933c9ed..b76bf4a320 100644
--- a/actionpack/test/dispatch/runner_test.rb
+++ b/actionpack/test/dispatch/runner_test.rb
@@ -4,7 +4,6 @@ class RunnerTest < ActiveSupport::TestCase
test "runner preserves the setting of integration_session" do
runner = Class.new do
def before_setup
-
end
end.new
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 2a1053be16..63dfc07c0d 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -175,7 +175,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def test_doesnt_write_session_cookie_if_session_is_unchanged
with_test_route_set do
- cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--" +
+ cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--" \
"fef868465920f415f2c0652d6910d3af288a0367"
get "/no_session_access"
assert_response :success
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
index e29ffa750c..757e26973f 100644
--- a/actionpack/test/dispatch/ssl_test.rb
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -102,7 +102,11 @@ class StrictTransportSecurityTest < SSLTest
def assert_hsts(expected, url: "https://example.org", hsts: { subdomains: true }, headers: {})
self.app = build_app ssl_options: { hsts: hsts }, headers: headers
get url
- assert_equal expected, response.headers["Strict-Transport-Security"]
+ if expected.nil?
+ assert_nil response.headers["Strict-Transport-Security"]
+ else
+ assert_equal expected, response.headers["Strict-Transport-Security"]
+ end
end
test "enabled by default" do
@@ -130,7 +134,7 @@ class StrictTransportSecurityTest < SSLTest
end
test ":expires supports AS::Duration arguments" do
- assert_hsts "max-age=31557600; includeSubDomains", hsts: { expires: 1.year }
+ assert_hsts "max-age=31556952; includeSubDomains", hsts: { expires: 1.year }
end
test "include subdomains" do
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index d8bc96e3e0..bd8318f5f6 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -163,7 +163,7 @@ module StaticTests
assert_equal file_name, env["PATH_INFO"]
end
- def test_serves_gzip_with_propper_content_type_fallback
+ def test_serves_gzip_with_proper_content_type_fallback
file_name = "/gzip/foo.zoo"
response = get(file_name, "HTTP_ACCEPT_ENCODING" => "gzip")
assert_gzip file_name, response
diff --git a/actionpack/test/dispatch/system_testing/browser_test.rb b/actionpack/test/dispatch/system_testing/browser_test.rb
new file mode 100644
index 0000000000..b0ad309492
--- /dev/null
+++ b/actionpack/test/dispatch/system_testing/browser_test.rb
@@ -0,0 +1,10 @@
+require "abstract_unit"
+require "action_dispatch/system_testing/browser"
+
+class BrowserTest < ActiveSupport::TestCase
+ test "initializing the browser" do
+ browser = ActionDispatch::SystemTesting::Browser.new(:chrome, [ 1400, 1400 ])
+ assert_equal :chrome, browser.instance_variable_get(:@name)
+ assert_equal [ 1400, 1400 ], browser.instance_variable_get(:@screen_size)
+ end
+end
diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb
new file mode 100644
index 0000000000..f0ebdb38db
--- /dev/null
+++ b/actionpack/test/dispatch/system_testing/driver_test.rb
@@ -0,0 +1,9 @@
+require "abstract_unit"
+require "action_dispatch/system_testing/driver"
+
+class DriverTest < ActiveSupport::TestCase
+ test "initializing the driver" do
+ driver = ActionDispatch::SystemTesting::Driver.new(:selenium)
+ assert_equal :selenium, driver.instance_variable_get(:@name)
+ end
+end
diff --git a/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
new file mode 100644
index 0000000000..8c14f799b0
--- /dev/null
+++ b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb
@@ -0,0 +1,18 @@
+require "abstract_unit"
+require "action_dispatch/system_testing/test_helpers/screenshot_helper"
+
+class ScreenshotHelperTest < ActiveSupport::TestCase
+ test "image path is saved in tmp directory" do
+ new_test = ActionDispatch::SystemTestCase.new("x")
+
+ assert_equal "tmp/screenshots/x.png", new_test.send(:image_path)
+ end
+
+ test "image path includes failures text if test did not pass" do
+ new_test = ActionDispatch::SystemTestCase.new("x")
+
+ new_test.stub :passed?, false do
+ assert_equal "tmp/screenshots/failures_x.png", new_test.send(:image_path)
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/system_testing/server_test.rb b/actionpack/test/dispatch/system_testing/server_test.rb
new file mode 100644
index 0000000000..10412d6367
--- /dev/null
+++ b/actionpack/test/dispatch/system_testing/server_test.rb
@@ -0,0 +1,17 @@
+require "abstract_unit"
+require "capybara/dsl"
+require "action_dispatch/system_testing/server"
+
+class ServerTest < ActiveSupport::TestCase
+ setup do
+ ActionDispatch::SystemTesting::Server.new.run
+ end
+
+ test "initializing the server port" do
+ assert_includes Capybara.servers, :rails_puma
+ end
+
+ test "port is always included" do
+ assert Capybara.always_include_port, "expected Capybara.always_include_port to be true"
+ end
+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
new file mode 100644
index 0000000000..a384902a14
--- /dev/null
+++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
@@ -0,0 +1,21 @@
+require "abstract_unit"
+
+class SystemTestCaseTest < ActiveSupport::TestCase
+ test "driven_by sets Capybara's default driver to poltergeist" do
+ ActionDispatch::SystemTestCase.driven_by :poltergeist
+
+ assert_equal :poltergeist, Capybara.default_driver
+ end
+
+ test "driven_by sets Capybara's drivers respectively" do
+ ActionDispatch::SystemTestCase.driven_by :selenium, using: :chrome
+
+ assert_includes Capybara.drivers, :selenium
+ assert_includes Capybara.drivers, :chrome
+ assert_equal :chrome, Capybara.default_driver
+ end
+
+ test "selenium? returns false if driver is poltergeist" do
+ assert_not ActionDispatch::SystemTestCase.selenium?(:poltergeist)
+ end
+end
diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb
index d61a8c023a..2c74617944 100644
--- a/actionpack/test/journey/path/pattern_test.rb
+++ b/actionpack/test/journey/path/pattern_test.rb
@@ -20,7 +20,7 @@ module ActionDispatch
"/:controller/*foo/bar" => %r{\A/(#{x})/(.+)/bar\Z},
"/:foo|*bar" => %r{\A/(?:([^/.?]+)|(.+))\Z},
}.each do |path, expected|
- define_method(:"test_to_regexp_#{path}") do
+ define_method(:"test_to_regexp_#{Regexp.escape(path)}") do
path = Pattern.build(
path,
{ controller: /.+/ },
@@ -44,7 +44,7 @@ module ActionDispatch
"/:controller/*foo/bar" => %r{\A/(#{x})/(.+)/bar},
"/:foo|*bar" => %r{\A/(?:([^/.?]+)|(.+))},
}.each do |path, expected|
- define_method(:"test_to_non_anchored_regexp_#{path}") do
+ define_method(:"test_to_non_anchored_regexp_#{Regexp.escape(path)}") do
path = Pattern.build(
path,
{ controller: /.+/ },
@@ -67,7 +67,7 @@ module ActionDispatch
"/:controller/*foo" => %w{ controller foo },
"/:controller/*foo/bar" => %w{ controller foo },
}.each do |path, expected|
- define_method(:"test_names_#{path}") do
+ define_method(:"test_names_#{Regexp.escape(path)}") do
path = Pattern.build(
path,
{ controller: /.+/ },
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index 046b4167bb..b768553e7a 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -1,6 +1,6 @@
require "active_model"
-class Customer < Struct.new(:name, :id)
+Customer = Struct.new(:name, :id) do
extend ActiveModel::Naming
include ActiveModel::Conversion
@@ -28,7 +28,7 @@ class Customer < Struct.new(:name, :id)
end
end
-class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost)
+Post = Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) do
extend ActiveModel::Naming
include ActiveModel::Conversion
extend ActiveModel::Translation
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 2a38ca7b63..b071b260c9 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,27 @@
+* Change the ERB handler from Erubis to Erubi.
+
+ Erubi is an Erubis fork that's svelte, simple, and currently maintained.
+ Plus it supports `--enable-frozen-string-literal` in Ruby 2.3+.
+
+ Compatibility: Drops support for `<%===` tags for debug output.
+ These were an unused, undocumented side effect of the Erubis
+ implementation.
+
+ Deprecation: The Erubis handler will be removed in Rails 5.2, for the
+ handful of folks using it directly.
+
+ *Jeremy Evans*
+
+* Allow render locals to be assigned to instance variables in a view.
+
+ Fixes #27480.
+
+ *Andrew White*
+
+* Add `check_parameters` option to `current_page?` which makes it more strict.
+
+ *Maksym Pugach*
+
* Return correct object name in form helper method after `fields_for`.
Fixes #26931.
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index 7bfdfbe29a..cfaa5007a1 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -22,8 +22,8 @@ Gem::Specification.new do |s|
s.add_dependency "activesupport", version
s.add_dependency "builder", "~> 3.1"
- s.add_dependency "erubis", "~> 2.7.0"
- s.add_dependency "rails-html-sanitizer", "~> 1.0", ">= 1.0.2"
+ s.add_dependency "erubi", "~> 1.4"
+ s.add_dependency "rails-html-sanitizer", "~> 1.0", ">= 1.0.3"
s.add_dependency "rails-dom-testing", "~> 2.0"
s.add_development_dependency "actionpack", version
diff --git a/actionview/app/assets/javascripts/config.coffee b/actionview/app/assets/javascripts/config.coffee
index 3d4706b0e1..a93325e903 100644
--- a/actionview/app/assets/javascripts/config.coffee
+++ b/actionview/app/assets/javascripts/config.coffee
@@ -1,21 +1,21 @@
#= export Rails
@Rails =
- # Link elements bound by jquery-ujs
+ # Link elements bound by rails-ujs
linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]'
- # Button elements bound by jquery-ujs
+ # Button elements bound by rails-ujs
buttonClickSelector:
selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])'
exclude: 'form button'
- # Select elements bound by jquery-ujs
+ # Select elements bound by rails-ujs
inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]'
- # Form elements bound by jquery-ujs
+ # Form elements bound by rails-ujs
formSubmitSelector: 'form'
- # Form input elements bound by jquery-ujs
+ # Form input elements bound by rails-ujs
formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])'
# Form input elements disabled during form submission
@@ -24,9 +24,6 @@
# Form input elements re-enabled after form submission
formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled'
- # Form required input elements
- requiredInputSelector: 'input[name][required]:not([disabled]), textarea[name][required]:not([disabled])'
-
# Form file input elements
fileInputSelector: 'input[name][type=file]:not([disabled])'
diff --git a/actionview/app/assets/javascripts/features/remote.coffee b/actionview/app/assets/javascripts/features/remote.coffee
index 30a5dc21fa..852587042c 100644
--- a/actionview/app/assets/javascripts/features/remote.coffee
+++ b/actionview/app/assets/javascripts/features/remote.coffee
@@ -4,7 +4,7 @@
matches, getData, setData
fire, stopEverything
ajax, isCrossDomain
- blankInputs, serializeElement
+ serializeElement
} = Rails
# Checks "data-remote" if true to handle the request through a XHR request.
@@ -71,16 +71,6 @@ Rails.handleRemote = (e) ->
)
stopEverything(e)
-# Check whether any required fields are empty
-# In both ajax mode and normal mode
-Rails.validateForm = (e) ->
- form = this
- return if form.noValidate or getData(form, 'ujs:formnovalidate-button')
- # Skip other logic when required values are missing or file upload is present
- blankRequiredInputs = blankInputs(form, Rails.requiredInputSelector, false)
- if blankRequiredInputs.length > 0 and fire(form, 'ajax:aborted:required', [blankRequiredInputs])
- stopEverything(e)
-
Rails.formSubmitButtonClick = (e) ->
button = this
form = button.form
diff --git a/actionview/app/assets/javascripts/rails-ujs.coffee b/actionview/app/assets/javascripts/rails-ujs.coffee
index f96d2eb6fd..df889ce067 100644
--- a/actionview/app/assets/javascripts/rails-ujs.coffee
+++ b/actionview/app/assets/javascripts/rails-ujs.coffee
@@ -14,7 +14,7 @@
refreshCSRFTokens, CSRFProtection
enableElement, disableElement
handleConfirm
- handleRemote, validateForm, formSubmitButtonClick, handleMetaClick
+ handleRemote, formSubmitButtonClick, handleMetaClick
handleMethod
} = Rails
@@ -25,9 +25,9 @@ if jQuery? and not jQuery.rails
CSRFProtection(xhr) unless options.crossDomain
Rails.start = ->
- # Cut down on the number of issues from people inadvertently including jquery_ujs twice
- # by detecting and raising an error when it happens.
- throw new Error('jquery-ujs has already been loaded!') if window._rails_loaded
+ # Cut down on the number of issues from people inadvertently including
+ # rails-ujs twice by detecting and raising an error when it happens.
+ throw new Error('rails-ujs has already been loaded!') if window._rails_loaded
# This event works the same as the load event, except that it fires every
# time the page is loaded.
@@ -58,7 +58,6 @@ Rails.start = ->
delegate document, Rails.inputChangeSelector, 'change', handleRemote
delegate document, Rails.formSubmitSelector, 'submit', handleConfirm
- delegate document, Rails.formSubmitSelector, 'submit', validateForm
delegate document, Rails.formSubmitSelector, 'submit', handleRemote
# Normal mode submit
# Slight timeout so that the submit button gets properly serialized
diff --git a/actionview/app/assets/javascripts/utils/event.coffee b/actionview/app/assets/javascripts/utils/event.coffee
index 049b2a3ecd..8d3ff007ea 100644
--- a/actionview/app/assets/javascripts/utils/event.coffee
+++ b/actionview/app/assets/javascripts/utils/event.coffee
@@ -6,14 +6,14 @@
# https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
CustomEvent = window.CustomEvent
-if typeof CustomEvent is 'function'
+if typeof CustomEvent isnt 'function'
CustomEvent = (event, params) ->
evt = document.createEvent('CustomEvent')
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail)
evt
CustomEvent.prototype = window.Event.prototype
-# Triggers an custom event on an element and returns false if the event result is false
+# Triggers a custom event on an element and returns false if the event result is false
fire = Rails.fire = (obj, name, data) ->
event = new CustomEvent(
name,
diff --git a/actionview/app/assets/javascripts/utils/form.coffee b/actionview/app/assets/javascripts/utils/form.coffee
index 251113deda..5fa337b518 100644
--- a/actionview/app/assets/javascripts/utils/form.coffee
+++ b/actionview/app/assets/javascripts/utils/form.coffee
@@ -14,7 +14,7 @@ Rails.serializeElement = (element, additionalParam) ->
if matches(input, 'select')
toArray(input.options).forEach (option) ->
params.push(name: input.name, value: option.value) if option.selected
- else if input.type isnt 'radio' and input.type isnt 'checkbox' or input.checked
+ else if input.checked or ['radio', 'checkbox', 'submit'].indexOf(input.type) == -1
params.push(name: input.name, value: input.value)
params.push(additionalParam) if additionalParam
@@ -34,28 +34,3 @@ Rails.formElements = (form, selector) ->
toArray(form.elements).filter (el) -> matches(el, selector)
else
toArray(form.querySelectorAll(selector))
-
-# Helper function which checks for blank inputs in a form that match the specified CSS selector
-Rails.blankInputs = (form, selector, nonBlank) ->
- foundInputs = []
- requiredInputs = toArray(form.querySelectorAll(selector or 'input, textarea'))
- checkedRadioButtonNames = {}
-
- requiredInputs.forEach (input) ->
- if input.type is 'radio'
- # Don't count unchecked required radio as blank if other radio with same name is checked,
- # regardless of whether same-name radio input has required attribute or not. The spec
- # states https://www.w3.org/TR/html5/forms.html#the-required-attribute
- radioName = input.name
- # Skip if we've already seen the radio with this name.
- unless checkedRadioButtonNames[radioName]
- # If none checked
- if form.querySelectorAll("input[type=radio][name='#{radioName}']:checked").length == 0
- radios = form.querySelectorAll("input[type=radio][name='#{radioName}']")
- foundInputs = foundInputs.concat(toArray(radios))
- # We only need to check each name once.
- checkedRadioButtonNames[radioName] = radioName
- else
- valueToCheck = if input.type is 'checkbox' then input.checked else !!input.value
- foundInputs.push(input) if valueToCheck is nonBlank
- foundInputs
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index b7c05fdb88..5387174467 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -11,7 +11,7 @@ module ActionView #:nodoc:
# = Action View Base
#
# Action View templates can be written in several ways.
- # If the template file has a <tt>.erb</tt> extension, then it uses the erubis[https://rubygems.org/gems/erubis]
+ # If the template file has a <tt>.erb</tt> extension, then it uses the erubi[https://rubygems.org/gems/erubi]
# template system which can embed Ruby into an HTML document.
# If the template file has a <tt>.builder</tt> extension, then Jim Weirich's Builder::XmlMarkup library is used.
#
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 72a094c629..750f96f29e 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -40,7 +40,7 @@ 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 a extention to the generated url unless the extension
+ # * <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.
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index c6a5e04aba..03bd1eb008 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -237,7 +237,11 @@ module ActionView
def compute_asset_extname(source, options = {})
return if options[:extname] == false
extname = options[:extname] || ASSET_EXTENSIONS[options[:type]]
- extname if extname && File.extname(source) != extname
+ if extname && File.extname(source) != extname
+ extname
+ else
+ nil
+ end
end
# Maps asset types to public directory.
@@ -406,7 +410,7 @@ module ActionView
def video_url(source, options = {})
url_to_asset(source, { type: :video }.merge!(options))
end
- alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route
+ alias_method :url_to_video, :video_url # aliased to avoid conflicts with a video_url named route
# Computes the path to an audio asset in the public audios directory.
# Full paths from the document root will be passed through.
@@ -445,7 +449,7 @@ module ActionView
def font_path(source, options = {})
path_to_asset(source, { type: :font }.merge!(options))
end
- alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
+ alias_method :path_to_font, :font_path # aliased to avoid conflicts with a font_path named route
# Computes the full URL to a font asset.
# This will use +font_path+ internally, so most of their behaviors will be the same.
@@ -457,7 +461,7 @@ module ActionView
def font_url(source, options = {})
url_to_asset(source, { type: :font }.merge!(options))
end
- alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
+ alias_method :url_to_font, :font_url # aliased to avoid conflicts with a font_url named route
end
end
end
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 7fdf0fd0e1..15ab7e304f 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -241,8 +241,6 @@ module ActionView
end
def write_fragment_for(name, options)
- # VIEW TODO: Make #capture usable outside of ERB
- # This dance is needed because Builder can't use capture
pos = output_buffer.length
yield
output_safe = output_buffer.html_safe?
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 9e80f0b2ee..b6bc5f4f6f 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -92,7 +92,7 @@ module ActionView
# (defaults to "%u%n"). Fields are <tt>%u</tt> for the
# currency, and <tt>%n</tt> for the number.
# * <tt>:negative_format</tt> - Sets the format for negative
- # numbers (defaults to prepending an hyphen to the formatted
+ # numbers (defaults to prepending a hyphen to the formatted
# number given by <tt>:format</tt>). Accepts the same fields
# than <tt>:format</tt>, except <tt>%n</tt> is here the
# absolute value of the number.
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index 9f1a890f6a..25defd1276 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -25,7 +25,7 @@ module ActionView #:nodoc:
# safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
# # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
#
- # safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />")
+ # safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />"))
# # => "<p>foo</p><br /><p>bar</p>"
#
def safe_join(array, sep = $,)
@@ -60,7 +60,7 @@ module ActionView #:nodoc:
when 2
safe_join([array[0], array[1]], options[:two_words_connector])
else
- safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]])
+ safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]], nil)
end
end
end
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 3d6ff598ee..1e9b813d3d 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -45,17 +45,15 @@ module ActionView
# Providing a custom Rails::Html scrubber:
#
# class CommentScrubber < Rails::Html::PermitScrubber
- # def allowed_node?(node)
- # !%w(form script comment blockquote).include?(node.name)
+ # def initialize
+ # super
+ # self.tags = %w( form script comment blockquote )
+ # self.attributes = %w( style )
# end
#
# def skip_node?(node)
# node.text?
# end
- #
- # def scrub_attribute?(name)
- # name == 'style'
- # end
# end
#
# <%= sanitize @comment.body, scrubber: CommentScrubber.new %>
diff --git a/actionview/lib/action_view/helpers/tags/date_select.rb b/actionview/lib/action_view/helpers/tags/date_select.rb
index 006605885a..638c134deb 100644
--- a/actionview/lib/action_view/helpers/tags/date_select.rb
+++ b/actionview/lib/action_view/helpers/tags/date_select.rb
@@ -16,7 +16,7 @@ module ActionView
class << self
def select_type
- @select_type ||= self.name.split("::").last.sub("Select", "").downcase
+ @select_type ||= name.split("::").last.sub("Select", "").downcase
end
end
diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb
index 4306c3543d..613cade7b3 100644
--- a/actionview/lib/action_view/helpers/tags/text_field.rb
+++ b/actionview/lib/action_view/helpers/tags/text_field.rb
@@ -17,7 +17,7 @@ module ActionView
class << self
def field_type
- @field_type ||= self.name.split("::").last.sub("Field", "").downcase
+ @field_type ||= name.split("::").last.sub("Field", "").downcase
end
end
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 07dccf5b41..bc922f9ce8 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -187,7 +187,7 @@ module ActionView
unless separator.empty?
text.split(separator).each do |value|
if value.match(regex)
- regex = phrase = value
+ phrase = value
break
end
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 58a4a04dcb..a306903c60 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -35,7 +35,7 @@ module ActionView
when :back
_back_url
else
- raise ArgumentError, "arguments passed to url_for can't be handled. Please require " +
+ raise ArgumentError, "arguments passed to url_for can't be handled. Please require " \
"routes or provide your own implementation"
end
end
@@ -517,6 +517,9 @@ module ActionView
# current_page?('http://www.example.com/shop/checkout')
# # => true
#
+ # current_page?('http://www.example.com/shop/checkout', check_parameters: true)
+ # # => false
+ #
# current_page?('/shop/checkout')
# # => true
#
@@ -530,7 +533,7 @@ module ActionView
#
# We can also pass in the symbol arguments instead of strings.
#
- def current_page?(options)
+ def current_page?(options, check_parameters: false)
unless request
raise "You cannot use helpers that need to determine the current " \
"page unless your view context provides a Request object " \
@@ -539,12 +542,14 @@ module ActionView
return false unless request.get? || request.head?
+ check_parameters ||= !options.is_a?(String) && options.try(:delete, :check_parameters)
url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY)
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function
# work with things like ?order=asc
- request_uri = url_string.index("?") ? request.fullpath : request.path
+ # the behaviour can be disabled with check_parameters: true
+ request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY)
url_string.chomp!("/") if url_string.start_with?("/") && url_string != "/"
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index 18e395a67f..81feb90486 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -319,7 +319,7 @@ module ActionView
name_clause
end
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
def _layout(formats)
if _conditional_layout?
#{layout_definition}
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index b8a79da97f..647b15ea94 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -99,7 +99,7 @@ module ActionView
# <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>
#
# If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return nil. This will allow you
- # to specify a text which will displayed instead by using this form:
+ # to specify a text which will be displayed instead by using this form:
#
# <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>
#
@@ -458,7 +458,7 @@ module ActionView
locals[counter] = index
locals[iteration] = partial_iteration
- template = (cache[path] ||= find_template(path, keys + [as, counter]))
+ template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))
content = template.render(view, locals)
partial_iteration.iterate!
content
@@ -532,11 +532,11 @@ module ActionView
[variable, variable_counter, variable_iteration]
end
- IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
+ IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \
"make sure your partial name starts with underscore."
- OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
- "make sure it starts with lowercase letter, " +
+ OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \
+ "make sure it starts with lowercase letter, " \
"and is followed by any combination of letters, numbers and underscores."
def raise_invalid_identifier(path)
diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
index 7ede034492..62ce985243 100644
--- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb
+++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
@@ -4,7 +4,6 @@ module ActionView
# == TODO
#
# * Support streaming from child templates, partials and so on.
- # * Integrate exceptions with exceptron
# * Rack::Cache needs to support streaming bodies
class StreamingTemplateRenderer < TemplateRenderer #:nodoc:
# A valid Rack::Body (i.e. it responds to each).
@@ -28,7 +27,6 @@ module ActionView
private
# This is the same logging logic as in ShowExceptions middleware.
- # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.
def log_error(exception)
logger = ActionView::Base.logger
return unless logger
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 0e72316eb7..cf18562c45 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -124,7 +124,11 @@ module ActionView
key = action.include?(?/) ? :template : :action
options[key] = action
else
- options[:partial] = action
+ if action.respond_to?(:permitted?) && action.permitted?
+ options = action
+ else
+ options[:partial] = action
+ end
end
options
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 4b793c3b16..b0e2f1e54e 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -326,7 +326,7 @@ module ActionView
# Only locals with valid variable names get set directly. Others will
# still be available in local_assigns.
locals = @locals - Module::RUBY_RESERVED_KEYWORDS
- locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
+ locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)
# Double assign to suppress the dreaded 'assigned but unused variable' warning
locals.each_with_object("") { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" }
@@ -345,7 +345,7 @@ module ActionView
end
def instrument(action, &block) # :doc:
- ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, instrument_payload, &block)
+ ActiveSupport::Notifications.instrument("#{action}.action_view", instrument_payload, &block)
end
def instrument_render_template(&block)
diff --git a/actionview/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb
index 494b543152..e99b921cb7 100644
--- a/actionview/lib/action_view/template/handlers/builder.rb
+++ b/actionview/lib/action_view/template/handlers/builder.rb
@@ -7,7 +7,7 @@ module ActionView
def call(template)
require_engine
- "xml = ::Builder::XmlMarkup.new(:indent => 2);" +
+ "xml = ::Builder::XmlMarkup.new(:indent => 2);" \
"self.output_buffer = xml.target!;" +
template.source +
";xml.target!;"
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 5d047a6991..58c7fd1a88 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -1,79 +1,12 @@
-require "erubis"
-
module ActionView
class Template
module Handlers
- class Erubis < ::Erubis::Eruby
- def add_preamble(src)
- @newline_pending = 0
- src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
- end
-
- def add_text(src, text)
- return if text.empty?
-
- if text == "\n"
- @newline_pending += 1
- else
- src << "@output_buffer.safe_append='"
- src << "\n" * @newline_pending if @newline_pending > 0
- src << escape_text(text)
- src << "'.freeze;"
-
- @newline_pending = 0
- end
- end
-
- # Erubis toggles <%= and <%== behavior when escaping is enabled.
- # We override to always treat <%== as escaped.
- def add_expr(src, code, indicator)
- case indicator
- when "=="
- add_expr_escaped(src, code)
- else
- super
- end
- end
-
- BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
-
- def add_expr_literal(src, code)
- flush_newline_if_pending(src)
- if BLOCK_EXPR.match?(code)
- src << "@output_buffer.append= " << code
- else
- src << "@output_buffer.append=(" << code << ");"
- end
- end
-
- def add_expr_escaped(src, code)
- flush_newline_if_pending(src)
- if BLOCK_EXPR.match?(code)
- src << "@output_buffer.safe_expr_append= " << code
- else
- src << "@output_buffer.safe_expr_append=(" << code << ");"
- end
- end
-
- def add_stmt(src, code)
- flush_newline_if_pending(src)
- super
- end
-
- def add_postamble(src)
- flush_newline_if_pending(src)
- src << "@output_buffer.to_s"
- end
-
- def flush_newline_if_pending(src)
- if @newline_pending > 0
- src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
- @newline_pending = 0
- end
- end
- end
+ autoload :Erubis, "action_view/template/handlers/erb/deprecated_erubis"
class ERB
+ autoload :Erubi, "action_view/template/handlers/erb/erubi"
+ autoload :Erubis, "action_view/template/handlers/erb/erubis"
+
# Specify trim mode for the ERB compiler. Defaults to '-'.
# See ERB documentation for suitable values.
class_attribute :erb_trim_mode
@@ -81,7 +14,7 @@ module ActionView
# Default implementation used.
class_attribute :erb_implementation
- self.erb_implementation = Erubis
+ self.erb_implementation = Erubi
# Do not escape templates of these mime types.
class_attribute :escape_whitelist
diff --git a/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb b/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb
new file mode 100644
index 0000000000..427ea20064
--- /dev/null
+++ b/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb
@@ -0,0 +1,9 @@
+::ActiveSupport::Deprecation.warn("ActionView::Template::Handlers::Erubis is deprecated and will be removed from Rails 5.2. Switch to ActionView::Template::Handlers::ERB::Erubi instead.")
+
+module ActionView
+ class Template
+ module Handlers
+ Erubis = ERB::Erubis
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/handlers/erb/erubi.rb b/actionview/lib/action_view/template/handlers/erb/erubi.rb
new file mode 100644
index 0000000000..755cc84015
--- /dev/null
+++ b/actionview/lib/action_view/template/handlers/erb/erubi.rb
@@ -0,0 +1,81 @@
+require "erubi"
+
+module ActionView
+ class Template
+ module Handlers
+ class ERB
+ class Erubi < ::Erubi::Engine
+ # :nodoc: all
+ def initialize(input, properties = {})
+ @newline_pending = 0
+
+ # Dup properties so that we don't modify argument
+ properties = Hash[properties]
+ properties[:preamble] = "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
+ properties[:postamble] = "@output_buffer.to_s"
+ properties[:bufvar] = "@output_buffer"
+ properties[:escapefunc] = ""
+
+ super
+ end
+
+ def evaluate(action_view_erb_handler_context)
+ pr = eval("proc { #{@src} }", binding, @filename || "(erubi)")
+ action_view_erb_handler_context.instance_eval(&pr)
+ end
+
+ private
+ def add_text(text)
+ return if text.empty?
+
+ if text == "\n"
+ @newline_pending += 1
+ else
+ src << "@output_buffer.safe_append='"
+ src << "\n" * @newline_pending if @newline_pending > 0
+ src << text.gsub(/['\\]/, '\\\\\&')
+ src << "'.freeze;"
+
+ @newline_pending = 0
+ end
+ end
+
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
+
+ def add_expression(indicator, code)
+ flush_newline_if_pending(src)
+
+ if (indicator == "==") || @escape
+ src << "@output_buffer.safe_expr_append="
+ else
+ src << "@output_buffer.append="
+ end
+
+ if BLOCK_EXPR.match?(code)
+ src << " " << code
+ else
+ src << "(" << code << ");"
+ end
+ end
+
+ def add_code(code)
+ flush_newline_if_pending(src)
+ super
+ end
+
+ def add_postamble(_)
+ flush_newline_if_pending(src)
+ super
+ end
+
+ def flush_newline_if_pending(src)
+ if @newline_pending > 0
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
+ @newline_pending = 0
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/handlers/erb/erubis.rb b/actionview/lib/action_view/template/handlers/erb/erubis.rb
new file mode 100644
index 0000000000..f3c35e1aec
--- /dev/null
+++ b/actionview/lib/action_view/template/handlers/erb/erubis.rb
@@ -0,0 +1,81 @@
+gem "erubis"
+require "erubis"
+
+module ActionView
+ class Template
+ module Handlers
+ class ERB
+ class Erubis < ::Erubis::Eruby
+ # :nodoc: all
+ def add_preamble(src)
+ @newline_pending = 0
+ src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
+ end
+
+ def add_text(src, text)
+ return if text.empty?
+
+ if text == "\n"
+ @newline_pending += 1
+ else
+ src << "@output_buffer.safe_append='"
+ src << "\n" * @newline_pending if @newline_pending > 0
+ src << escape_text(text)
+ src << "'.freeze;"
+
+ @newline_pending = 0
+ end
+ end
+
+ # Erubis toggles <%= and <%== behavior when escaping is enabled.
+ # We override to always treat <%== as escaped.
+ def add_expr(src, code, indicator)
+ case indicator
+ when "=="
+ add_expr_escaped(src, code)
+ else
+ super
+ end
+ end
+
+ BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
+
+ def add_expr_literal(src, code)
+ flush_newline_if_pending(src)
+ if BLOCK_EXPR.match?(code)
+ src << "@output_buffer.append= " << code
+ else
+ src << "@output_buffer.append=(" << code << ");"
+ end
+ end
+
+ def add_expr_escaped(src, code)
+ flush_newline_if_pending(src)
+ if BLOCK_EXPR.match?(code)
+ src << "@output_buffer.safe_expr_append= " << code
+ else
+ src << "@output_buffer.safe_expr_append=(" << code << ");"
+ end
+ end
+
+ def add_stmt(src, code)
+ flush_newline_if_pending(src)
+ super
+ end
+
+ def add_postamble(src)
+ flush_newline_if_pending(src)
+ src << "@output_buffer.to_s"
+ end
+
+ def flush_newline_if_pending(src)
+ if @newline_pending > 0
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
+ @newline_pending = 0
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 9da13663d7..d3905b5f23 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -164,8 +164,8 @@ module ActionView
# This is what child classes implement. No defaults are needed
# because Resolver guarantees that the arguments are present and
# normalized.
- def find_templates(name, prefix, partial, details)
- raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details) method"
+ def find_templates(name, prefix, partial, details, outside_app_allowed = false)
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false) method"
end
# Helpers that builds a path. Useful for building virtual paths.
@@ -226,7 +226,7 @@ module ActionView
template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
template_paths.map do |template|
- handler, format, variant = extract_handler_and_format_and_variant(template, formats)
+ handler, format, variant = extract_handler_and_format_and_variant(template)
contents = File.binread(template)
Template.new(contents, File.expand_path(template), handler,
@@ -289,7 +289,7 @@ module ActionView
# Extract handler, formats and variant from path. If a format cannot be found neither
# from the path, or the handler, we should return the array of formats given
# to the resolver.
- def extract_handler_and_format_and_variant(path, default_formats)
+ def extract_handler_and_format_and_variant(path)
pieces = File.basename(path).split(".".freeze)
pieces.shift
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 5fb7bb54b5..ae4fec4337 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -124,6 +124,10 @@ module ActionView
@_rendered_views ||= RenderedViewsCollection.new
end
+ def _routes
+ @controller._routes if @controller.respond_to?(:_routes)
+ end
+
# Need to experiment if this priority is the best one: rendered => output_buffer
class RenderedViewsCollection
def initialize
@@ -206,8 +210,8 @@ module ActionView
view = @controller.view_context
view.singleton_class.include(_helpers)
view.extend(Locals)
- view.rendered_views = self.rendered_views
- view.output_buffer = self.output_buffer
+ view.rendered_views = rendered_views
+ view.output_buffer = output_buffer
view
end
end
@@ -258,10 +262,6 @@ module ActionView
end]
end
- def _routes
- @controller._routes if @controller.respond_to?(:_routes)
- end
-
def method_missing(selector, *args)
begin
routes = @controller.respond_to?(:_routes) && @controller._routes
diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb
index f4a7a9138c..3188526b63 100644
--- a/actionview/lib/action_view/testing/resolvers.rb
+++ b/actionview/lib/action_view/testing/resolvers.rb
@@ -17,35 +17,35 @@ module ActionView #:nodoc:
@hash.keys.join(", ")
end
- private
-
- def query(path, exts, formats, _)
- query = ""
- EXTENSIONS.each_key do |ext|
- query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)"
- end
- query = /^(#{Regexp.escape(path)})#{query}$/
-
- templates = []
- @hash.each do |_path, array|
- source, updated_at = array
- next unless query.match?(_path)
- handler, format, variant = extract_handler_and_format_and_variant(_path, formats)
- templates << Template.new(source, _path, handler,
- virtual_path: path.virtual,
- format: format,
- variant: variant,
- updated_at: updated_at
- )
+ private
+
+ def query(path, exts, _, _)
+ query = ""
+ EXTENSIONS.each_key do |ext|
+ query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)"
+ end
+ query = /^(#{Regexp.escape(path)})#{query}$/
+
+ templates = []
+ @hash.each do |_path, array|
+ source, updated_at = array
+ next unless query.match?(_path)
+ handler, format, variant = extract_handler_and_format_and_variant(_path)
+ templates << Template.new(source, _path, handler,
+ virtual_path: path.virtual,
+ format: format,
+ variant: variant,
+ updated_at: updated_at
+ )
+ end
+
+ templates.sort_by { |t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
end
-
- templates.sort_by { |t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
- end
end
class NullResolver < PathResolver
- def query(path, exts, formats, _)
- handler, format, variant = extract_handler_and_format_and_variant(path, formats)
+ def query(path, exts, _, _)
+ handler, format, variant = extract_handler_and_format_and_variant(path)
[ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant)]
end
end
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index a9638d1e6d..f0fe6831fa 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -5,7 +5,7 @@ module ActionView
included do
class_attribute :_view_paths
self._view_paths = ActionView::PathSet.new
- self._view_paths.freeze
+ _view_paths.freeze
end
delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=,
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 5bc4151087..dde66a7ba0 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -9,8 +9,8 @@ require "active_support/core_ext/kernel/reporting"
# These are the normal settings that will be set up by Railties
# TODO: Have these tests support other combinations of these values
silence_warnings do
- Encoding.default_internal = "UTF-8"
- Encoding.default_external = "UTF-8"
+ Encoding.default_internal = Encoding::UTF_8
+ Encoding.default_external = Encoding::UTF_8
end
require "active_support/testing/autorun"
@@ -271,15 +271,15 @@ module ActionDispatch
end
end
-# Skips the current run on Rubinius using Minitest::Assertions#skip
-def rubinius_skip(message = "")
- skip message if RUBY_ENGINE == "rbx"
-end
-# Skips the current run on JRuby using Minitest::Assertions#skip
-def jruby_skip(message = "")
- skip message if defined?(JRUBY_VERSION)
-end
-
class ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
+
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
+ private def rubinius_skip(message = "")
+ skip message if RUBY_ENGINE == "rbx"
+ end
+ # Skips the current run on JRuby using Minitest::Assertions#skip
+ private def jruby_skip(message = "")
+ skip message if defined?(JRUBY_VERSION)
+ end
end
diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb
index b4a757d6b8..51ec8899b1 100644
--- a/actionview/test/actionpack/controller/render_test.rb
+++ b/actionview/test/actionpack/controller/render_test.rb
@@ -1,45 +1,14 @@
require "abstract_unit"
require "active_model"
+require "controller/fake_models"
class ApplicationController < ActionController::Base
self.view_paths = File.join(FIXTURE_LOAD_PATH, "actionpack")
end
-class Customer < Struct.new(:name, :id)
- extend ActiveModel::Naming
- include ActiveModel::Conversion
-
- undef_method :to_json
-
- def to_xml(options = {})
- if options[:builder]
- options[:builder].name name
- else
- "<name>#{name}</name>"
- end
- end
-
- def to_js(options = {})
- "name: #{name.inspect}"
- end
- alias :to_text :to_js
-
- def errors
- []
- end
-
- def persisted?
- id.present?
- end
-
- def cache_key
- name.to_s
- end
-end
-
module Quiz
#Models
- class Question < Struct.new(:name, :id)
+ Question = Struct.new(:name, :id) do
extend ActiveModel::Naming
include ActiveModel::Conversion
@@ -56,9 +25,6 @@ module Quiz
end
end
-class BadCustomer < Customer; end
-class GoodCustomer < Customer; end
-
module Fun
class GamesController < ApplicationController
def hello_world; end
diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb
index 0f7960b408..3b314588c7 100644
--- a/actionview/test/activerecord/form_helper_activerecord_test.rb
+++ b/actionview/test/activerecord/form_helper_activerecord_test.rb
@@ -45,7 +45,7 @@ class FormHelperActiveRecordTest < ActionView::TestCase
end
expected = whole_form("/developers/123", "edit_developer_123", "edit_developer", method: "patch") do
- '<input id="developer_projects_attributes_abc_name" name="developer[projects_attributes][abc][name]" type="text" value="project #321" />' +
+ '<input id="developer_projects_attributes_abc_name" name="developer[projects_attributes][abc][name]" type="text" value="project #321" />' \
'<input id="developer_projects_attributes_abc_id" name="developer[projects_attributes][abc][id]" type="hidden" value="321" />'
end
diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb
index 8495949975..e99c769dc2 100644
--- a/actionview/test/activerecord/polymorphic_routes_test.rb
+++ b/actionview/test/activerecord/polymorphic_routes_test.rb
@@ -45,7 +45,7 @@ class ModelDelegate
end
end
-module Blog
+module Weblog
class Post < ActiveRecord::Base
self.table_name = "projects"
end
@@ -61,7 +61,7 @@ end
class PolymorphicRoutesTest < ActionController::TestCase
include SharedTestRoutes.url_helpers
- self.default_url_options[:host] = "example.com"
+ default_url_options[:host] = "example.com"
def setup
@project = Project.new
@@ -72,8 +72,8 @@ class PolymorphicRoutesTest < ActionController::TestCase
@fax = Fax.new
@delegator = ModelDelegator.new
@series = Series.new
- @blog_post = Blog::Post.new
- @blog_blog = Blog::Blog.new
+ @blog_post = Weblog::Post.new
+ @blog_blog = Weblog::Blog.new
end
def assert_url(url, args)
@@ -86,8 +86,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
def test_string
with_test_routes do
- # FIXME: why are these different? Symbol case passes through to
- # `polymorphic_url`, but the String case doesn't.
+ assert_equal "/projects", polymorphic_path("projects")
assert_equal "http://example.com/projects", polymorphic_url("projects")
assert_equal "projects", url_for("projects")
end
diff --git a/actionview/test/activerecord/render_partial_with_record_identification_test.rb b/actionview/test/activerecord/render_partial_with_record_identification_test.rb
index 55886da30f..60c3ab3045 100644
--- a/actionview/test/activerecord/render_partial_with_record_identification_test.rb
+++ b/actionview/test/activerecord/render_partial_with_record_identification_test.rb
@@ -87,7 +87,7 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
end
end
-class Game < Struct.new(:name, :id)
+Game = Struct.new(:name, :id) do
extend ActiveModel::Naming
include ActiveModel::Conversion
def to_param
diff --git a/actionview/test/fixtures/test/_partial_iteration_1.erb b/actionview/test/fixtures/test/_partial_iteration_1.erb
new file mode 100644
index 0000000000..c0fdd4c22a
--- /dev/null
+++ b/actionview/test/fixtures/test/_partial_iteration_1.erb
@@ -0,0 +1 @@
+<%= defined?(partial_iteration_1_iteration) %>
diff --git a/actionview/test/fixtures/test/_partial_iteration_2.erb b/actionview/test/fixtures/test/_partial_iteration_2.erb
new file mode 100644
index 0000000000..50dd11db27
--- /dev/null
+++ b/actionview/test/fixtures/test/_partial_iteration_2.erb
@@ -0,0 +1 @@
+<%= defined?(partial_iteration_2_iteration) -%>
diff --git a/actionview/test/fixtures/test/render_file_instance_variable.erb b/actionview/test/fixtures/test/render_file_instance_variable.erb
new file mode 100644
index 0000000000..5344ac8a66
--- /dev/null
+++ b/actionview/test/fixtures/test/render_file_instance_variable.erb
@@ -0,0 +1 @@
+<%= @foo %> \ No newline at end of file
diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb
index ddc915895d..5250101220 100644
--- a/actionview/test/lib/controller/fake_models.rb
+++ b/actionview/test/lib/controller/fake_models.rb
@@ -1,6 +1,6 @@
require "active_model"
-class Customer < Struct.new(:name, :id)
+Customer = Struct.new(:name, :id) do
extend ActiveModel::Naming
include ActiveModel::Conversion
@@ -26,12 +26,16 @@ class Customer < Struct.new(:name, :id)
def persisted?
id.present?
end
-end
-class GoodCustomer < Customer
+ def cache_key
+ name.to_s
+ end
end
-class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost)
+class BadCustomer < Customer; end
+class GoodCustomer < Customer; end
+
+Post = Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) do
extend ActiveModel::Naming
include ActiveModel::Conversion
extend ActiveModel::Translation
@@ -163,7 +167,7 @@ module Blog
true
end
- class Post < Struct.new(:title, :id)
+ Post = Struct.new(:title, :id) do
extend ActiveModel::Naming
include ActiveModel::Conversion
@@ -183,8 +187,7 @@ class ArelLike
end
end
-class Car < Struct.new(:color)
-end
+Car = Struct.new(:color)
class Plane
attr_reader :to_key
diff --git a/actionview/test/template/active_model_helper_test.rb b/actionview/test/template/active_model_helper_test.rb
index 8b8f686f96..6b63aa25a5 100644
--- a/actionview/test/template/active_model_helper_test.rb
+++ b/actionview/test/template/active_model_helper_test.rb
@@ -4,7 +4,7 @@ class ActiveModelHelperTest < ActionView::TestCase
tests ActionView::Helpers::ActiveModelHelper
silence_warnings do
- class Post < Struct.new(:author_name, :body, :updated_at)
+ Post = Struct.new(:author_name, :body, :updated_at) do
include ActiveModel::Conversion
include ActiveModel::Validations
diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb
index e9a923dd72..1245a1a966 100644
--- a/actionview/test/template/atom_feed_helper_test.rb
+++ b/actionview/test/template/atom_feed_helper_test.rb
@@ -1,6 +1,6 @@
require "abstract_unit"
-class Scroll < Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at)
+Scroll = Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at) do
extend ActiveModel::Naming
include ActiveModel::Conversion
diff --git a/actionview/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb
index 40ac867b38..adb2be9be4 100644
--- a/actionview/test/template/compiled_templates_test.rb
+++ b/actionview/test/template/compiled_templates_test.rb
@@ -38,6 +38,10 @@ class CompiledTemplatesTest < ActiveSupport::TestCase
assert_equal "🎂", render(file: "test/render_file_unicode_local", locals: { 🎃: "🎂" })
end
+ def test_template_with_instance_variable_identifier
+ assert_equal "bar", render(file: "test/render_file_instance_variable", locals: { "@foo": "bar" })
+ end
+
def test_template_gets_recompiled_when_using_different_keys_in_local_assigns
assert_equal "one", render(file: "test/render_file_with_locals_and_default")
assert_equal "two", render(file: "test/render_file_with_locals_and_default", locals: { secret: "two" })
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index a84ac18bee..bfd3ecd6fd 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -1407,7 +1407,6 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_with_custom_prompt
-
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="">Choose year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
expected << "</select>\n"
diff --git a/actionview/test/template/erb/deprecated_erubis_implementation_test.rb b/actionview/test/template/erb/deprecated_erubis_implementation_test.rb
new file mode 100644
index 0000000000..aaf99f85c0
--- /dev/null
+++ b/actionview/test/template/erb/deprecated_erubis_implementation_test.rb
@@ -0,0 +1,13 @@
+require "abstract_unit"
+
+module ERBTest
+ class DeprecatedErubisImplementationTest < ActionView::TestCase
+ test "Erubis implementation is deprecated" do
+ assert_deprecated "ActionView::Template::Handlers::Erubis is deprecated and will be removed from Rails 5.2. Switch to ActionView::Template::Handlers::ERB::Erubi instead." do
+ assert_equal "ActionView::Template::Handlers::ERB::Erubis", ActionView::Template::Handlers::Erubis.to_s
+
+ assert_nothing_raised { Class.new(ActionView::Template::Handlers::Erubis) }
+ end
+ end
+ end
+end
diff --git a/actionview/test/template/erb/helper.rb b/actionview/test/template/erb/helper.rb
index a1973068d5..bc1eedc15f 100644
--- a/actionview/test/template/erb/helper.rb
+++ b/actionview/test/template/erb/helper.rb
@@ -14,7 +14,7 @@ module ERBTest
class BlockTestCase < ActiveSupport::TestCase
def render_content(start, inside)
template = block_helper(start, inside)
- ActionView::Template::Handlers::Erubis.new(template).evaluate(ViewContext.new)
+ ActionView::Template::Handlers::ERB.erb_implementation.new(template).evaluate(ViewContext.new)
end
def block_helper(str, rest)
diff --git a/actionview/test/template/erb/tag_helper_test.rb b/actionview/test/template/erb/tag_helper_test.rb
index 84e328d8be..233f37c48a 100644
--- a/actionview/test/template/erb/tag_helper_test.rb
+++ b/actionview/test/template/erb/tag_helper_test.rb
@@ -18,8 +18,8 @@ module ERBTest
end
test "percent equals works with form tags" do
- expected_output = %r{<form.*action="foo".*method="post">.*hello*</form>}
- assert_match expected_output, render_content("form_tag('foo')", "<%= 'hello' %>")
+ expected_output = %r{<form.*action="/foo".*method="post">.*hello*</form>}
+ assert_match expected_output, render_content("form_tag('/foo')", "<%= 'hello' %>")
end
test "percent equals works with fieldset tags" do
diff --git a/actionview/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb
index 0a16364ed3..4412ea37fa 100644
--- a/actionview/test/template/erb_util_test.rb
+++ b/actionview/test/template/erb_util_test.rb
@@ -51,7 +51,12 @@ class ErbUtilTest < ActiveSupport::TestCase
def test_json_escape_does_not_alter_json_string_meaning
JSON_ESCAPE_TEST_CASES.each do |(raw, _)|
- assert_equal ActiveSupport::JSON.decode(raw), ActiveSupport::JSON.decode(json_escape(raw))
+ expected = ActiveSupport::JSON.decode(raw)
+ if expected.nil?
+ assert_nil ActiveSupport::JSON.decode(json_escape(raw))
+ else
+ assert_equal expected, ActiveSupport::JSON.decode(json_escape(raw))
+ end
end
end
diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb
index 3774dcf872..6160524eb3 100644
--- a/actionview/test/template/form_collections_helper_test.rb
+++ b/actionview/test/template/form_collections_helper_test.rb
@@ -1,7 +1,6 @@
require "abstract_unit"
-class Category < Struct.new(:id, :name)
-end
+Category = Struct.new(:id, :name)
class FormCollectionsHelperTest < ActionView::TestCase
def assert_no_select(selector, value = nil)
diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb
index 3a91c7dce7..a4a2966ff9 100644
--- a/actionview/test/template/form_helper/form_with_test.rb
+++ b/actionview/test/template/form_helper/form_with_test.rb
@@ -310,13 +310,13 @@ class FormWithActsLikeFormForTest < FormWithTest
end
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' />" +
- "<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' />" +
- "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" +
- "<button name='button' type='submit'>Create post</button>" +
+ "<label for='post_title'>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' />" \
+ "<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
@@ -330,7 +330,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts") do
- '<label for="title">Label me</label>' +
+ '<label for="title">Label me</label>' \
'<input type="text" name="title">'
end
@@ -344,7 +344,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123") do
- '<label for="title">Label me</label>' +
+ '<label for="title">Label me</label>' \
'<input type="text" name="title">'
end
@@ -400,10 +400,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts") do
- "<input type='hidden' name='post[active]' value='' />" +
- "<input name='post[active]' type='radio' value='true' />" +
- "<label for='post_active_true'>true</label>" +
- "<input checked='checked' name='post[active]' type='radio' value='false' />" +
+ "<input type='hidden' name='post[active]' value='' />" \
+ "<input name='post[active]' type='radio' value='true' />" \
+ "<label for='post_active_true'>true</label>" \
+ "<input checked='checked' name='post[active]' type='radio' value='false' />" \
"<label for='post_active_false'>false</label>"
end
@@ -422,12 +422,12 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts") do
- "<input type='hidden' name='post[active]' value='' />" +
- "<label for='post_active_true'>" +
- "<input name='post[active]' type='radio' value='true' />" +
- "true</label>" +
- "<label for='post_active_false'>" +
- "<input checked='checked' name='post[active]' type='radio' value='false' />" +
+ "<input type='hidden' name='post[active]' value='' />" \
+ "<label for='post_active_true'>" \
+ "<input name='post[active]' type='radio' value='true' />" \
+ "true</label>" \
+ "<label for='post_active_false'>" \
+ "<input checked='checked' name='post[active]' type='radio' value='false' />" \
"false</label>"
end
@@ -448,13 +448,13 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts") do
- "<input type='hidden' name='post[active]' value='' />" +
- "<label for='post_active_true'>" +
- "<input name='post[active]' type='radio' value='true' />" +
- "true</label>" +
- "<label for='post_active_false'>" +
- "<input checked='checked' name='post[active]' type='radio' value='false' />" +
- "false</label>" +
+ "<input type='hidden' name='post[active]' value='' />" \
+ "<label for='post_active_true'>" \
+ "<input name='post[active]' type='radio' value='true' />" \
+ "true</label>" \
+ "<label for='post_active_false'>" \
+ "<input checked='checked' name='post[active]' type='radio' value='false' />" \
+ "false</label>" \
"<input name='post[id]' type='hidden' value='1' />"
end
@@ -470,10 +470,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts") do
- "<input type='hidden' name='post[1][active]' value='' />" +
- "<input name='post[1][active]' type='radio' value='true' />" +
- "<label for='post_1_active_true'>true</label>" +
- "<input checked='checked' name='post[1][active]' type='radio' value='false' />" +
+ "<input type='hidden' name='post[1][active]' value='' />" \
+ "<input name='post[1][active]' type='radio' value='true' />" \
+ "<label for='post_1_active_true'>true</label>" \
+ "<input checked='checked' name='post[1][active]' type='radio' value='false' />" \
"<label for='post_1_active_false'>false</label>"
end
@@ -489,12 +489,12 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts") do
- "<input name='post[tag_ids][]' type='hidden' value='' />" +
- "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" +
- "<label for='post_tag_ids_1'>Tag 1</label>" +
- "<input name='post[tag_ids][]' type='checkbox' value='2' />" +
- "<label for='post_tag_ids_2'>Tag 2</label>" +
- "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" \
+ "<label for='post_tag_ids_1'>Tag 1</label>" \
+ "<input name='post[tag_ids][]' type='checkbox' value='2' />" \
+ "<label for='post_tag_ids_2'>Tag 2</label>" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" \
"<label for='post_tag_ids_3'>Tag 3</label>"
end
@@ -513,15 +513,15 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts") do
- "<input name='post[tag_ids][]' type='hidden' value='' />" +
- "<label for='post_tag_ids_1'>" +
- "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" +
- "Tag 1</label>" +
- "<label for='post_tag_ids_2'>" +
- "<input name='post[tag_ids][]' type='checkbox' value='2' />" +
- "Tag 2</label>" +
- "<label for='post_tag_ids_3'>" +
- "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />" \
+ "<label for='post_tag_ids_1'>" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" \
+ "Tag 1</label>" \
+ "<label for='post_tag_ids_2'>" \
+ "<input name='post[tag_ids][]' type='checkbox' value='2' />" \
+ "Tag 2</label>" \
+ "<label for='post_tag_ids_3'>" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" \
"Tag 3</label>"
end
@@ -543,16 +543,16 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts") do
- "<input name='post[tag_ids][]' type='hidden' value='' />" +
- "<label for='post_tag_ids_1'>" +
- "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" +
- "Tag 1</label>" +
- "<label for='post_tag_ids_2'>" +
- "<input name='post[tag_ids][]' type='checkbox' value='2' />" +
- "Tag 2</label>" +
- "<label for='post_tag_ids_3'>" +
- "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" +
- "Tag 3</label>" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />" \
+ "<label for='post_tag_ids_1'>" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" \
+ "Tag 1</label>" \
+ "<label for='post_tag_ids_2'>" \
+ "<input name='post[tag_ids][]' type='checkbox' value='2' />" \
+ "Tag 2</label>" \
+ "<label for='post_tag_ids_3'>" \
+ "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" \
+ "Tag 3</label>" \
"<input name='post[id]' type='hidden' value='1' />"
end
@@ -569,8 +569,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts") do
- "<input name='post[1][tag_ids][]' type='hidden' value='' />" +
- "<input checked='checked' name='post[1][tag_ids][]' type='checkbox' value='1' />" +
+ "<input name='post[1][tag_ids][]' type='hidden' value='' />" \
+ "<input checked='checked' name='post[1][tag_ids][]' type='checkbox' value='1' />" \
"<label for='post_1_tag_ids_1'>Tag 1</label>"
end
@@ -636,7 +636,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.' />" \
"<input name='commit' data-disable-with='Edit post' type='submit' value='Edit post' />"
end
@@ -653,11 +653,11 @@ class FormWithActsLikeFormForTest < FormWithTest
end
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[secret]' value='0' type='hidden' />" +
- "<input name='other_name[secret]' checked='checked' value='1' type='checkbox' />" +
+ "<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[secret]' value='0' type='hidden' />" \
+ "<input name='other_name[secret]' checked='checked' value='1' type='checkbox' />" \
"<input name='commit' value='Create post' data-disable-with='Create post' type='submit' />"
end
@@ -672,9 +672,9 @@ 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[secret]' type='hidden' value='0' />" +
+ "<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' />"
end
@@ -689,9 +689,9 @@ 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[secret]' type='hidden' value='0' />" +
+ "<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' />"
end
@@ -720,9 +720,9 @@ 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[secret]' type='hidden' value='0' />" +
+ "<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' />"
end
@@ -761,9 +761,9 @@ 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[secret]' type='hidden' value='0' />" +
+ "<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' />"
end
@@ -779,10 +779,10 @@ class FormWithActsLikeFormForTest < FormWithTest
end
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][secret]' type='hidden' value='0' />" +
+ "<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][secret]' type='hidden' value='0' />" \
"<input name='post[123][secret]' checked='checked' type='checkbox' value='1' />"
end
@@ -797,9 +797,9 @@ 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[][secret]' type='hidden' value='0' />" +
+ "<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' />"
end
@@ -814,8 +814,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
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'><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>" \
"<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
@@ -832,8 +832,8 @@ class FormWithActsLikeFormForTest < FormWithTest
end
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'><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>" \
"<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
@@ -847,7 +847,7 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", method: "patch") do
- "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" +
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" \
"<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>"
end
@@ -863,9 +863,9 @@ class FormWithActsLikeFormForTest < FormWithTest
end
expected = whole_form("/posts/123", "namespace_edit_post_123", "edit_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[secret]' type='hidden' value='0' />" +
+ "<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' />"
end
@@ -1009,7 +1009,7 @@ 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][title]' type='text' value='Hello World' />" \
"<input name='post[123][comment][][name]' type='text' value='new comment' />"
end
@@ -1025,7 +1025,7 @@ 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][title]' type='text' value='Hello World' />" \
"<input name='post[1][comment][1][name]' type='text' value='new comment' />"
end
@@ -1135,7 +1135,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" />' \
'<input name="post[author_attributes][name]" type="text" value="new author" />'
end
@@ -1162,8 +1162,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" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />' \
'<input name="post[author_attributes][id]" type="hidden" value="321" />'
end
@@ -1181,8 +1181,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" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />' \
'<input name="post[author_attributes][id]" type="hidden" value="321" />'
end
@@ -1200,7 +1200,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" />' \
'<input name="post[author_attributes][name]" type="text" value="author #321" />'
end
@@ -1218,7 +1218,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" />' \
'<input name="post[author_attributes][name]" type="text" value="author #321" />'
end
@@ -1236,8 +1236,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" />' \
+ '<input name="post[author_attributes][name]" type="text" value="author #321" />' \
'<input name="post[author_attributes][id]" type="hidden" value="321" />'
end
@@ -1256,8 +1256,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][id]" type="hidden" value="321" />' +
+ '<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" />'
end
@@ -1277,10 +1277,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 #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[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" />'
end
@@ -1304,10 +1304,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[author_attributes][id]" type="hidden" value="321" />' +
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<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" />'
end
@@ -1331,9 +1331,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[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<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" />'
end
@@ -1357,10 +1357,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[author_attributes][id]" type="hidden" value="321" />' +
- '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<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" />'
end
@@ -1380,10 +1380,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 #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[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" />'
end
@@ -1404,10 +1404,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][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[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" />'
end
@@ -1427,8 +1427,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[comments_attributes][0][name]" type="text" value="new comment" />' +
+ '<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" />'
end
@@ -1448,9 +1448,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="comment #321" />' +
- '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
+ '<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" />'
end
@@ -1483,10 +1483,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 #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[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" />'
end
@@ -1504,10 +1504,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 #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[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" />'
end
@@ -1539,10 +1539,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 #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[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" />'
end
@@ -1562,9 +1562,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="comment #321" />' +
- '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
+ '<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" />'
end
@@ -1582,7 +1582,7 @@ 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][name]" type="text" value="comment #321" />' \
'<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
end
@@ -1599,7 +1599,7 @@ 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][name]" type="text" value="comment #321" />' \
'<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
end
@@ -1622,7 +1622,7 @@ 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][name]" type="text" value="comment #321" />' \
'<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
end
@@ -1708,17 +1708,17 @@ 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[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" />'
end
@@ -1749,9 +1749,9 @@ 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[secret]' type='hidden' value='0' />" +
+ "<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' />"
assert_dom_equal expected, output_buffer
@@ -1765,9 +1765,9 @@ 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][secret]' type='hidden' value='0' />" +
+ "<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][secret]' type='hidden' value='0' />" \
"<input name='post[123][secret]' checked='checked' type='checkbox' value='1' />"
assert_dom_equal expected, output_buffer
@@ -1781,9 +1781,9 @@ 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[][secret]' type='hidden' value='0' />" +
+ "<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' />"
assert_dom_equal expected, output_buffer
@@ -1797,9 +1797,9 @@ 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][secret]' type='hidden' value='0' />" +
+ "<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][secret]' type='hidden' value='0' />" \
"<input name='post[abc][secret]' checked='checked' type='checkbox' value='1' />"
assert_dom_equal expected, output_buffer
@@ -1813,9 +1813,9 @@ 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[secret]' type='hidden' value='0' />" +
+ "<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' />"
assert_dom_equal expected, output_buffer
@@ -1829,9 +1829,9 @@ 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[secret]' type='hidden' value='0' />" +
+ "<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' />"
assert_dom_equal expected, output_buffer
@@ -1843,7 +1843,7 @@ class FormWithActsLikeFormForTest < FormWithTest
concat f.text_field(:title)
end
- assert_dom_equal "<label for=\"author_post_title\">Title</label>" +
+ assert_dom_equal "<label for=\"author_post_title\">Title</label>" \
"<input name='author[post][title]' type='text' value='Hello World' />",
output_buffer
end
@@ -1854,7 +1854,7 @@ class FormWithActsLikeFormForTest < FormWithTest
concat f.text_field(:title)
end
- assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" +
+ assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" \
"<input name='author[post][1][title]' type='text' value='Hello World' />",
output_buffer
end
@@ -1874,9 +1874,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='parent_post[secret]' type='hidden' value='0' />" +
+ "<input name='post[title]' type='text' value='Hello World' />" \
+ "<textarea name='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' />"
end
@@ -1894,8 +1894,8 @@ 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' />" \
+ "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[comment][name]' type='text' value='new comment' />"
end
@@ -1934,8 +1934,8 @@ 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='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/>"
end
@@ -1953,8 +1953,8 @@ 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='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/>"
end
@@ -2012,8 +2012,8 @@ 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='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/>"
assert_dom_equal expected, output_buffer
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 2bc0434771..b3a180b28a 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -257,11 +257,11 @@ class FormHelperTest < ActionView::TestCase
end
def test_label_with_non_active_record_object
- form_for(OpenStruct.new(name: "ok"), as: "person", url: "an_url", html: { id: "create-person" }) do |f|
+ form_for(OpenStruct.new(name: "ok"), as: "person", url: "/an", html: { id: "create-person" }) do |f|
f.label(:name)
end
- expected = whole_form("an_url", "create-person", "new_person", method: "post") do
+ expected = whole_form("/an", "create-person", "new_person", method: "post") do
'<label for="person_name">Name</label>'
end
@@ -767,7 +767,6 @@ class FormHelperTest < ActionView::TestCase
'<input name="post[bar][comment_ids][]" type="hidden" value="0" /><input checked="checked" id="post_bar_comment_ids_3" name="post[bar][comment_ids][]" type="checkbox" value="3" />',
check_box("post", "comment_ids", { multiple: true, index: "bar" }, 3)
)
-
end
def test_checkbox_disabled_disables_hidden_field
@@ -1515,13 +1514,13 @@ class FormHelperTest < ActionView::TestCase
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>" +
+ "<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
@@ -1536,10 +1535,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
- "<input type='hidden' name='post[active]' value='' />" +
- "<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
- "<label for='post_active_true'>true</label>" +
- "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
+ "<input type='hidden' name='post[active]' value='' />" \
+ "<input id='post_active_true' name='post[active]' type='radio' value='true' />" \
+ "<label for='post_active_true'>true</label>" \
+ "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" \
"<label for='post_active_false'>false</label>"
end
@@ -1558,12 +1557,12 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
- "<input type='hidden' name='post[active]' value='' />" +
- "<label for='post_active_true'>" +
- "<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
- "true</label>" +
- "<label for='post_active_false'>" +
- "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
+ "<input type='hidden' name='post[active]' value='' />" \
+ "<label for='post_active_true'>" \
+ "<input id='post_active_true' name='post[active]' type='radio' value='true' />" \
+ "true</label>" \
+ "<label for='post_active_false'>" \
+ "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" \
"false</label>"
end
@@ -1584,13 +1583,13 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post_1", "new_post") do
- "<input type='hidden' name='post[active]' value='' />" +
- "<label for='post_active_true'>" +
- "<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
- "true</label>" +
- "<label for='post_active_false'>" +
- "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
- "false</label>" +
+ "<input type='hidden' name='post[active]' value='' />" \
+ "<label for='post_active_true'>" \
+ "<input id='post_active_true' name='post[active]' type='radio' value='true' />" \
+ "true</label>" \
+ "<label for='post_active_false'>" \
+ "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" \
+ "false</label>" \
"<input id='post_id' name='post[id]' type='hidden' value='1' />"
end
@@ -1606,10 +1605,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "foo_new_post", "new_post") do
- "<input type='hidden' name='post[active]' value='' />" +
- "<input id='foo_post_active_true' name='post[active]' type='radio' value='true' />" +
- "<label for='foo_post_active_true'>true</label>" +
- "<input checked='checked' id='foo_post_active_false' name='post[active]' type='radio' value='false' />" +
+ "<input type='hidden' name='post[active]' value='' />" \
+ "<input id='foo_post_active_true' name='post[active]' type='radio' value='true' />" \
+ "<label for='foo_post_active_true'>true</label>" \
+ "<input checked='checked' id='foo_post_active_false' name='post[active]' type='radio' value='false' />" \
"<label for='foo_post_active_false'>false</label>"
end
@@ -1625,10 +1624,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
- "<input type='hidden' name='post[1][active]' value='' />" +
- "<input id='post_1_active_true' name='post[1][active]' type='radio' value='true' />" +
- "<label for='post_1_active_true'>true</label>" +
- "<input checked='checked' id='post_1_active_false' name='post[1][active]' type='radio' value='false' />" +
+ "<input type='hidden' name='post[1][active]' value='' />" \
+ "<input id='post_1_active_true' name='post[1][active]' type='radio' value='true' />" \
+ "<label for='post_1_active_true'>true</label>" \
+ "<input checked='checked' id='post_1_active_false' name='post[1][active]' type='radio' value='false' />" \
"<label for='post_1_active_false'>false</label>"
end
@@ -1644,12 +1643,12 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
- "<input name='post[tag_ids][]' type='hidden' value='' />" +
- "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
- "<label for='post_tag_ids_1'>Tag 1</label>" +
- "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" +
- "<label for='post_tag_ids_2'>Tag 2</label>" +
- "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />" \
+ "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" \
+ "<label for='post_tag_ids_1'>Tag 1</label>" \
+ "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" \
+ "<label for='post_tag_ids_2'>Tag 2</label>" \
+ "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" \
"<label for='post_tag_ids_3'>Tag 3</label>"
end
@@ -1668,15 +1667,15 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
- "<input name='post[tag_ids][]' type='hidden' value='' />" +
- "<label for='post_tag_ids_1'>" +
- "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
- "Tag 1</label>" +
- "<label for='post_tag_ids_2'>" +
- "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" +
- "Tag 2</label>" +
- "<label for='post_tag_ids_3'>" +
- "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />" \
+ "<label for='post_tag_ids_1'>" \
+ "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" \
+ "Tag 1</label>" \
+ "<label for='post_tag_ids_2'>" \
+ "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" \
+ "Tag 2</label>" \
+ "<label for='post_tag_ids_3'>" \
+ "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" \
"Tag 3</label>"
end
@@ -1698,16 +1697,16 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post_1", "new_post") do
- "<input name='post[tag_ids][]' type='hidden' value='' />" +
- "<label for='post_tag_ids_1'>" +
- "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
- "Tag 1</label>" +
- "<label for='post_tag_ids_2'>" +
- "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" +
- "Tag 2</label>" +
- "<label for='post_tag_ids_3'>" +
- "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
- "Tag 3</label>" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />" \
+ "<label for='post_tag_ids_1'>" \
+ "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" \
+ "Tag 1</label>" \
+ "<label for='post_tag_ids_2'>" \
+ "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" \
+ "Tag 2</label>" \
+ "<label for='post_tag_ids_3'>" \
+ "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" \
+ "Tag 3</label>" \
"<input id='post_id' name='post[id]' type='hidden' value='1' />"
end
@@ -1724,8 +1723,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "foo_new_post", "new_post") do
- "<input name='post[tag_ids][]' type='hidden' value='' />" +
- "<input checked='checked' id='foo_post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />" \
+ "<input checked='checked' id='foo_post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" \
"<label for='foo_post_tag_ids_1'>Tag 1</label>"
end
@@ -1742,8 +1741,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post") do
- "<input name='post[1][tag_ids][]' type='hidden' value='' />" +
- "<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" +
+ "<input name='post[1][tag_ids][]' type='hidden' value='' />" \
+ "<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" \
"<label for='post_1_tag_ids_1'>Tag 1</label>"
end
@@ -1797,7 +1796,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/44", "edit_post_44", "edit_post", method: "patch") do
- "<input name='post[title]' type='text' id='post_title' value='And his name will be forty and four.' />" +
+ "<input name='post[title]' type='text' id='post_title' value='And his name will be forty and four.' />" \
"<input name='commit' data-disable-with='Edit post' type='submit' value='Edit post' />"
end
@@ -1814,11 +1813,11 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "create-post", "edit_other_name", method: "patch") do
- "<label for='other_name_title' class='post_title'>Title</label>" +
- "<input name='other_name[title]' id='other_name_title' value='Hello World' type='text' />" +
- "<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' id='other_name_secret' value='1' type='checkbox' />" +
+ "<label for='other_name_title' class='post_title'>Title</label>" \
+ "<input name='other_name[title]' id='other_name_title' value='Hello World' type='text' />" \
+ "<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' id='other_name_secret' value='1' type='checkbox' />" \
"<input name='commit' value='Create post' data-disable-with='Create post' type='submit' />"
end
@@ -1847,9 +1846,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/", "create-post", "edit_post", method: "delete") do
- "<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[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' />"
end
@@ -1864,9 +1863,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/", "create-post", "edit_post", method: "delete") do
- "<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[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' />"
end
@@ -1895,9 +1894,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/", "create-post", "edit_post", method: "patch", remote: true) do
- "<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[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' />"
end
@@ -1936,9 +1935,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/", "create-post", "edit_post", method: "patch", remote: true) do
- "<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[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' />"
end
@@ -1955,9 +1954,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", "new_post", "new_post", remote: true) do
- "<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[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' />"
end
@@ -1973,9 +1972,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/", "create-post") do
- "<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[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' />"
end
@@ -1991,10 +1990,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post[]", "edit_post[]", method: "patch") do
- "<label for='post_123_title'>Title</label>" +
- "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" +
- "<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' />" +
+ "<label for='post_123_title'>Title</label>" \
+ "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" \
+ "<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' id='post_123_secret' value='1' />"
end
@@ -2009,9 +2008,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post[]", "edit_post[]", method: "patch") do
- "<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[][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' />"
end
@@ -2026,8 +2025,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", 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' id='post_author_name' value='' /></div>" +
+ "<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' id='post_author_name' value='' /></div>" \
"<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
@@ -2044,8 +2043,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", 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' id='post_author_name' value='' /></div>" +
+ "<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' id='post_author_name' value='' /></div>" \
"<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"
end
@@ -2059,7 +2058,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" +
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" \
"<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>"
end
@@ -2074,9 +2073,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "namespace_edit_post_123", "edit_post", method: "patch") do
- "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" +
- "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" \
+ "<textarea name='post[body]' id='namespace_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='namespace_post_secret' value='1' />"
end
@@ -2098,7 +2097,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "namespace_edit_post_123", "edit_post", method: "patch") do
- "<label for='namespace_post_title'>Title</label>" +
+ "<label for='namespace_post_title'>Title</label>" \
"<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />"
end
@@ -2124,7 +2123,7 @@ class FormHelperTest < ActionView::TestCase
end
expected_1 = whole_form("/posts/123", "namespace_1_edit_post_123", "edit_post", method: "patch") do
- "<label for='namespace_1_post_title'>Title</label>" +
+ "<label for='namespace_1_post_title'>Title</label>" \
"<input name='post[title]' type='text' id='namespace_1_post_title' value='Hello World' />"
end
@@ -2136,7 +2135,7 @@ class FormHelperTest < ActionView::TestCase
end
expected_2 = whole_form("/posts/123", "namespace_2_edit_post_123", "edit_post", method: "patch") do
- "<label for='namespace_2_post_title'>Title</label>" +
+ "<label for='namespace_2_post_title'>Title</label>" \
"<input name='post[title]' type='text' id='namespace_2_post_title' value='Hello World' />"
end
@@ -2154,8 +2153,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "namespace_edit_post_123", "edit_post", method: "patch") do
- "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" \
+ "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" \
"<input name='post[comment][body]' type='text' id='namespace_post_comment_body' value='Hello World' />"
end
@@ -2266,8 +2265,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post[]", "edit_post[]", method: "patch") do
- "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" +
- "<input name='post[123][comment][][name]' type='text' id='post_123_comment__name' value='new comment' />" +
+ "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" \
+ "<input name='post[123][comment][][name]' type='text' id='post_123_comment__name' value='new comment' />" \
"<input name='post[123][body]' type='text' id='post_123_body' value='Back to the hill and over it again!' />"
end
@@ -2283,7 +2282,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- "<input name='post[1][title]' type='text' id='post_1_title' value='Hello World' />" +
+ "<input name='post[1][title]' type='text' id='post_1_title' value='Hello World' />" \
"<input name='post[1][comment][1][name]' type='text' id='post_1_comment_1_name' value='new comment' />"
end
@@ -2393,7 +2392,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
'<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="new author" />'
end
@@ -2420,8 +2419,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
end
@@ -2439,8 +2438,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
end
@@ -2458,7 +2457,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
'<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />'
end
@@ -2476,7 +2475,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
'<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />'
end
@@ -2494,8 +2493,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
end
@@ -2514,8 +2513,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' \
'<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />'
end
@@ -2535,10 +2534,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
end
@@ -2562,10 +2561,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
- '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \
+ '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
end
@@ -2589,9 +2588,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
end
@@ -2615,10 +2614,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
- '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \
+ '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
end
@@ -2638,10 +2637,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
end
@@ -2662,10 +2661,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
+ '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' \
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
end
@@ -2685,8 +2684,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="new comment" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="new comment" />' \
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />'
end
@@ -2706,9 +2705,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' \
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />'
end
@@ -2741,10 +2740,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
end
@@ -2762,10 +2761,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
end
@@ -2797,10 +2796,10 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
end
@@ -2820,9 +2819,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' \
'<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />'
end
@@ -2840,7 +2839,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \
'<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
end
@@ -2857,7 +2856,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \
'<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
end
@@ -2880,7 +2879,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \
'<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
end
@@ -2966,17 +2965,17 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' +
- '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' +
- '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
- '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" type="text" value="tag #123" />' +
- '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' +
- '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' +
- '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' +
- '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" type="text" value="tag #456" />' +
- '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' +
- '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \
+ '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' \
+ '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' \
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' \
+ '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" type="text" value="tag #123" />' \
+ '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' \
+ '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' \
+ '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' \
+ '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" type="text" value="tag #456" />' \
+ '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' \
+ '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' \
'<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />'
end
@@ -3007,9 +3006,9 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<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[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' />"
assert_dom_equal expected, output_buffer
@@ -3023,9 +3022,9 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" +
- "<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][title]' type='text' id='post_123_title' value='Hello World' />" \
+ "<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' id='post_123_secret' value='1' />"
assert_dom_equal expected, output_buffer
@@ -3039,9 +3038,9 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<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[][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' />"
assert_dom_equal expected, output_buffer
@@ -3055,9 +3054,9 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<input name='post[abc][title]' type='text' id='post_abc_title' value='Hello World' />" +
- "<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][title]' type='text' id='post_abc_title' value='Hello World' />" \
+ "<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' id='post_abc_secret' value='1' />"
assert_dom_equal expected, output_buffer
@@ -3071,9 +3070,9 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<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[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' />"
assert_dom_equal expected, output_buffer
@@ -3087,9 +3086,9 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<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[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' />"
assert_dom_equal expected, output_buffer
@@ -3101,7 +3100,7 @@ class FormHelperTest < ActionView::TestCase
concat f.text_field(:title)
end
- assert_dom_equal "<label for=\"author_post_title\">Title</label>" +
+ assert_dom_equal "<label for=\"author_post_title\">Title</label>" \
"<input name='author[post][title]' type='text' id='author_post_title' value='Hello World' />",
output_buffer
end
@@ -3112,7 +3111,7 @@ class FormHelperTest < ActionView::TestCase
concat f.text_field(:title)
end
- assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" +
+ assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" \
"<input name='author[post][1][title]' type='text' id='author_post_1_title' value='Hello World' />",
output_buffer
end
@@ -3132,9 +3131,9 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do
- "<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='parent_post[secret]' type='hidden' value='0' />" +
+ "<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='parent_post[secret]' type='hidden' value='0' />" \
"<input name='parent_post[secret]' checked='checked' type='checkbox' id='parent_post_secret' value='1' />"
end
@@ -3152,8 +3151,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do
- "<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[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[comment][name]' type='text' id='post_comment_name' value='new comment' />"
end
@@ -3192,8 +3191,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><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='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><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' id='post_secret' value='1' /><br/>"
end
@@ -3211,8 +3210,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do
- "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><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='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><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' id='post_secret' value='1' /><br/>"
end
@@ -3270,8 +3269,8 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><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='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><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' id='post_secret' value='1' /><br/>"
assert_dom_equal expected, output_buffer
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index 51f5d25958..258dcdb806 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -393,30 +393,30 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_options_no_params
opts = time_zone_options_for_select
- assert_dom_equal "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\">D</option>\n" +
+ assert_dom_equal "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\">D</option>\n" \
"<option value=\"E\">E</option>",
opts
end
def test_time_zone_options_with_selected
opts = time_zone_options_for_select("D")
- assert_dom_equal "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ assert_dom_equal "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
"<option value=\"E\">E</option>",
opts
end
def test_time_zone_options_with_unknown_selected
opts = time_zone_options_for_select("K")
- assert_dom_equal "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\">D</option>\n" +
+ assert_dom_equal "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\">D</option>\n" \
"<option value=\"E\">E</option>",
opts
end
@@ -424,11 +424,11 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_options_with_priority_zones
zones = [ ActiveSupport::TimeZone.new("B"), ActiveSupport::TimeZone.new("E") ]
opts = time_zone_options_for_select(nil, zones)
- assert_dom_equal "<option value=\"B\">B</option>\n" +
- "<option value=\"E\">E</option>" +
- "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"C\">C</option>\n" +
+ assert_dom_equal "<option value=\"B\">B</option>\n" \
+ "<option value=\"E\">E</option>" \
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"C\">C</option>\n" \
"<option value=\"D\">D</option>",
opts
end
@@ -436,11 +436,11 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_options_with_selected_priority_zones
zones = [ ActiveSupport::TimeZone.new("B"), ActiveSupport::TimeZone.new("E") ]
opts = time_zone_options_for_select("E", zones)
- assert_dom_equal "<option value=\"B\">B</option>\n" +
- "<option value=\"E\" selected=\"selected\">E</option>" +
- "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"C\">C</option>\n" +
+ assert_dom_equal "<option value=\"B\">B</option>\n" \
+ "<option value=\"E\" selected=\"selected\">E</option>" \
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"C\">C</option>\n" \
"<option value=\"D\">D</option>",
opts
end
@@ -448,11 +448,11 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_options_with_unselected_priority_zones
zones = [ ActiveSupport::TimeZone.new("B"), ActiveSupport::TimeZone.new("E") ]
opts = time_zone_options_for_select("C", zones)
- assert_dom_equal "<option value=\"B\">B</option>\n" +
- "<option value=\"E\">E</option>" +
- "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"C\" selected=\"selected\">C</option>\n" +
+ assert_dom_equal "<option value=\"B\">B</option>\n" \
+ "<option value=\"E\">E</option>" \
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"C\" selected=\"selected\">C</option>\n" \
"<option value=\"D\">D</option>",
opts
end
@@ -1031,12 +1031,12 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_select
@firm = Firm.new("D")
html = time_zone_select("firm", "time_zone")
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
end
@@ -1049,12 +1049,12 @@ class FormOptionsHelperTest < ActionView::TestCase
end
assert_dom_equal(
- "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
output_buffer
)
@@ -1068,12 +1068,12 @@ class FormOptionsHelperTest < ActionView::TestCase
end
assert_dom_equal(
- "<select id=\"firm_305_time_zone\" name=\"firm[305][time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ "<select id=\"firm_305_time_zone\" name=\"firm[305][time_zone]\">" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
output_buffer
)
@@ -1088,12 +1088,12 @@ class FormOptionsHelperTest < ActionView::TestCase
end
assert_dom_equal(
- "<select id=\"firm_305_time_zone\" name=\"firm[305][time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ "<select id=\"firm_305_time_zone\" name=\"firm[305][time_zone]\">" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
output_buffer
)
@@ -1102,13 +1102,13 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_select_with_blank
@firm = Firm.new("D")
html = time_zone_select("firm", "time_zone", nil, include_blank: true)
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"\"></option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \
+ "<option value=\"\"></option>\n" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
end
@@ -1116,13 +1116,13 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_time_zone_select_with_blank_as_string
@firm = Firm.new("D")
html = time_zone_select("firm", "time_zone", nil, include_blank: "No Zone")
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"\">No Zone</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \
+ "<option value=\"\">No Zone</option>\n" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
end
@@ -1131,12 +1131,12 @@ class FormOptionsHelperTest < ActionView::TestCase
@firm = Firm.new("D")
html = time_zone_select("firm", "time_zone", nil, {},
"style" => "color: red")
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
assert_dom_equal html, time_zone_select("firm", "time_zone", nil, {},
@@ -1147,13 +1147,13 @@ class FormOptionsHelperTest < ActionView::TestCase
@firm = Firm.new("D")
html = time_zone_select("firm", "time_zone", nil,
{ include_blank: true }, "style" => "color: red")
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
- "<option value=\"\"></option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" \
+ "<option value=\"\"></option>\n" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
assert_dom_equal html, time_zone_select("firm", "time_zone", nil,
@@ -1164,13 +1164,13 @@ class FormOptionsHelperTest < ActionView::TestCase
@firm = Firm.new("D")
html = time_zone_select("firm", "time_zone", nil,
{ include_blank: "No Zone" }, "style" => "color: red")
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
- "<option value=\"\">No Zone</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" \
+ "<option value=\"\">No Zone</option>\n" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
assert_dom_equal html, time_zone_select("firm", "time_zone", nil,
@@ -1181,13 +1181,13 @@ class FormOptionsHelperTest < ActionView::TestCase
@firm = Firm.new("D")
zones = [ ActiveSupport::TimeZone.new("A"), ActiveSupport::TimeZone.new("D") ]
html = time_zone_select("firm", "time_zone", zones)
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>" +
- "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>" \
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
end
@@ -1200,13 +1200,13 @@ class FormOptionsHelperTest < ActionView::TestCase
end
html = time_zone_select("firm", "time_zone", /A|D/)
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>" +
- "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>" \
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
end
@@ -1221,13 +1221,13 @@ class FormOptionsHelperTest < ActionView::TestCase
end
html = time_zone_select("firm", "time_zone", /A|D/)
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
end
@@ -1237,12 +1237,12 @@ class FormOptionsHelperTest < ActionView::TestCase
@firm.time_zone = nil
html = time_zone_select("firm", "time_zone", nil, default: "B")
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\" selected=\"selected\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\" selected=\"selected\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
end
@@ -1251,12 +1251,12 @@ class FormOptionsHelperTest < ActionView::TestCase
@firm = Firm.new("D")
html = time_zone_select("firm", "time_zone", nil, default: "B")
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \
+ "<option value=\"A\">A</option>\n" \
+ "<option value=\"B\">B</option>\n" \
+ "<option value=\"C\">C</option>\n" \
+ "<option value=\"D\" selected=\"selected\">D</option>\n" \
+ "<option value=\"E\">E</option>" \
"</select>",
html
end
diff --git a/actionview/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb
index 8691bb11ee..537b4393ee 100644
--- a/actionview/test/template/output_safety_helper_test.rb
+++ b/actionview/test/template/output_safety_helper_test.rb
@@ -33,6 +33,22 @@ class OutputSafetyHelperTest < ActionView::TestCase
assert_equal "&quot;a&quot; &lt;br/&gt; &lt;b&gt; &lt;br/&gt; &lt;c&gt;", joined
end
+ test "safe_join should return the safe string separated by $, when second argument is not passed" do
+ default_delimeter = $,
+
+ begin
+ $, = nil
+ joined = safe_join(["a", "b"])
+ assert_equal "ab", joined
+
+ $, = "|"
+ joined = safe_join(["a", "b"])
+ assert_equal "a|b", joined
+ ensure
+ $, = default_delimeter
+ end
+ end
+
test "to_sentence should escape non-html_safe values" do
actual = to_sentence(%w(< > & ' "))
assert actual.html_safe?
@@ -87,4 +103,15 @@ class OutputSafetyHelperTest < ActionView::TestCase
assert_equal "one, two three", to_sentence(["one", "two", "three"], last_word_connector: " ")
assert_equal "one, two and three", to_sentence(["one", "two", "three"], last_word_connector: " and ")
end
+
+ test "to_sentence is not affected by $," do
+ separator_was = $,
+ $, = "|"
+ begin
+ assert_equal "one and two", to_sentence(["one", "two"])
+ assert_equal "one, two, and three", to_sentence(["one", "two", "three"])
+ ensure
+ $, = separator_was
+ end
+ end
end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index e7e0b147c7..412948719c 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -220,15 +220,15 @@ module RenderTestCases
def test_render_partial_with_invalid_option_as
e = assert_raises(ArgumentError) { @view.render(partial: "test/partial_only", as: "a-in") }
- assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " +
- "make sure it starts with lowercase letter, " +
+ assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " \
+ "make sure it starts with lowercase letter, " \
"and is followed by any combination of letters, numbers and underscores.", e.message
end
def test_render_partial_with_hyphen_and_invalid_option_as
e = assert_raises(ArgumentError) { @view.render(partial: "test/a-in", as: "a-in") }
- assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " +
- "make sure it starts with lowercase letter, " +
+ assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " \
+ "make sure it starts with lowercase letter, " \
"and is followed by any combination of letters, numbers and underscores.", e.message
end
@@ -253,7 +253,7 @@ module RenderTestCases
def test_render_sub_template_with_errors
e = assert_raises(ActionView::Template::Error) { @view.render(template: "test/sub_template_raise") }
assert_match %r!method.*doesnt_exist!, e.message
- assert_equal "Trace of template inclusion: #{File.expand_path("#{FIXTURE_LOAD_PATH}/test/sub_template_raise.html.erb")}", e.sub_template_message
+ assert_match %r{Trace of template inclusion: .*test/sub_template_raise.html.erb}, e.sub_template_message
assert_equal "1", e.line_number
assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name
end
@@ -301,6 +301,15 @@ module RenderTestCases
@view.render(partial: "test/local_inspector", collection: [ Customer.new("mary") ])
end
+ def test_render_partial_collection_with_different_partials_still_provides_partial_iteration
+ a = {}
+ b = {}
+ def a.to_partial_path; "test/partial_iteration_1"; end
+ def b.to_partial_path; "test/partial_iteration_2"; end
+
+ assert_equal "local-variable\nlocal-variable", @controller_view.render([a, b])
+ end
+
def test_render_partial_with_empty_collection_should_return_nil
assert_nil @view.render(partial: "test/customer", collection: [])
end
@@ -393,13 +402,11 @@ module RenderTestCases
assert_equal :partial_name_local_variable, exception.cause.name
end
- # TODO: The reason for this test is unclear, improve documentation
- def test_render_partial_and_fallback_to_layout
+ def test_render_partial_with_no_block_given_to_yield
assert_equal "Before (Josh)\n\nAfter", @view.render(partial: "test/layout_for_partial", locals: { name: "Josh" })
end
- # TODO: The reason for this test is unclear, improve documentation
- def test_render_missing_xml_partial_and_raise_missing_template
+ def test_render_partial_with_non_existent_format_and_raise_missing_template
@view.formats = [:xml]
assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/layout_for_partial") }
ensure
@@ -424,7 +431,7 @@ module RenderTestCases
end
CustomHandler = lambda do |template|
- "@output_buffer = ''\n" +
+ "@output_buffer = ''\n" \
"@output_buffer << 'source: #{template.source.inspect}'\n"
end
@@ -475,11 +482,9 @@ module RenderTestCases
end
def test_render_ignores_templates_with_malformed_template_handlers
- ActiveSupport::Deprecation.silence do
- %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name|
- assert File.exist?(File.expand_path("#{FIXTURE_LOAD_PATH}/test/malformed/#{name}~")), "Malformed file (#{name}~) which should be ignored does not exists"
- assert_raises(ActionView::MissingTemplate) { @view.render(file: "test/malformed/#{name}") }
- end
+ %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name|
+ assert File.exist?(File.expand_path("#{FIXTURE_LOAD_PATH}/test/malformed/#{name}~")), "Malformed file (#{name}~) which should be ignored does not exists"
+ assert_raises(ActionView::MissingTemplate) { @view.render(file: "test/malformed/#{name}") }
end
end
diff --git a/actionview/test/template/text_test.rb b/actionview/test/template/text_test.rb
index ee526dc367..72ffd5f3be 100644
--- a/actionview/test/template/text_test.rb
+++ b/actionview/test/template/text_test.rb
@@ -4,4 +4,20 @@ class TextTest < ActiveSupport::TestCase
test "formats always return :text" do
assert_equal [:text], ActionView::Template::Text.new("").formats
end
+
+ test "identifier should return 'text template'" do
+ assert_equal "text template", ActionView::Template::Text.new("").identifier
+ end
+
+ test "inspect should return 'text template'" do
+ assert_equal "text template", ActionView::Template::Text.new("").inspect
+ end
+
+ test "to_str should return a given string" do
+ assert_equal "a cat", ActionView::Template::Text.new("a cat").to_str
+ end
+
+ test "render should return a given string" do
+ assert_equal "a dog", ActionView::Template::Text.new("a dog").render
+ end
end
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 1e64385b52..09454b32cc 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -235,14 +235,14 @@ class UrlHelperTest < ActiveSupport::TestCase
end
end
- def test_button_to_with_permited_strong_params
+ def test_button_to_with_permitted_strong_params
assert_dom_equal(
%{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="baz" value="quux" /><input type="hidden" name="foo" value="bar" /></form>},
button_to("Hello", "http://www.example.com", params: FakeParams.new)
)
end
- def test_button_to_with_unpermited_strong_params
+ def test_button_to_with_unpermitted_strong_params
assert_raises(ArgumentError) do
button_to("Hello", "http://www.example.com", params: FakeParams.new(false))
end
@@ -496,6 +496,15 @@ class UrlHelperTest < ActiveSupport::TestCase
assert current_page?("http://www.example.com/")
end
+ def test_current_page_considering_params
+ @request = request_for_url("/?order=desc&page=1")
+
+ assert !current_page?(url_hash, check_parameters: true)
+ assert !current_page?(url_hash.merge(check_parameters: true))
+ assert !current_page?(ActionController::Parameters.new(url_hash.merge(check_parameters: true)).permit!)
+ assert !current_page?("http://www.example.com/", check_parameters: true)
+ end
+
def test_current_page_with_params_that_match
@request = request_for_url("/?order=desc&page=1")
@@ -812,7 +821,7 @@ class TasksController < ActionController::Base
private
def render_default
- render inline: "<%= link_to_unless_current('tasks', tasks_path) %>\n" +
+ render inline: "<%= link_to_unless_current('tasks', tasks_path) %>\n" \
"<%= link_to_unless_current('tasks', tasks_url) %>"
end
end
diff --git a/actionview/test/ujs/public/test/call-remote-callbacks.js b/actionview/test/ujs/public/test/call-remote-callbacks.js
index 082d10bfbd..707e21541d 100644
--- a/actionview/test/ujs/public/test/call-remote-callbacks.js
+++ b/actionview/test/ujs/public/test/call-remote-callbacks.js
@@ -108,202 +108,6 @@ asyncTest('stopping the "ajax:beforeSend" event aborts the request', 1, function
})
})
-asyncTest('blank required form input field should abort request and trigger "ajax:aborted:required" event', 5, function() {
- $(document).bind('iframe:loading', function() {
- ok(false, 'form should not get submitted')
- })
-
- var form = $('form[data-remote]')
- .append($('<input type="text" name="user_name" required="required">'))
- .append($('<textarea name="user_bio" required="required"></textarea>'))
- .bindNative('ajax:beforeSend', function() {
- ok(false, 'ajax:beforeSend should not run')
- })
- .bindNative('ajax:aborted:required', function(e, data) {
- data = $(data)
- ok(data.length == 2, 'ajax:aborted:required event is passed all blank required inputs (jQuery objects)')
- ok(data.first().is('input[name="user_name"]'), 'ajax:aborted:required adds blank required input to data')
- ok(data.last().is('textarea[name="user_bio"]'), 'ajax:aborted:required adds blank required textarea to data')
- ok(true, 'ajax:aborted:required should run')
- })
- .triggerNative('submit')
-
- setTimeout(function() {
- form.find('input[required],textarea[required]').val('Tyler')
- form.unbind('ajax:beforeSend')
- submit()
- }, 13)
-})
-
-asyncTest('blank required form input for non-remote form should abort normal submission', 1, function() {
- var form = $('form[data-remote]')
- .append($('<input type="text" name="user_name" required="required">'))
- .removeAttr('data-remote')
- .bindNative('ujs:everythingStopped', function() {
- ok(true, 'ujs:everythingStopped should run')
- })
- .triggerNative('submit')
-
- setTimeout(function() {
- start()
- }, 13)
-})
-
-asyncTest('form should be submitted with blank required fields if handler is bound to "ajax:aborted:required" event that returns false', 1, function() {
- var form = $('form[data-remote]')
- .append($('<input type="text" name="user_name" required="required">'))
- .bindNative('ajax:beforeSend', function() {
- ok(true, 'ajax:beforeSend should run')
- })
- .bindNative('ajax:aborted:required', function() {
- return false
- })
- .triggerNative('submit')
-
- setTimeout(function() {
- start()
- }, 13)
-})
-
-asyncTest('disabled fields should not be included in blank required check', 2, function() {
- var form = $('form[data-remote]')
- .append($('<input type="text" name="user_name" required="required" disabled="disabled">'))
- .append($('<textarea name="user_bio" required="required" disabled="disabled"></textarea>'))
- .bindNative('ajax:beforeSend', function() {
- ok(true, 'ajax:beforeSend should run')
- })
- .bindNative('ajax:aborted:required', function() {
- ok(false, 'ajax:aborted:required should not run')
- })
-
- submit()
-})
-
-asyncTest('form should be submitted with blank required fields if it has the "novalidate" attribute', 2, function() {
- var form = $('form[data-remote]')
- .append($('<input type="text" name="user_name" required="required">'))
- .attr('novalidate', 'novalidate')
- .bindNative('ajax:beforeSend', function() {
- ok(true, 'ajax:beforeSend should run')
- })
- .bindNative('ajax:aborted:required', function() {
- ok(false, 'ajax:aborted:required should not run')
- })
-
- submit()
-})
-
-asyncTest('form should be submitted with blank required fields if the button has the "formnovalidate" attribute', 2, function() {
- var submit_button = $('<input type="submit" formnovalidate>')
- var form = $('form[data-remote]')
- .append($('<input type="text" name="user_name" required="required">'))
- .append(submit_button)
- .bindNative('ajax:beforeSend', function() {
- ok(true, 'ajax:beforeSend should run')
- })
- .bindNative('ajax:aborted:required', function() {
- ok(false, 'ajax:aborted:required should not run')
- })
-
- submit_with_button(submit_button)
-})
-
-asyncTest('blank required form input for non-remote form with "novalidate" attribute should not abort normal submission', 1, function() {
- $(document).bind('iframe:loading', function() {
- ok(true, 'form should get submitted')
- })
-
- var form = $('form[data-remote]')
- .append($('<input type="text" name="user_name" required="required">'))
- .removeAttr('data-remote')
- .attr('novalidate', 'novalidate')
- .triggerNative('submit')
-
- setTimeout(function() {
- start()
- }, 13)
-})
-
-asyncTest('unchecked required checkbox should abort form submission', 1, function() {
- var form = $('form[data-remote]')
- .append($('<input type="checkbox" name="agree" required="required">'))
- .removeAttr('data-remote')
- .bindNative('ujs:everythingStopped', function() {
- ok(true, 'ujs:everythingStopped should run')
- })
- .triggerNative('submit')
-
- setTimeout(function() {
- start()
- }, 13)
-})
-
-asyncTest('unchecked required radio should abort form submission', 3, function() {
- var form = $('form[data-remote]')
- .append($('<input type="radio" name="yes_no_none" required="required" value=1>'))
- .append($('<input type="radio" name="yes_no_none" required="required" value=2>'))
- .removeAttr('data-remote')
- .bindNative('ujs:everythingStopped', function() {
- ok(true, 'ujs:everythingStopped should run')
- })
- .bindNative('ajax:aborted:required', function(e, data) {
- data = $(data)
- equal(data.length, 2, 'blankRequiredInputs should include both radios')
- ok(data.first().is('input[type=radio][value=1]'), 'blankRequiredInputs[0] should be the first radio')
- })
- .triggerNative('submit')
-
- setTimeout(function() {
- start()
- }, 13)
-})
-
-asyncTest('required radio should only require one to be checked', 1, function() {
- $(document).bind('iframe:loading', function() {
- ok(true, 'form should get submitted')
- })
-
- var form = $('form[data-remote]')
- .append($('<input type="radio" name="yes_no" required="required" value=1 id="checkme">'))
- .append($('<input type="radio" name="yes_no" required="required" value=2>'))
- .removeAttr('data-remote')
- .bindNative('ujs:everythingStopped', function() {
- ok(false, 'ujs:everythingStopped should not run')
- })
- .find('#checkme').prop('checked', true)
- .end()
- .triggerNative('submit')
-
- setTimeout(function() {
- start()
- }, 13)
-})
-
-asyncTest('required radio should only require one to be checked if not all radios are required', 1, function() {
- $(document).bind('iframe:loading', function() {
- ok(true, 'form should get submitted')
- })
-
- var form = $('form[data-remote]')
- // Check the radio that is not required
- .append($('<input type="radio" name="yes_no_maybe" value=1 >'))
- // Check the radio that is not required
- .append($('<input type="radio" name="yes_no_maybe" value=2 id="checkme">'))
- // Only one needs to be required
- .append($('<input type="radio" name="yes_no_maybe" required="required" value=3>'))
- .removeAttr('data-remote')
- .bindNative('ujs:everythingStopped', function() {
- ok(false, 'ujs:everythingStopped should not run')
- })
- .find('#checkme').prop('checked', true)
- .end()
- .triggerNative('submit')
-
- setTimeout(function() {
- start()
- }, 13)
-})
-
function skipIt() {
// This test cannot work due to the security feature in browsers which makes the value
// attribute of file input fields readonly, so it cannot be set with default value.
diff --git a/actionview/test/ujs/public/test/data-remote.js b/actionview/test/ujs/public/test/data-remote.js
index a51aa10417..b756add24e 100644
--- a/actionview/test/ujs/public/test/data-remote.js
+++ b/actionview/test/ujs/public/test/data-remote.js
@@ -73,7 +73,6 @@ asyncTest('clicking on a link with data-remote attribute', 5, function() {
.bindNative('ajax:success', function(e, data, status, xhr) {
App.assertCallbackInvoked('ajax:success')
App.assertRequestPath(data, '/echo')
- console.log(data.params)
equal(data.params.data1, 'value1', 'ajax arguments should have key data1 with right value')
equal(data.params.data2, 'value2', 'ajax arguments should have key data2 with right value')
App.assertGetRequest(data)
@@ -398,3 +397,19 @@ asyncTest('form should be serialized correctly', 6, function() {
})
.triggerNative('submit')
})
+
+asyncTest('form buttons should only be serialized when clicked', 4, function() {
+ $('form')
+ .append('<input type="submit" name="submit1" value="submit1" />')
+ .append('<button name="submit2" value="submit2" />')
+ .append('<button name="submit3" value="submit3" />')
+ .bindNative('ajax:success', function(e, data, status, xhr) {
+ equal(data.params.submit1, undefined)
+ equal(data.params.submit2, 'submit2')
+ equal(data.params.submit3, undefined)
+ equal(data['rack.request.form_vars'], 'user_name=john&submit2=submit2')
+
+ start()
+ })
+ .find('[name=submit2]').triggerNative('click')
+})
diff --git a/actionview/test/ujs/public/test/override.js b/actionview/test/ujs/public/test/override.js
index be6ec7749b..299c7018cc 100644
--- a/actionview/test/ujs/public/test/override.js
+++ b/actionview/test/ujs/public/test/override.js
@@ -46,7 +46,7 @@ asyncTest('the event selector strings are overridable', 1, function() {
start()
})
-asyncTest('including jquery-ujs multiple times throws error', 1, function() {
+asyncTest('including rails-ujs multiple times throws error', 1, function() {
throws(function() {
Rails.start()
}, 'appending rails.js again throws error')
diff --git a/actionview/test/ujs/public/test/settings.js b/actionview/test/ujs/public/test/settings.js
index 3375456f11..299c71bb00 100644
--- a/actionview/test/ujs/public/test/settings.js
+++ b/actionview/test/ujs/public/test/settings.js
@@ -63,12 +63,12 @@ $(document).bind('submit', function(e) {
}
})
-var MouseEvent = window.MouseEvent
+var _MouseEvent = window.MouseEvent
try {
- new MouseEvent()
+ new _MouseEvent()
} catch (e) {
- MouseEvent = function(type, options) {
+ _MouseEvent = function(type, options) {
var evt = document.createEvent('MouseEvents')
evt.initMouseEvent(type, options.bubbles, options.cancelable, window, options.detail, 0, 0, 80, 20, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, null)
return evt
@@ -76,12 +76,12 @@ try {
}
$.fn.extend({
- // trigger an native click event
+ // trigger a native click event
triggerNative: function(type, options) {
var el = this[0],
event,
Evt = {
- 'click': MouseEvent,
+ 'click': _MouseEvent,
'change': Event,
'pageshow': PageTransitionEvent,
'submit': Event
diff --git a/actionview/test/ujs/public/vendor/jquery.metadata.js b/actionview/test/ujs/public/vendor/jquery.metadata.js
index ad8bfba404..5b5253cdf7 100644
--- a/actionview/test/ujs/public/vendor/jquery.metadata.js
+++ b/actionview/test/ujs/public/vendor/jquery.metadata.js
@@ -111,7 +111,7 @@ $.extend({
*
* @name metadata
* @descr Returns element's metadata object
- * @param Object opts An object contianing settings to override the defaults
+ * @param Object opts An object containing settings to override the defaults
* @type jQuery
* @cat Plugins/Metadata
*/
diff --git a/actionview/test/ujs/views/layouts/application.html.erb b/actionview/test/ujs/views/layouts/application.html.erb
index 74fa3bd06d..e09b213b72 100644
--- a/actionview/test/ujs/views/layouts/application.html.erb
+++ b/actionview/test/ujs/views/layouts/application.html.erb
@@ -21,7 +21,7 @@
<%= script_tag jquery_src %>
<script>
// This is for test in override.js.
- // Must go after jQuery is loaded, but before jquery-ujs.
+ // Must go before rails-ujs.
$(document).bind('rails:attachBindings', function() {
$.rails.linkClickSelector += ', a[data-custom-remote-link]';
// Hijacks link click before ujs binds any handlers
diff --git a/actionview/test/ujs/views/tests/index.html.erb b/actionview/test/ujs/views/tests/index.html.erb
index 393a5ee235..2ac44eeb81 100644
--- a/actionview/test/ujs/views/tests/index.html.erb
+++ b/actionview/test/ujs/views/tests/index.html.erb
@@ -1,4 +1,4 @@
-<% @title = "jquery-ujs test" %>
+<% @title = "rails-ujs test" %>
<%= test_to 'data-confirm', 'data-remote', 'data-disable', 'data-disable-with', 'call-remote', 'call-remote-callbacks', 'data-method', 'override', 'csrf-refresh', 'csrf-token' %>
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 5e8d8cb5c9..786b508e38 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,13 @@
+* Correctly set test adapter when configure the queue adapter on a per job.
+
+ Fixes #26360.
+
+ *Yuji Yaginuma*
+
+* Push skipped jobs to `enqueued_jobs` when using `perform_enqueued_jobs` with a `only` filter in tests
+
+ *Alexander Pauly*
+
* Removed deprecated support to passing the adapter class to `.queue_adapter`.
*Rafael Mendonça França*
diff --git a/activejob/bin/test b/activejob/bin/test
new file mode 100755
index 0000000000..a7beb14b27
--- /dev/null
+++ b/activejob/bin/test
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+COMPONENT_ROOT = File.expand_path("..", __dir__)
+require File.expand_path("../tools/test", COMPONENT_ROOT)
diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb
index bcc555d33e..9dae80ffc2 100644
--- a/activejob/lib/active_job/queue_adapter.rb
+++ b/activejob/lib/active_job/queue_adapter.rb
@@ -1,4 +1,3 @@
-require "active_job/queue_adapters/inline_adapter"
require "active_support/core_ext/string/inflections"
module ActiveJob
diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb
index da042cfebf..ec825f12cd 100644
--- a/activejob/lib/active_job/queue_adapters/test_adapter.rb
+++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb
@@ -24,15 +24,11 @@ module ActiveJob
end
def enqueue(job) #:nodoc:
- return if filtered?(job)
-
job_data = job_to_hash(job)
enqueue_or_perform(perform_enqueued_jobs, job, job_data)
end
def enqueue_at(job, timestamp) #:nodoc:
- return if filtered?(job)
-
job_data = job_to_hash(job, at: timestamp)
enqueue_or_perform(perform_enqueued_at_jobs, job, job_data)
end
@@ -44,11 +40,11 @@ module ActiveJob
end
def enqueue_or_perform(perform, job, job_data)
- if perform
+ if !perform || filtered?(job)
+ enqueued_jobs << job_data
+ else
performed_jobs << job_data
Base.execute job.serialize
- else
- enqueued_jobs << job_data
end
end
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index d01795f0c5..a61e4f59a5 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -8,16 +8,35 @@ module ActiveJob
:performed_jobs, :performed_jobs=,
to: :queue_adapter
+ module TestQueueAdapter
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :_test_adapter, instance_accessor: false, instance_predicate: false
+ end
+
+ module ClassMethods
+ def queue_adapter
+ self._test_adapter.nil? ? super : self._test_adapter
+ end
+
+ def disable_test_adapter
+ self._test_adapter = nil
+ end
+
+ def enable_test_adapter(test_adapter)
+ self._test_adapter = test_adapter
+ end
+ end
+ end
+
+ ActiveJob::Base.include(TestQueueAdapter)
+
def before_setup # :nodoc:
test_adapter = queue_adapter_for_test
- @old_queue_adapters = (ActiveJob::Base.descendants << ActiveJob::Base).select do |klass|
- # only override explicitly set adapters, a quirk of `class_attribute`
- klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter)
- end.map do |klass|
- [klass, klass.queue_adapter].tap do
- klass.queue_adapter = test_adapter
- end
+ queue_adapter_changed_jobs.each do |klass|
+ klass.enable_test_adapter(test_adapter)
end
clear_enqueued_jobs
@@ -27,9 +46,8 @@ module ActiveJob
def after_teardown # :nodoc:
super
- @old_queue_adapters.each do |(klass, adapter)|
- klass.queue_adapter = adapter
- end
+
+ queue_adapter_changed_jobs.each { |klass| klass.disable_test_adapter }
end
# Specifies the queue adapter to use with all active job test helpers.
@@ -55,7 +73,7 @@ module ActiveJob
# assert_enqueued_jobs 2
# end
#
- # If a block is passed, that block should cause the specified number of
+ # If a block is passed, that block will cause the specified number of
# jobs to be enqueued.
#
# def test_jobs_again
@@ -77,14 +95,23 @@ module ActiveJob
# HelloJob.perform_later('jeremy')
# end
# end
- def assert_enqueued_jobs(number, only: nil)
+ #
+ # The number of times a job is enqueued to a specific queue can also be asserted.
+ #
+ # def test_logging_job
+ # assert_enqueued_jobs 2, queue: 'default' do
+ # LoggingJob.perform_later
+ # HelloJob.perform_later('elfassy')
+ # end
+ # end
+ def assert_enqueued_jobs(number, only: nil, queue: nil)
if block_given?
- original_count = enqueued_jobs_size(only: only)
+ original_count = enqueued_jobs_size(only: only, queue: queue)
yield
- new_count = enqueued_jobs_size(only: only)
+ new_count = enqueued_jobs_size(only: only, queue: queue)
assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
else
- actual_count = enqueued_jobs_size(only: only)
+ actual_count = enqueued_jobs_size(only: only, queue: queue)
assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
end
end
@@ -284,7 +311,7 @@ module ActiveJob
# def test_perform_enqueued_jobs_with_only
# perform_enqueued_jobs(only: MyJob) do
# MyJob.perform_later(1, 2, 3) # will be performed
- # HelloJob.perform_later(1, 2, 3) # will not be perfomed
+ # HelloJob.perform_later(1, 2, 3) # will not be performed
# end
# assert_performed_jobs 1
# end
@@ -323,11 +350,16 @@ module ActiveJob
performed_jobs.clear
end
- def enqueued_jobs_size(only: nil)
- if only
- enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) }
- else
- enqueued_jobs.count
+ def enqueued_jobs_size(only: nil, queue: nil)
+ enqueued_jobs.count do |job|
+ job_class = job.fetch(:job)
+ if only
+ next false unless Array(only).include?(job_class)
+ end
+ if queue
+ next false unless queue.to_s == job.fetch(:queue, job_class.queue_name)
+ end
+ true
end
end
@@ -344,5 +376,12 @@ module ActiveJob
job.queue_name = payload[:queue]
job
end
+
+ def queue_adapter_changed_jobs
+ (ActiveJob::Base.descendants << ActiveJob::Base).select do |klass|
+ # only override explicitly set adapters, a quirk of `class_attribute`
+ klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter)
+ end
+ end
end
end
diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb
index 97c11a9ea6..50476a2e50 100644
--- a/activejob/lib/rails/generators/job/job_generator.rb
+++ b/activejob/lib/rails/generators/job/job_generator.rb
@@ -19,7 +19,7 @@ module Rails # :nodoc:
template "job.rb", File.join("app/jobs", class_path, "#{file_name}_job.rb")
in_root do
- if self.behavior == :invoke && !File.exist?(application_job_file_name)
+ if behavior == :invoke && !File.exist?(application_job_file_name)
template "application_job.rb", application_job_file_name
end
end
diff --git a/activejob/test/cases/queue_adapter_test.rb b/activejob/test/cases/queue_adapter_test.rb
index f1e0cf78ad..9611b0909b 100644
--- a/activejob/test/cases/queue_adapter_test.rb
+++ b/activejob/test/cases/queue_adapter_test.rb
@@ -21,6 +21,7 @@ class QueueAdapterTest < ActiveJob::TestCase
end
test "should allow overriding the queue_adapter at the child class level without affecting the parent or its sibling" do
+ ActiveJob::Base.disable_test_adapter
base_queue_adapter = ActiveJob::Base.queue_adapter
child_job_one = Class.new(ActiveJob::Base)
diff --git a/activejob/test/cases/queue_naming_test.rb b/activejob/test/cases/queue_naming_test.rb
index 0e1e0decd1..0247bc111e 100644
--- a/activejob/test/cases/queue_naming_test.rb
+++ b/activejob/test/cases/queue_naming_test.rb
@@ -56,7 +56,7 @@ class QueueNamingTest < ActiveSupport::TestCase
original_queue_name = HelloJob.queue_name
begin
- HelloJob.queue_as { self.arguments.first == "1" ? :one : :two }
+ HelloJob.queue_as { arguments.first == "1" ? :one : :two }
assert_equal "one", HelloJob.new("1").queue_name
assert_equal "two", HelloJob.new("3").queue_name
ensure
diff --git a/activejob/test/cases/queue_priority_test.rb b/activejob/test/cases/queue_priority_test.rb
index b0d0e903fc..171fb1e593 100644
--- a/activejob/test/cases/queue_priority_test.rb
+++ b/activejob/test/cases/queue_priority_test.rb
@@ -32,7 +32,7 @@ class QueuePriorityTest < ActiveSupport::TestCase
original_priority = HelloJob.priority
begin
- HelloJob.queue_with_priority { self.arguments.first == "1" ? 99 : 11 }
+ HelloJob.queue_with_priority { arguments.first == "1" ? 99 : 11 }
assert_equal 99, HelloJob.new("1").priority
assert_equal 11, HelloJob.new("3").priority
ensure
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 51fc6cc0c4..2e6357f824 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -56,6 +56,17 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_enqueued_jobs_when_performing_with_only_option
+ assert_nothing_raised do
+ assert_enqueued_jobs 1, only: HelloJob do
+ perform_enqueued_jobs only: LoggingJob do
+ HelloJob.perform_later("sean")
+ LoggingJob.perform_later("yves")
+ end
+ end
+ end
+ end
+
def test_assert_no_enqueued_jobs_with_no_block
assert_nothing_raised do
assert_no_enqueued_jobs
@@ -110,6 +121,27 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_enqueued_jobs_with_only_and_queue_option
+ assert_nothing_raised do
+ assert_enqueued_jobs 1, only: HelloJob, queue: :some_queue do
+ HelloJob.set(queue: :some_queue).perform_later
+ HelloJob.set(queue: :other_queue).perform_later
+ LoggingJob.perform_later
+ end
+ end
+ end
+
+ def test_assert_enqueued_jobs_with_queue_option
+ assert_nothing_raised do
+ assert_enqueued_jobs 2, queue: :default do
+ HelloJob.perform_later
+ LoggingJob.perform_later
+ HelloJob.set(queue: :other_queue).perform_later
+ LoggingJob.set(queue: :other_queue).perform_later
+ end
+ end
+ end
+
def test_assert_enqueued_jobs_with_only_option_and_none_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_enqueued_jobs 1, only: HelloJob do
@@ -528,3 +560,20 @@ class InheritedJobTest < ActiveJob::TestCase
assert_instance_of ActiveJob::QueueAdapters::TestAdapter, InheritedJob.queue_adapter
end
end
+
+class QueueAdapterJobTest < ActiveJob::TestCase
+ def before_setup
+ @original_autoload_paths = ActiveSupport::Dependencies.autoload_paths
+ ActiveSupport::Dependencies.autoload_paths = %w(test/jobs)
+ super
+ end
+
+ def after_teardown
+ ActiveSupport::Dependencies.autoload_paths = @original_autoload_paths
+ super
+ end
+
+ def test_queue_adapter_is_test_adapter
+ assert_instance_of ActiveJob::QueueAdapters::TestAdapter, QueueAdapterJob.queue_adapter
+ end
+end
diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb
index 776f7788de..db07ecab7a 100644
--- a/activejob/test/helper.rb
+++ b/activejob/test/helper.rb
@@ -1,7 +1,6 @@
require "active_job"
require "support/job_buffer"
-ActiveSupport.halt_callback_chains_on_return_false = false
GlobalID.app = "aj"
@adapter = ENV["AJ_ADAPTER"] || "inline"
diff --git a/activejob/test/jobs/queue_adapter_job.rb b/activejob/test/jobs/queue_adapter_job.rb
new file mode 100644
index 0000000000..160dfd74ec
--- /dev/null
+++ b/activejob/test/jobs/queue_adapter_job.rb
@@ -0,0 +1,3 @@
+class QueueAdapterJob < ActiveJob::Base
+ self.queue_adapter = :inline
+end
diff --git a/activejob/test/support/integration/adapters/backburner.rb b/activejob/test/support/integration/adapters/backburner.rb
index 263097c792..2e194933a1 100644
--- a/activejob/test/support/integration/adapters/backburner.rb
+++ b/activejob/test/support/integration/adapters/backburner.rb
@@ -23,12 +23,12 @@ module BackburnerJobsManager
end
def tube
- @tube ||= Beaneater::Tube.new(Backburner::Worker.connection, "backburner.worker.queue.integration-tests") # backburner dasherizes the queue name
+ @tube ||= Beaneater::Tube.new(@worker.connection, "backburner.worker.queue.integration-tests") # backburner dasherizes the queue name
end
def can_run?
begin
- Backburner::Worker.connection.send :connect!
+ @worker = Backburner::Worker.new
rescue
return false
end
diff --git a/activejob/test/support/sneakers/inline.rb b/activejob/test/support/sneakers/inline.rb
index 3cdc54e6d5..cf102ae5c2 100644
--- a/activejob/test/support/sneakers/inline.rb
+++ b/activejob/test/support/sneakers/inline.rb
@@ -4,7 +4,7 @@ module Sneakers
module Worker
module ClassMethods
def enqueue(msg)
- worker = self.new(nil, nil, {})
+ worker = new(nil, nil, {})
worker.work(*msg)
end
end
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 853a1e7d9d..edaac8c7cd 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,11 @@
+* Remove deprecated behavior that halts callbacks when the return is false.
+
+ *Rafael Mendonça França*
+
+* Remove unused `ActiveModel::TestCase` class.
+
+ *Yuji Yaginuma*
+
* Moved DecimalWithoutScale, Text, and UnsignedInteger from Active Model to Active Record
*Iain Beeston*
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 8163856a46..2389c858d5 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -42,7 +42,6 @@ module ActiveModel
autoload :Naming
autoload :SecurePassword
autoload :Serialization
- autoload :TestCase
autoload :Translation
autoload :Validations
autoload :Validator
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 09825cf861..166c6ac21f 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -289,7 +289,7 @@ module ActiveModel
generate_method = "define_method_#{matcher.method_missing_target}"
if respond_to?(generate_method, true)
- send(generate_method, attr_name)
+ send(generate_method, attr_name.to_s)
else
define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
end
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index e99bfab6fd..eac2761433 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -103,7 +103,6 @@ module ActiveModel
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
- terminator: deprecated_false_terminator,
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name],
only: [:before, :around, :after]
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 9532c63b65..9853cf38fe 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -234,7 +234,7 @@ module ActiveModel
# Person.model_name.plural # => "people"
def model_name
@_model_name ||= begin
- namespace = self.parents.detect do |n|
+ namespace = parents.detect do |n|
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
end
ActiveModel::Name.new(self, namespace)
diff --git a/activemodel/lib/active_model/test_case.rb b/activemodel/lib/active_model/test_case.rb
deleted file mode 100644
index 5004855d56..0000000000
--- a/activemodel/lib/active_model/test_case.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-module ActiveModel #:nodoc:
- class TestCase < ActiveSupport::TestCase #:nodoc:
- end
-end
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index b8cf43cc10..35fc7cf743 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -44,7 +44,7 @@ module ActiveModel
parts = attribute.to_s.split(".")
attribute = parts.pop
namespace = parts.join("/") unless parts.empty?
- attributes_scope = "#{self.i18n_scope}.attributes"
+ attributes_scope = "#{i18n_scope}.attributes"
if namespace
defaults = lookup_ancestors.map do |klass|
diff --git a/activemodel/lib/active_model/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb
index 6c5c0451c6..541a12c8a1 100644
--- a/activemodel/lib/active_model/type/decimal.rb
+++ b/activemodel/lib/active_model/type/decimal.rb
@@ -4,6 +4,7 @@ module ActiveModel
module Type
class Decimal < Value # :nodoc:
include Helpers::Numeric
+ BIGDECIMAL_PRECISION = 18
def type
:decimal
@@ -21,7 +22,7 @@ module ActiveModel
when ::Float
convert_float_to_big_decimal(value)
when ::Numeric, ::String
- BigDecimal(value, precision.to_i)
+ BigDecimal(value, precision || BIGDECIMAL_PRECISION)
else
if value.respond_to?(:to_d)
value.to_d
diff --git a/activemodel/lib/active_model/type/integer.rb b/activemodel/lib/active_model/type/integer.rb
index 230e45ba60..106b5d966c 100644
--- a/activemodel/lib/active_model/type/integer.rb
+++ b/activemodel/lib/active_model/type/integer.rb
@@ -48,7 +48,7 @@ module ActiveModel
def ensure_in_range(value)
unless range.cover?(value)
- raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit}"
+ raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
end
end
diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb
index 75bf655578..4618f46e30 100644
--- a/activemodel/lib/active_model/validations/absence.rb
+++ b/activemodel/lib/active_model/validations/absence.rb
@@ -22,7 +22,7 @@ module ActiveModel
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
- # See <tt>ActiveModel::Validation#validates</tt> for more information
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
def validates_absence_of(*attr_names)
validates_with AbsenceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index e11005b9ba..a26c37daa5 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -95,7 +95,7 @@ module ActiveModel
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
- # See <tt>ActiveModel::Validation#validates</tt> for more information.
+ # See <tt>ActiveModel::Validations#validates</tt> for more information.
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index 70bc1a0624..4e94422cf1 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -23,7 +23,6 @@ module ActiveModel
included do
include ActiveSupport::Callbacks
define_callbacks :validation,
- terminator: deprecated_false_terminator,
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name]
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index 33ca6f6946..03585bf5e1 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -69,7 +69,7 @@ module ActiveModel
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
- # See <tt>ActiveModel::Validation#validates</tt> for more information
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
def validates_confirmation_of(*attr_names)
validates_with ConfirmationValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 82a1893823..b7156ba802 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -38,7 +38,7 @@ module ActiveModel
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
- # See <tt>ActiveModel::Validation#validates</tt> for more information
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
def validates_exclusion_of(*attr_names)
validates_with ExclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index fa183885ab..b4b8d9f33c 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -104,7 +104,7 @@ module ActiveModel
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
- # See <tt>ActiveModel::Validation#validates</tt> for more information
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
def validates_format_of(*attr_names)
validates_with FormatValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 0ea6012a5d..c6c5bae649 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -36,7 +36,7 @@ module ActiveModel
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
- # See <tt>ActiveModel::Validation#validates</tt> for more information
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
def validates_inclusion_of(*attr_names)
validates_with InclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index de1524829e..940c58f3a7 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/string/strip"
-
module ActiveModel
module Validations
class LengthValidator < EachValidator # :nodoc:
@@ -112,7 +110,7 @@ module ActiveModel
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+ and +:strict+.
- # See <tt>ActiveModel::Validation#validates</tt> for more information
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
def validates_length_of(*attr_names)
validates_with LengthValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 2297faaee5..30a9ef472d 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -63,27 +63,27 @@ module ActiveModel
private
- def is_number?(raw_value) # :doc:
+ def is_number?(raw_value)
!parse_raw_value_as_a_number(raw_value).nil?
rescue ArgumentError, TypeError
false
end
- def parse_raw_value_as_a_number(raw_value) # :doc:
+ def parse_raw_value_as_a_number(raw_value)
Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/
end
- def is_integer?(raw_value) # :doc:
+ def is_integer?(raw_value)
/\A[+-]?\d+\z/ === raw_value.to_s
end
- def filtered_options(value) # :doc:
+ def filtered_options(value)
filtered = options.except(*RESERVED_OPTIONS)
filtered[:value] = value
filtered
end
- def allow_only_integer?(record) # :doc:
+ def allow_only_integer?(record)
case options[:only_integer]
when Symbol
record.send(options[:only_integer])
@@ -134,7 +134,7 @@ module ActiveModel
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
- # See <tt>ActiveModel::Validation#validates</tt> for more information
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
#
# The following checks can also be supplied with a proc or a symbol which
# corresponds to a method:
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 0c11cf4265..6e8a434bbe 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -29,7 +29,7 @@ module ActiveModel
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
- # See <tt>ActiveModel::Validation#validates</tt> for more information
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index e3f7a9bcb2..9227dd06ff 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -61,7 +61,7 @@ module ActiveModel
# The method, proc or string should return or evaluate to a +true+ or
# +false+ value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ # See <tt>ActiveModel::Validations#validates!</tt> for more information.
#
# If you pass any additional configuration options, they will be passed
# to the class and available as +options+:
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 8212744170..98234e9b6b 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -141,8 +141,8 @@ module ActiveModel
end
# Performs validation on the supplied record. By default this will call
- # +validates_each+ to determine validity therefore subclasses should
- # override +validates_each+ with validation logic.
+ # +validate_each+ to determine validity therefore subclasses should
+ # override +validate_each+ with validation logic.
def validate(record)
attributes.each do |attribute|
value = record.read_attribute_for_validation(attribute)
diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb
index 63b6c56f8c..f85cd7dec4 100644
--- a/activemodel/test/cases/callbacks_test.rb
+++ b/activemodel/test/cases/callbacks_test.rb
@@ -27,7 +27,7 @@ class CallbacksTest < ActiveModel::TestCase
false
end
- ActiveSupport::Deprecation.silence { after_create "@callbacks << :final_callback" }
+ after_create { |model| model.callbacks << :final_callback }
def initialize(options = {})
@callbacks = []
@@ -63,12 +63,10 @@ class CallbacksTest < ActiveModel::TestCase
assert_equal model.callbacks.last, :final_callback
end
- test "the callback chain is halted when a before callback returns false (deprecated)" do
+ test "the callback chain is not halted when a before callback returns false)" do
model = ModelCallbacks.new(before_create_returns: false)
- assert_deprecated do
- model.create
- assert_equal model.callbacks.last, :before_create
- end
+ model.create
+ assert_equal model.callbacks.last, :final_callback
end
test "the callback chain is halted when a callback throws :abort" do
@@ -127,6 +125,7 @@ class CallbacksTest < ActiveModel::TestCase
test "after_create callbacks with both callbacks declared in one line" do
assert_equal ["callback1", "callback2"], Violin1.new.create.history
end
+
test "after_create callbacks with both callbacks declared in different lines" do
assert_equal ["callback1", "callback2"], Violin2.new.create.history
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index e6ba06301d..0872084cf5 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "active_support/core_ext/string/strip"
require "yaml"
class ErrorsTest < ActiveModel::TestCase
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 4e9f43ad86..eeb5c85a48 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -9,15 +9,15 @@ I18n.enforce_available_locales = false
require "active_support/testing/autorun"
require "active_support/testing/method_call_assertions"
-# Skips the current run on Rubinius using Minitest::Assertions#skip
-def rubinius_skip(message = "")
- skip message if RUBY_ENGINE == "rbx"
-end
-# Skips the current run on JRuby using Minitest::Assertions#skip
-def jruby_skip(message = "")
- skip message if defined?(JRUBY_VERSION)
-end
-
-class ActiveModel::TestCase
+class ActiveModel::TestCase < ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
+
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
+ private def rubinius_skip(message = "")
+ skip message if RUBY_ENGINE == "rbx"
+ end
+ # Skips the current run on JRuby using Minitest::Assertions#skip
+ private def jruby_skip(message = "")
+ skip message if defined?(JRUBY_VERSION)
+ end
end
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
index 83e8ac9522..f2e4a5946d 100644
--- a/activemodel/test/cases/validations/callbacks_test.rb
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -29,7 +29,7 @@ class DogWithTwoValidators < Dog
before_validation { history << "before_validation_marker2" }
end
-class DogDeprecatedBeforeValidatorReturningFalse < Dog
+class DogBeforeValidatorReturningFalse < Dog
before_validation { false }
before_validation { history << "before_validation_marker2" }
end
@@ -121,13 +121,11 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase
assert_equal false, output
end
- def test_deprecated_further_callbacks_should_not_be_called_if_before_validation_returns_false
- d = DogDeprecatedBeforeValidatorReturningFalse.new
- assert_deprecated do
- output = d.valid?
- assert_equal [], d.history
- assert_equal false, output
- end
+ def test_further_callbacks_should_be_called_if_before_validation_returns_false
+ d = DogBeforeValidatorReturningFalse.new
+ output = d.valid?
+ assert_equal ["before_validation_marker2"], d.history
+ assert_equal true, output
end
def test_further_callbacks_should_be_called_if_after_validation_returns_false
diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb
index 4881008017..048d27446e 100644
--- a/activemodel/test/cases/validations/conditional_validation_test.rb
+++ b/activemodel/test/cases/validations/conditional_validation_test.rb
@@ -43,7 +43,9 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_string_true
# When the evaluated string returns true
- Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "a = 1; a == 1")
+ ActiveSupport::Deprecation.silence do
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "a = 1; a == 1")
+ end
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -52,7 +54,9 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_string_true
# When the evaluated string returns true
- Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "a = 1; a == 1")
+ ActiveSupport::Deprecation.silence do
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "a = 1; a == 1")
+ end
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert_empty t.errors[:title]
@@ -60,7 +64,9 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_if_validation_using_string_false
# When the evaluated string returns false
- Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "false")
+ ActiveSupport::Deprecation.silence do
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "false")
+ end
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.valid?
assert_empty t.errors[:title]
@@ -68,7 +74,9 @@ class ConditionalValidationTest < ActiveModel::TestCase
def test_unless_validation_using_string_false
# When the evaluated string returns false
- Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "false")
+ ActiveSupport::Deprecation.silence do
+ Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "false")
+ end
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
assert t.invalid?
assert t.errors[:title].any?
@@ -118,7 +126,9 @@ class ConditionalValidationTest < ActiveModel::TestCase
# ensure that it works correctly
def test_validation_with_if_as_string
Topic.validates_presence_of(:title)
- Topic.validates_presence_of(:author_name, if: "title.to_s.match('important')")
+ ActiveSupport::Deprecation.silence do
+ Topic.validates_presence_of(:author_name, if: "title.to_s.match('important')")
+ end
t = Topic.new
assert t.invalid?, "A topic without a title should not be valid"
diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb
index 20c11dd852..5ce86738cd 100644
--- a/activemodel/test/cases/validations/with_validation_test.rb
+++ b/activemodel/test/cases/validations/with_validation_test.rb
@@ -69,26 +69,34 @@ class ValidatesWithTest < ActiveModel::TestCase
end
test "with if statements that return false" do
- Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 2")
+ ActiveSupport::Deprecation.silence do
+ Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 2")
+ end
topic = Topic.new
assert topic.valid?
end
test "with if statements that return true" do
- Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1")
+ ActiveSupport::Deprecation.silence do
+ Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1")
+ end
topic = Topic.new
assert topic.invalid?
assert_includes topic.errors[:base], ERROR_MESSAGE
end
test "with unless statements that return true" do
- Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 1")
+ ActiveSupport::Deprecation.silence do
+ Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 1")
+ end
topic = Topic.new
assert topic.valid?
end
test "with unless statements that returns false" do
- Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2")
+ ActiveSupport::Deprecation.silence do
+ Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2")
+ end
topic = Topic.new
assert topic.invalid?
assert_includes topic.errors[:base], ERROR_MESSAGE
@@ -102,7 +110,9 @@ class ValidatesWithTest < ActiveModel::TestCase
validator.expect(:is_a?, false, [Symbol])
validator.expect(:is_a?, false, [String])
- Topic.validates_with(validator, if: "1 == 1", foo: :bar)
+ ActiveSupport::Deprecation.silence do
+ Topic.validates_with(validator, if: "1 == 1", foo: :bar)
+ end
assert topic.valid?
validator.verify
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 743a38b087..b27c03d935 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,12 +1,122 @@
+* All integer-like PKs are autoincrement unless they have an explicit default.
+
+ *Matthew Draper*
+
+* Omit redundant `using: :btree` for schema dumping.
+
+ *Ryuta Kamizono*
+
+* Deprecate passing `default` to `index_name_exists?`.
+
+ *Ryuta Kamizono*
+
+* PostgreSQL: schema dumping support for interval and OID columns.
+
+ *Ryuta Kamizono*
+
+* Deprecate `supports_primary_key?` on connection adapters since it's
+ been long unused and unsupported.
+
+ *Ryuta Kamizono*
+
+* Make `table_name=` reset current statement cache,
+ so queries are not run against the previous table name.
+
+ *namusyaka*
+
+* Allow `ActiveRecord::Base#as_json` to be passed a frozen Hash.
+
+ *Isaac Betesh*
+
+* Fix inspection behavior when the :id column is not primary key.
+
+ *namusyaka*
+
+* Deprecate locking records with unpersisted changes.
+
+ *Marc Schütz*
+
+* Remove deprecated behavior that halts callbacks when the return is false.
+
+ *Rafael Mendonça França*
+
+* Deprecate `ColumnDumper#migration_keys`.
+
+ *Ryuta Kamizono*
+
+* Fix `association_primary_key_type` for reflections with symbol primary key.
+
+ Fixes #27864.
+
+ *Daniel Colson*
+
+* Virtual/generated column support for MySQL 5.7.5+ and MariaDB 5.2.0+.
+
+ MySQL generated columns: https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html
+ MariaDB virtual columns: https://mariadb.com/kb/en/mariadb/virtual-computed-columns/
+
+ Declare virtual columns with `t.virtual name, type: …, as: "expression"`.
+ Pass `stored: true` to persist the generated value (false by default).
+
+ Example:
+
+ create_table :generated_columns do |t|
+ t.string :name
+ t.virtual :upper_name, type: :string, as: "UPPER(name)"
+ t.virtual :name_length, type: :integer, as: "LENGTH(name)", stored: true
+ t.index :name_length # May be indexed, too!
+ end
+
+ *Ryuta Kamizono*
+
+* Deprecate `initialize_schema_migrations_table` and `initialize_internal_metadata_table`.
+
+ *Ryuta Kamizono*
+
+* Support foreign key creation for SQLite3.
+
+ *Ryuta Kamizono*
+
+* Place generated migrations into the path set by `config.paths["db/migrate"]`.
+
+ *Kevin Glowacz*
+
+* Raise `ActiveRecord::InvalidForeignKey` when a foreign key constraint fails on SQLite3.
+
+ *Ryuta Kamizono*
+
+* Add the touch option to `#increment!` and `#decrement!`.
+
+ *Hiroaki Izu*
+
+* Deprecate passing a class to the `class_name` because it eagerloads more classes than
+ necessary and potentially creates circular dependencies.
+
+ *Kir Shatrov*
+
+* Raise error when has_many through is defined before through association.
+
+ Fixes #26834.
+
+ *Chris Holmes*
+
+* Deprecate passing `name` to `indexes`.
+
+ *Ryuta Kamizono*
+
+* Remove deprecated tasks: `db:test:clone`, `db:test:clone_schema`, `db:test:clone_structure`.
+
+ *Rafel Mendonça França*
+
* Compare deserialized values for `PostgreSQL::OID::Hstore` types when
- calling `ActiveRecord::Dirty#changed_in_place?`
+ calling `ActiveRecord::Dirty#changed_in_place?`.
Fixes #27502.
*Jon Moss*
-* Raise `ArgumentError` when passing an `ActiveRecord::Base` instance to `.find`
- and `.exists?`.
+* Raise `ArgumentError` when passing an `ActiveRecord::Base` instance to `.find`,
+ `.exists?` and `.update`.
*Rafael Mendonça França*
@@ -107,7 +217,7 @@
*Rafael Mendonça França*
* Set `:time` as a timezone aware type and remove deprecation when
- `config.active_record.time_zone_aware_types` is not explictly set.
+ `config.active_record.time_zone_aware_types` is not explicitly set.
*Rafael Mendonça França*
@@ -191,7 +301,7 @@
*Sean Griffin*
-* Fix that unsigned with zerofill is treated as signed.
+* Don't treat unsigned integers with zerofill as signed.
Fixes #27125.
@@ -397,7 +507,7 @@
*Ryuta Kamizono*
-* Sqlite3 migrations to add a column to an existing table can now be
+* SQLite3 migrations to add a column to an existing table can now be
successfully rolled back when the column was given and invalid column
type.
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 250f48fad9..96b8545dfc 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -44,7 +44,6 @@ module ActiveRecord
autoload :Explain
autoload :Inheritance
autoload :Integration
- autoload :LegacyYamlAdapter
autoload :Migration
autoload :Migrator, "active_record/migration"
autoload :ModelSchema
@@ -85,6 +84,8 @@ module ActiveRecord
autoload :AttributeMethods
autoload :AutosaveAssociation
+ autoload :LegacyYamlAdapter
+
autoload :Relation
autoload :AssociationRelation
autoload :NullRelation
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 1db5fc0dd1..4606c91ffd 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -97,6 +97,16 @@ module ActiveRecord
end
end
+ class HasManyThroughOrderError < ActiveRecordError #:nodoc:
+ def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil)
+ if owner_class_name && reflection && through_reflection
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through '#{owner_class_name}##{through_reflection.name}' before the through association is defined.")
+ else
+ super("Cannot have a has_many :through association before the through association is defined.")
+ end
+ end
+ end
+
class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:
def initialize(owner = nil, reflection = nil)
if owner && reflection
@@ -1817,7 +1827,7 @@ module ActiveRecord
builder = Builder::HasAndBelongsToMany.new name, self, options
- join_model = builder.through_model
+ join_model = ActiveSupport::Deprecation.silence { builder.through_model }
const_set join_model.name, join_model
private_constant join_model.name
@@ -1846,8 +1856,8 @@ module ActiveRecord
hm_options[k] = options[k] if options.key? k
end
- has_many name, scope, hm_options, &extension
- self._reflections[name.to_s].parent_reflection = habtm_reflection
+ ActiveSupport::Deprecation.silence { has_many name, scope, hm_options, &extension }
+ _reflections[name.to_s].parent_reflection = habtm_reflection
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index 84d0493a60..1cb2b2d7c6 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -83,7 +83,7 @@ module ActiveRecord
end
def scope
- target_scope.merge(association_scope)
+ target_scope.merge!(association_scope)
end
# The scope for this association.
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index c6d204d3c2..badde9973f 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -128,9 +128,9 @@ module ActiveRecord
reflection = chain_head
while reflection
table = reflection.alias_name
+ next_reflection = reflection.next
unless reflection == chain_tail
- next_reflection = reflection.next
foreign_table = next_reflection.alias_name
scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)
end
@@ -138,7 +138,7 @@ module ActiveRecord
# Exclude the scope of the association itself, because that
# was already merged in the #scope method.
reflection.constraints.each do |scope_chain_item|
- item = eval_scope(reflection.klass, scope_chain_item, owner)
+ item = eval_scope(reflection.klass, table, scope_chain_item, owner)
if scope_chain_item == refl.scope
scope.merge! item.except(:where, :includes)
@@ -153,14 +153,15 @@ module ActiveRecord
scope.order_values |= item.order_values
end
- reflection = reflection.next
+ reflection = next_reflection
end
scope
end
- def eval_scope(klass, scope, owner)
- klass.unscoped.instance_exec(owner, &scope)
+ def eval_scope(klass, table, scope, owner)
+ predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
+ ActiveRecord::Relation.create(klass, table, predicate_builder).instance_exec(owner, &scope)
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 0437a79b84..77282e6463 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -30,13 +30,7 @@ module ActiveRecord
reload
end
- if null_scope?
- # Cache the proxy separately before the owner has an id
- # or else a post-save proxy will still lack the id
- @null_proxy ||= CollectionProxy.create(klass, self)
- else
- @proxy ||= CollectionProxy.create(klass, self)
- end
+ CollectionProxy.create(klass, self)
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -315,9 +309,9 @@ module ActiveRecord
record
end
- def scope(opts = {})
- scope = super()
- scope.none! if opts.fetch(:nullify, true) && null_scope?
+ def scope
+ scope = super
+ scope.none! if null_scope?
scope
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 0d84805b4d..55bf2e0ff0 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -28,12 +28,9 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class CollectionProxy < Relation
- delegate :exists?, :update_all, :arel, to: :scope
-
def initialize(klass, association) #:nodoc:
@association = association
super klass, klass.arel_table, klass.predicate_builder
- merge! association.scope(nullify: false)
end
def target
@@ -956,19 +953,10 @@ module ActiveRecord
@association
end
- # We don't want this object to be put on the scoping stack, because
- # that could create an infinite loop where we call an @association
- # method, which gets the current scope, which is this object, which
- # delegates to @association, and so on.
- def scoping
- @association.scope.scoping { yield }
- end
-
# Returns a <tt>Relation</tt> object for the records in this association
def scope
- @association.scope
+ @scope ||= @association.scope
end
- alias spawn scope
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
# contain the same number of elements and if each element is equal
@@ -1100,6 +1088,7 @@ module ActiveRecord
# person.pets(true) # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reload
+ @scope = nil
proxy_association.reload
self
end
@@ -1121,11 +1110,21 @@ module ActiveRecord
# person.pets # fetches pets from the database
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
def reset
+ @scope = nil
proxy_association.reset
proxy_association.reset_scope
self
end
+ delegate_methods = [
+ QueryMethods,
+ SpawnMethods,
+ ].flat_map { |klass|
+ klass.public_instance_methods(false)
+ } - self.public_instance_methods(false) + [:scoping]
+
+ delegate(*delegate_methods, to: :scope)
+
private
def find_nth_with_limit(index, limit)
@@ -1149,6 +1148,18 @@ module ActiveRecord
def exec_queries
load_target
end
+
+ def respond_to_missing?(method, _)
+ scope.respond_to?(method) || super
+ end
+
+ def method_missing(method, *args, &block)
+ if scope.respond_to?(method)
+ scope.public_send(method, *args, &block)
+ else
+ super
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 25613d2fa7..b413eb3f9c 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -100,7 +100,7 @@ module ActiveRecord
end
def delete_or_nullify_all_records(method)
- count = delete_count(method, self.scope)
+ count = delete_count(method, scope)
update_counter(-count)
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index b624154def..8458253ff8 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -85,7 +85,7 @@ module ActiveRecord
if target.persisted? && owner.persisted? && !target.save
set_owner_attributes(target)
- raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " +
+ raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \
"The record failed to save after its foreign key was set to nil."
end
end
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 604904abcc..1183bdf6f4 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -22,6 +22,10 @@ module ActiveRecord
elsif record
attributes = construct_join_attributes(record)
+ if through_record && through_record.destroyed?
+ through_record = through_proxy.tap(&:reload).target
+ end
+
if through_record
through_record.update(attributes)
elsif owner.new_record?
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 4cd1e64c3d..87e0847ec1 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -32,7 +32,7 @@ module ActiveRecord
@alias_cache[node][column]
end
- class Table < Struct.new(:node, :columns) # :nodoc:
+ Table = Struct.new(:node, :columns) do # :nodoc:
def table
Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
end
@@ -171,7 +171,7 @@ module ActiveRecord
chain = child.reflection.chain
foreign_table = parent.table
foreign_klass = parent.base_klass
- child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
+ child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, chain)
end
def make_outer_joins(parent, child)
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index a5705951f3..f5fcba1236 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -23,14 +23,11 @@ module ActiveRecord
JoinInformation = Struct.new :joins, :binds
- def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain)
+ def join_constraints(foreign_table, foreign_klass, node, join_type, tables, chain)
joins = []
binds = []
tables = tables.reverse
- scope_chain_index = 0
- scope_chain = scope_chain.reverse
-
# The chain starts with the target table, but we want to end with it here (makes
# more sense in this context), so we reverse
chain.reverse_each do |reflection|
@@ -44,7 +41,7 @@ module ActiveRecord
constraint = build_constraint(klass, table, key, foreign_table, foreign_key)
predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table))
- scope_chain_items = scope_chain[scope_chain_index].map do |item|
+ scope_chain_items = reflection.scopes.map do |item|
if item.is_a?(Relation)
item
else
@@ -52,7 +49,6 @@ module ActiveRecord
.instance_exec(node, &item)
end
end
- scope_chain_index += 1
klass_scope =
if klass.current_scope
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 1ed1deec55..ebe06566cc 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -394,28 +394,21 @@ module ActiveRecord
protected
- def clone_attribute_value(reader_method, attribute_name) # :nodoc:
- value = send(reader_method, attribute_name)
- value.duplicable? ? value.clone : value
- rescue TypeError, NoMethodError
- value
+ def attribute_method?(attr_name) # :nodoc:
+ # We check defined? because Syck calls respond_to? before actually calling initialize.
+ defined?(@attributes) && @attributes.key?(attr_name)
end
- def arel_attributes_with_values_for_create(attribute_names) # :nodoc:
+ private
+
+ def arel_attributes_with_values_for_create(attribute_names)
arel_attributes_with_values(attributes_for_create(attribute_names))
end
- def arel_attributes_with_values_for_update(attribute_names) # :nodoc:
+ def arel_attributes_with_values_for_update(attribute_names)
arel_attributes_with_values(attributes_for_update(attribute_names))
end
- def attribute_method?(attr_name) # :nodoc:
- # We check defined? because Syck calls respond_to? before actually calling initialize.
- defined?(@attributes) && @attributes.key?(attr_name)
- end
-
- private
-
# Returns a Hash of the Arel::Attributes and attribute values that have been
# typecasted for use in an Arel insert/update method.
def arel_attributes_with_values(attribute_names)
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index b0e1391cb9..31c1e687dc 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -275,7 +275,17 @@ module ActiveRecord
def attribute_will_change!(attr_name)
super
- mutations_from_database.force_change(attr_name)
+ if self.class.has_attribute?(attr_name)
+ mutations_from_database.force_change(attr_name)
+ else
+ ActiveSupport::Deprecation.warn(<<-EOW.squish)
+ #{attr_name} is not an attribute known to Active Record.
+ This behavior is deprecated and will be removed in the next
+ version of Rails. If you'd like #{attr_name} to be managed
+ by Active Record, add `attribute :#{attr_name} to your class.
+ EOW
+ mutations_from_database.deprecated_force_change(attr_name)
+ end
end
def _update_record(*)
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index 8fcac82a0d..2f32caa257 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -9,7 +9,7 @@ module ActiveRecord
# available.
def to_key
sync_with_transaction_state
- key = self.id
+ key = id
[key] if key
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 369a6e35aa..fdc4bf6621 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -54,7 +54,7 @@ module ActiveRecord
attr_name.to_s
end
- name = self.class.primary_key if name == "id".freeze
+ name = self.class.primary_key if name == "id".freeze && self.class.primary_key
_read_attribute(name, &block)
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 945192fe04..4d9aff76cc 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -54,7 +54,7 @@ module ActiveRecord
elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }
class_name_or_coder
else
- Coders::YAMLColumn.new(class_name_or_coder)
+ Coders::YAMLColumn.new(attr_name, class_name_or_coder)
end
decorate_attribute_type(attr_name, :serialize) do |type|
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index df1231ad47..321d039ed4 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/string/strip"
-
module ActiveRecord
module AttributeMethods
module TimeZoneConversion
diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb
index 3417090830..4de993e169 100644
--- a/activerecord/lib/active_record/attribute_mutation_tracker.rb
+++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb
@@ -5,6 +5,7 @@ module ActiveRecord
def initialize(attributes)
@attributes = attributes
@forced_changes = Set.new
+ @deprecated_forced_changes = Set.new
end
def changed_values
@@ -31,7 +32,7 @@ module ActiveRecord
end
def any_changes?
- attr_names.any? { |attr| changed?(attr) }
+ attr_names.any? { |attr| changed?(attr) } || deprecated_forced_changes.any?
end
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
@@ -60,11 +61,15 @@ module ActiveRecord
forced_changes << attr_name.to_s
end
+ def deprecated_force_change(attr_name)
+ deprecated_forced_changes << attr_name.to_s
+ end
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
# Workaround for Ruby 2.2 "private attribute?" warning.
protected
- attr_reader :attributes, :forced_changes
+ attr_reader :attributes, :forced_changes, :deprecated_forced_changes
private
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 9d0b501862..6bccbc06cd 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -328,9 +328,9 @@ module ActiveRecord
def association_valid?(reflection, record, index = nil)
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
- validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
+ context = validation_context unless [:create, :update].include?(validation_context)
- unless valid = record.valid?(validation_context)
+ unless valid = record.valid?(context)
if reflection.options[:autosave]
indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors)
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index be6720ddf3..eb44887e18 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -225,6 +225,55 @@ module ActiveRecord
#
# This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
#
+ # Also, there are cases when you want several callbacks of the same type to
+ # be executed in order.
+ #
+ # For example:
+ #
+ # class Topic
+ # has_many :children
+ #
+ # after_save :log_children
+ # after_save :do_something_else
+ #
+ # private
+ #
+ # def log_chidren
+ # # Child processing
+ # end
+ #
+ # def do_something_else
+ # # Something else
+ # end
+ # end
+ #
+ # In this case the +log_children+ gets executed before +do_something_else+.
+ # The same applies to all non-transactional callbacks.
+ #
+ # In case there are multiple transactional callbacks as seen below, the order
+ # is reversed.
+ #
+ # For example:
+ #
+ # class Topic
+ # has_many :children
+ #
+ # after_commit :log_children
+ # after_commit :do_something_else
+ #
+ # private
+ #
+ # def log_chidren
+ # # Child processing
+ # end
+ #
+ # def do_something_else
+ # # Something else
+ # end
+ # end
+ #
+ # In this case the +do_something_else+ gets executed before +log_children+.
+ #
# == \Transactions
#
# The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index 3a04a10fc9..9c52a31b95 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -5,7 +5,8 @@ module ActiveRecord
class YAMLColumn # :nodoc:
attr_accessor :object_class
- def initialize(object_class = Object)
+ def initialize(attr_name, object_class = Object)
+ @attr_name = attr_name
@object_class = object_class
check_arity_of_constructor
end
@@ -13,7 +14,7 @@ module ActiveRecord
def dump(obj)
return if obj.nil?
- assert_valid_value(obj)
+ assert_valid_value(obj, action: "dump")
YAML.dump obj
end
@@ -22,27 +23,25 @@ module ActiveRecord
return yaml unless yaml.is_a?(String) && /^---/.match?(yaml)
obj = YAML.load(yaml)
- assert_valid_value(obj)
+ assert_valid_value(obj, action: "load")
obj ||= object_class.new if object_class != Object
obj
end
- def assert_valid_value(obj)
+ def assert_valid_value(obj, action:)
unless obj.nil? || obj.is_a?(object_class)
raise SerializationTypeMismatch,
- "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
+ "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
end
end
private
def check_arity_of_constructor
- begin
- load(nil)
- rescue ArgumentError
- raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
- end
+ load(nil)
+ rescue ArgumentError
+ raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
end
end
end
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 5ec2fc073e..3f2e86a98d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -353,6 +353,16 @@ module ActiveRecord
@threads_blocking_new_connections = 0
@available = ConnectionLeasingQueue.new self
+
+ @lock_thread = false
+ end
+
+ def lock_thread=(lock_thread)
+ if lock_thread
+ @lock_thread = Thread.current
+ else
+ @lock_thread = nil
+ end
end
# Retrieve the connection associated with the current thread, or call
@@ -361,7 +371,7 @@ module ActiveRecord
# #connection can be called any number of times; the connection is
# held in a cache keyed by a thread.
def connection
- @thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
+ @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
end
# Returns true if there is an open connection being used for the current thread.
@@ -967,7 +977,7 @@ module ActiveRecord
end
def pool_from_any_process_for(spec_name)
- owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] }
+ owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
owner_to_pool && owner_to_pool[spec_name]
end
end
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 2c352819fb..769f488469 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -51,8 +51,7 @@ module ActiveRecord
# Returns a single value from a record
def select_value(arel, name = nil, binds = [])
- arel, binds = binds_from_relation arel, binds
- if result = select_rows(to_sql(arel, binds), name, binds).first
+ if result = select_rows(arel, name, binds).first
result.first
end
end
@@ -60,14 +59,13 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
def select_values(arel, name = nil, binds = [])
- arel, binds = binds_from_relation arel, binds
- select_rows(to_sql(arel, binds), name, binds).map(&:first)
+ select_rows(arel, name, binds).map(&:first)
end
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
- def select_rows(sql, name = nil, binds = [])
- exec_query(sql, name, binds).rows
+ def select_rows(arel, name = nil, binds = [])
+ select_all(arel, name, binds).rows
end
# Executes the SQL statement in the context of this connection and returns
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 7eab7de5d3..e53ba4e666 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -83,7 +83,9 @@ module ActiveRecord
# the same SQL query and repeatedly return the same result each time, silently
# undermining the randomness you were expecting.
def clear_query_cache
- @query_cache.clear
+ @lock.synchronize do
+ @query_cache.clear
+ end
end
def select_all(arel, name = nil, binds = [], preparable: nil)
@@ -99,21 +101,23 @@ module ActiveRecord
private
def cache_sql(sql, name, binds)
- result =
- if @query_cache[sql].key?(binds)
- ActiveSupport::Notifications.instrument(
- "sql.active_record",
- sql: sql,
- binds: binds,
- name: name,
- connection_id: object_id,
- cached: true,
- )
- @query_cache[sql][binds]
- else
- @query_cache[sql][binds] = yield
- end
- result.dup
+ @lock.synchronize do
+ result =
+ if @query_cache[sql].key?(binds)
+ ActiveSupport::Notifications.instrument(
+ "sql.active_record",
+ sql: sql,
+ binds: binds,
+ name: name,
+ connection_id: object_id,
+ cached: true,
+ )
+ @query_cache[sql][binds]
+ else
+ @query_cache[sql][binds] = yield
+ end
+ result.dup
+ end
end
# If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 0c6bc16e6f..7f4132accf 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -1,4 +1,5 @@
require "active_support/core_ext/big_decimal/conversions"
+require "active_support/multibyte/chars"
module ActiveRecord
module ConnectionAdapters # :nodoc:
@@ -140,6 +141,10 @@ module ActiveRecord
quoted_date(value).sub(/\A2000-01-01 /, "")
end
+ def quoted_binary(value) # :nodoc:
+ "'#{quote_string(value.to_s)}'"
+ end
+
private
def type_casted_binds(binds)
@@ -152,7 +157,7 @@ module ActiveRecord
def _quote(value)
case value
- when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data
+ when String, ActiveSupport::Multibyte::Chars
"'#{quote_string(value.to_s)}'"
when true then quoted_true
when false then quoted_false
@@ -160,6 +165,7 @@ module ActiveRecord
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s("F")
when Numeric, ActiveSupport::Duration then value.to_s
+ when Type::Binary::Data then quoted_binary(value)
when Type::Time::Value then "'#{quoted_time(value)}'"
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
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 322684672f..c48a4acff8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -15,9 +15,9 @@ module ActiveRecord
end
delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
- :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options, to: :@conn
+ :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options, to: :@conn
private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
- :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options
+ :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options
private
@@ -29,7 +29,7 @@ module ActiveRecord
end
def visit_ColumnDefinition(o)
- o.sql_type ||= type_to_sql(o.type, o.limit, o.precision, o.scale)
+ o.sql_type = type_to_sql(o.type, o.options)
column_sql = "#{quote_column_name(o.name)} #{o.sql_type}"
add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
column_sql
@@ -49,7 +49,7 @@ module ActiveRecord
statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
end
- if supports_foreign_keys?
+ if supports_foreign_keys_in_create?
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
end
@@ -96,17 +96,7 @@ module ActiveRecord
end
def column_options(o)
- column_options = {}
- column_options[:null] = o.null unless o.null.nil?
- column_options[:default] = o.default unless o.default.nil?
- column_options[:column] = o
- column_options[:first] = o.first
- column_options[:after] = o.after
- column_options[:auto_increment] = o.auto_increment
- column_options[:primary_key] = o.primary_key
- column_options[:collation] = o.collation
- column_options[:comment] = o.comment
- column_options
+ o.options.merge(column: o)
end
def add_column_options!(sql, options)
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 9b324c090b..5eb7787226 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -3,29 +3,37 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
- end
+ IndexDefinition = Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc:
+ ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
def primary_key?
- primary_key || type.to_sym == :primary_key
+ options[:primary_key]
end
- end
- class AddColumnDefinition < Struct.new(:column) # :nodoc:
- end
+ [:limit, :precision, :scale, :default, :null, :collation, :comment].each do |option_name|
+ module_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{option_name}
+ options[:#{option_name}]
+ end
- class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc:
+ def #{option_name}=(value)
+ options[:#{option_name}] = value
+ end
+ CODE
+ end
end
- class PrimaryKeyDefinition < Struct.new(:name) # :nodoc:
- end
+ AddColumnDefinition = Struct.new(:column) # :nodoc:
+
+ ChangeColumnDefinition = Struct.new(:column, :name) #:nodoc:
- class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
+ PrimaryKeyDefinition = Struct.new(:name) # :nodoc:
+
+ ForeignKeyDefinition = Struct.new(:from_table, :to_table, :options) do #:nodoc:
def name
options[:name]
end
@@ -177,6 +185,7 @@ module ActiveRecord
:text,
:time,
:timestamp,
+ :virtual,
].each do |column_type|
module_eval <<-CODE, __FILE__, __LINE__ + 1
def #{column_type}(*args, **options)
@@ -357,33 +366,22 @@ module ActiveRecord
#
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
def references(*args, **options)
- args.each do |col|
- ReferenceDefinition.new(col, **options).add_to(self)
+ args.each do |ref_name|
+ ReferenceDefinition.new(ref_name, options).add_to(self)
end
end
alias :belongs_to :references
- def new_column_definition(name, type, options) # :nodoc:
+ def new_column_definition(name, type, **options) # :nodoc:
type = aliased_types(type.to_s, type)
- column = create_column_definition name, type
-
- column.limit = options[:limit]
- column.precision = options[:precision]
- column.scale = options[:scale]
- column.default = options[:default]
- column.null = options[:null]
- column.first = options[:first]
- column.after = options[:after]
- column.auto_increment = options[:auto_increment]
- column.primary_key = type == :primary_key || options[:primary_key]
- column.collation = options[:collation]
- column.comment = options[:comment]
- column
+ options[:primary_key] ||= type == :primary_key
+ options[:null] = false if options[:primary_key]
+ create_column_definition(name, type, options)
end
private
- def create_column_definition(name, type)
- ColumnDefinition.new name, type
+ def create_column_definition(name, type, options)
+ ColumnDefinition.new(name, type, options)
end
def aliased_types(name, fallback)
@@ -591,8 +589,7 @@ module ActiveRecord
# t.belongs_to(:supplier, foreign_key: true)
#
# See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
- def references(*args)
- options = args.extract_options!
+ def references(*args, **options)
args.each do |ref_name|
@base.add_reference(name, ref_name, options)
end
@@ -605,8 +602,7 @@ module ActiveRecord
# t.remove_belongs_to(:supplier, polymorphic: true)
#
# See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference]
- def remove_references(*args)
- options = args.extract_options!
+ def remove_references(*args, **options)
args.each do |ref_name|
@base.remove_reference(name, ref_name, options)
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index b912d24626..34036d8a1d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -7,13 +7,15 @@ module ActiveRecord
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
def column_spec(column)
- [schema_type(column), prepare_column_options(column)]
+ [schema_type_with_virtual(column), prepare_column_options(column)]
end
def column_spec_for_primary_key(column)
return {} if default_primary_key?(column)
spec = { id: schema_type(column).inspect }
spec.merge!(prepare_column_options(column).except!(:null))
+ spec[:default] ||= "nil" if explicit_primary_key_default?(column)
+ spec
end
# This can be overridden on an Adapter level basis to support other
@@ -49,9 +51,10 @@ module ActiveRecord
end
# Lists the valid migration options
- def migration_keys
- [:limit, :precision, :scale, :default, :null, :collation, :comment]
+ def migration_keys # :nodoc:
+ column_options_keys
end
+ deprecate :migration_keys
private
@@ -59,6 +62,18 @@ module ActiveRecord
schema_type(column) == :bigint
end
+ def explicit_primary_key_default?(column)
+ false
+ end
+
+ def schema_type_with_virtual(column)
+ if supports_virtual_columns? && column.virtual?
+ :virtual
+ else
+ schema_type(column)
+ end
+ end
+
def schema_type(column)
if column.bigint?
:bigint
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 e5c0e1690c..3686ad8b54 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -69,7 +69,9 @@ module ActiveRecord
end
# Returns an array of indexes for the given table.
- # def indexes(table_name, name = nil) end
+ def indexes(table_name, name = nil)
+ raise NotImplementedError, "#indexes is not implemented"
+ end
# Checks to see if an index exists on a table for a given index definition.
#
@@ -120,7 +122,7 @@ module ActiveRecord
checks = []
checks << lambda { |c| c.name == column_name }
checks << lambda { |c| c.type == type } if type
- migration_keys.each do |attr|
+ column_options_keys.each do |attr|
checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
end
@@ -271,8 +273,8 @@ module ActiveRecord
yield td if block_given?
- if options[:force] && data_source_exists?(table_name)
- drop_table(table_name, options)
+ if options[:force]
+ drop_table(table_name, **options, if_exists: true)
end
result = execute schema_creation.accept td
@@ -766,16 +768,17 @@ module ActiveRecord
raise ArgumentError, "You must specify the index name"
end
else
- index_name(table_name, column: options)
+ index_name(table_name, index_name_options(options))
end
end
# Verifies the existence of an index with a given name.
- #
- # The default argument is returned if the underlying implementation does not define the indexes method,
- # as there's no way to determine the correct answer in that case.
- def index_name_exists?(table_name, index_name, default)
- return default unless respond_to?(:indexes)
+ def index_name_exists?(table_name, index_name, default = nil)
+ unless default.nil?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing default to #index_name_exists? is deprecated without replacement.
+ MSG
+ end
index_name = index_name.to_s
indexes(table_name).detect { |i| i.name == index_name }
end
@@ -826,8 +829,8 @@ module ActiveRecord
#
# add_reference(:products, :supplier, foreign_key: {to_table: :firms})
#
- def add_reference(table_name, *args)
- ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self))
+ def add_reference(table_name, ref_name, **options)
+ ReferenceDefinition.new(ref_name, options).add_to(update_table_definition(table_name, self))
end
alias :add_belongs_to :add_reference
@@ -1006,15 +1009,15 @@ module ActiveRecord
end
end
- # Should not be called normally, but this operation is non-destructive.
- # The migrations module handles this automatically.
- def initialize_schema_migrations_table
+ def initialize_schema_migrations_table # :nodoc:
ActiveRecord::SchemaMigration.create_table
end
+ deprecate :initialize_schema_migrations_table
- def initialize_internal_metadata_table
+ def initialize_internal_metadata_table # :nodoc:
ActiveRecord::InternalMetadata.create_table
end
+ deprecate :initialize_internal_metadata_table
def internal_string_options_for_primary_key # :nodoc:
{ primary_key: true }
@@ -1050,7 +1053,7 @@ module ActiveRecord
end
end
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc:
type = type.to_sym if type
if native = native_database_types[type]
column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
@@ -1068,7 +1071,7 @@ module ActiveRecord
raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
end
- elsif [:datetime, :time].include?(type) && precision ||= native[:precision]
+ elsif [:datetime, :time, :interval].include?(type) && precision ||= native[:precision]
if (0..6) === precision
column_type_sql << "(#{precision})"
else
@@ -1120,18 +1123,14 @@ module ActiveRecord
end
def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
- if column_name.is_a?(String) && /\W/.match?(column_name)
- column_names = column_name
- else
- column_names = Array(column_name)
- end
+ column_names = index_column_names(column_name)
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
index_type = options[:type].to_s if options.key?(:type)
index_type ||= options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
- index_name ||= index_name(table_name, index_name_options(column_names))
+ index_name ||= index_name(table_name, column_names)
if options.key?(:algorithm)
algorithm = index_algorithms.fetch(options[:algorithm]) {
@@ -1147,7 +1146,7 @@ module ActiveRecord
validate_index_length!(table_name, index_name, options.fetch(:internal, false))
- if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false)
+ if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
@@ -1170,6 +1169,9 @@ module ActiveRecord
end
private
+ def column_options_keys
+ [:limit, :precision, :scale, :default, :null, :collation, :comment]
+ end
def add_index_sort_order(quoted_columns, **options)
if order = options[:order]
@@ -1208,13 +1210,13 @@ module ActiveRecord
if options.is_a?(Hash)
checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
- column_names = Array(options[:column]).map(&:to_s)
+ column_names = index_column_names(options[:column])
else
- column_names = Array(options).map(&:to_s)
+ column_names = index_column_names(options)
end
- if column_names.any?
- checks << lambda { |i| i.columns.join("_and_") == column_names.join("_and_") }
+ if column_names.present?
+ checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
end
raise ArgumentError, "No name or columns specified" if checks.none?
@@ -1261,8 +1263,16 @@ module ActiveRecord
AlterTable.new create_table_definition(name)
end
+ def index_column_names(column_names)
+ if column_names.is_a?(String) && /\W/.match?(column_names)
+ column_names
+ else
+ Array(column_names)
+ end
+ end
+
def index_name_options(column_names)
- if column_names.is_a?(String)
+ if column_names.is_a?(String) && /\W/.match?(column_names)
column_names = column_names.scan(/\w+/).join("_")
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 4046b3829d..b31ce0a181 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -107,6 +107,7 @@ module ActiveRecord
@schema_cache = SchemaCache.new self
@quoted_column_names, @quoted_table_names = {}, {}
@visitor = arel_visitor
+ @lock = Monitor.new
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
@@ -176,7 +177,7 @@ module ActiveRecord
if @owner == Thread.current
msg << "it is already leased by the current thread."
else
- msg << "it is already in use by a different thread: #{@owner}. " <<
+ msg << "it is already in use by a different thread: #{@owner}. " \
"Current thread: #{Thread.current}."
end
raise ActiveRecordError, msg
@@ -194,8 +195,8 @@ module ActiveRecord
def expire
if in_use?
if @owner != Thread.current
- raise ActiveRecordError, "Cannot expire connection, " <<
- "it is owned by a different thread: #{@owner}. " <<
+ raise ActiveRecordError, "Cannot expire connection, " \
+ "it is owned by a different thread: #{@owner}. " \
"Current thread: #{Thread.current}."
end
@@ -236,11 +237,10 @@ module ActiveRecord
false
end
- # Can this adapter determine the primary key for tables not attached
- # to an Active Record class, such as join tables?
- def supports_primary_key?
- false
+ def supports_primary_key? # :nodoc:
+ true
end
+ deprecate :supports_primary_key?
# Does this adapter support DDL rollbacks in transactions? That is, would
# CREATE TABLE or ALTER TABLE get rolled back by a transaction?
@@ -310,6 +310,12 @@ module ActiveRecord
false
end
+ # Does this adapter support creating foreign key constraints
+ # in the same statement as creating the table?
+ def supports_foreign_keys_in_create?
+ supports_foreign_keys?
+ end
+
# Does this adapter support views?
def supports_views?
false
@@ -340,6 +346,11 @@ module ActiveRecord
true
end
+ # Does this adapter support virtual columns?
+ def supports_virtual_columns?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -441,15 +452,15 @@ module ActiveRecord
@connection
end
- def case_sensitive_comparison(table, attribute, column, value)
- table[attribute].eq(Arel::Nodes::BindParam.new)
+ def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
+ table[attribute].eq(value)
end
- def case_insensitive_comparison(table, attribute, column, value)
+ def case_insensitive_comparison(table, attribute, column, value) # :nodoc:
if can_perform_case_insensitive_comparison_for?(column)
- table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new))
+ table[attribute].lower.eq(table.lower(value))
else
- table[attribute].eq(Arel::Nodes::BindParam.new)
+ table[attribute].eq(value)
end
end
@@ -499,6 +510,10 @@ module ActiveRecord
result
end
+ def default_index_type?(index) # :nodoc:
+ index.using.nil?
+ end
+
private
def initialize_type_map(m)
@@ -591,7 +606,11 @@ module ActiveRecord
binds: binds,
type_casted_binds: type_casted_binds,
statement_name: statement_name,
- connection_id: object_id) { yield }
+ connection_id: object_id) do
+ @lock.synchronize do
+ yield
+ end
+ end
rescue => e
raise translate_exception_class(e, sql)
end
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 01cd1e5446..14269b4570 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -93,10 +93,6 @@ module ActiveRecord
true
end
- def supports_primary_key?
- true
- end
-
def supports_bulk_alter? #:nodoc:
true
end
@@ -141,6 +137,14 @@ module ActiveRecord
end
end
+ def supports_virtual_columns?
+ if mariadb?
+ version >= "5.2.0"
+ else
+ version >= "5.7.5"
+ end
+ end
+
def supports_advisory_locks?
true
end
@@ -367,6 +371,12 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc:
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
indexes = []
current_index = nil
execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result|
@@ -521,6 +531,7 @@ module ActiveRecord
WHERE fk.referenced_column_name IS NOT NULL
AND fk.table_schema = #{quote(schema)}
AND fk.table_name = #{quote(name)}
+ AND rc.table_name = #{quote(name)}
SQL
fk_info.map do |row|
@@ -559,7 +570,7 @@ module ActiveRecord
end
# Maps logical Rails types to MySQL-specific data types.
- def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc:
sql = \
case type.to_s
when "integer"
@@ -575,7 +586,7 @@ module ActiveRecord
binary_to_sql(limit)
end
else
- super(type, limit, precision, scale)
+ super
end
sql << " unsigned" if unsigned && type != :primary_key
@@ -604,9 +615,9 @@ module ActiveRecord
SQL
end
- def case_sensitive_comparison(table, attribute, column, value)
+ def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
if column.collation && !column.case_sensitive?
- table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
+ table[attribute].eq(Arel::Nodes::Bin.new(value))
else
super
end
@@ -640,6 +651,10 @@ module ActiveRecord
!native_database_types[type].nil?
end
+ def default_index_type?(index) # :nodoc:
+ index.using == :btree || super
+ end
+
private
def initialize_type_map(m)
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index dcf56997db..3e4ea28f63 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -149,9 +149,18 @@ module ActiveRecord
# Expands each key in @configurations hash into fully resolved hash
def resolve_all
config = configurations.dup
+
+ if env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ env_config = config[env] if config[env].is_a?(Hash) && !(config[env].key?("adapter") || config[env].key?("url"))
+ end
+
+ config.reject! { |k, v| v.is_a?(Hash) && !(v.key?("adapter") || v.key?("url")) }
+ config.merge! env_config if env_config
+
config.each do |key, value|
config[key] = resolve(value) if value
end
+
config
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
index 1499c1681f..c9ad47c035 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
@@ -15,6 +15,10 @@ module ActiveRecord
def auto_increment?
extra == "auto_increment"
end
+
+ def virtual?
+ /\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra)
+ end
end
end
end
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 78e7181266..8c67a7a80b 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module MySQL
module DatabaseStatements
# Returns an ActiveRecord::Result instance.
- def select_all(arel, name = nil, binds = [], preparable: nil)
+ def select_all(arel, name = nil, binds = [], preparable: nil) # :nodoc:
result = if ExplainRegistry.collect? && prepared_statements
unprepared_statement { super }
else
@@ -15,8 +15,8 @@ module ActiveRecord
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
- def select_rows(sql, name = nil, binds = [])
- select_result(sql, name, binds) do |result|
+ def select_rows(arel, name = nil, binds = []) # :nodoc:
+ select_result(arel, name, binds) do |result|
@connection.next_result while @connection.more_results?
result.to_a
end
@@ -58,7 +58,9 @@ module ActiveRecord
@connection.last_id
end
- def select_result(sql, name = nil, binds = [])
+ def select_result(arel, name, binds)
+ arel, binds = binds_from_relation(arel, binds)
+ sql = to_sql(arel, binds)
if without_prepared_statement?(binds)
execute_and_free(sql, name) { |result| yield result }
else
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
index 9d11ad28d4..d4f5906b33 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -36,15 +36,9 @@ module ActiveRecord
end
end
- private
-
- def _quote(value)
- if value.is_a?(Type::Binary::Data)
- "x'#{value.hex}'"
- else
- super
- end
- end
+ def quoted_binary(value)
+ "x'#{value.hex}'"
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
index d808b50332..e8358271ab 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -1,9 +1,9 @@
module ActiveRecord
module ConnectionAdapters
module MySQL
- class SchemaCreation < AbstractAdapter::SchemaCreation
- delegate :add_sql_comment!, to: :@conn
- private :add_sql_comment!
+ class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
+ delegate :add_sql_comment!, :mariadb?, to: :@conn
+ private :add_sql_comment!, :mariadb?
private
@@ -11,11 +11,6 @@ module ActiveRecord
"DROP FOREIGN KEY #{name}"
end
- def visit_ColumnDefinition(o)
- o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned)
- super
- end
-
def visit_AddColumnDefinition(o)
add_column_position!(super, column_options(o.column))
end
@@ -29,12 +24,6 @@ module ActiveRecord
add_sql_comment!(super, options[:comment])
end
- def column_options(o)
- column_options = super
- column_options[:charset] = o.charset
- column_options
- end
-
def add_column_options!(sql, options)
if charset = options[:charset]
sql << " CHARACTER SET #{charset}"
@@ -44,6 +33,13 @@ module ActiveRecord
sql << " COLLATE #{collation}"
end
+ if as = options[:as]
+ sql << " AS (#{as})"
+ if options[:stored]
+ sql << (mariadb? ? " PERSISTENT" : " STORED")
+ end
+ end
+
add_sql_comment!(super, options[:comment])
end
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 0cf40de70f..773bbcef4e 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -3,10 +3,7 @@ module ActiveRecord
module MySQL
module ColumnMethods
def primary_key(name, type = :primary_key, **options)
- if type == :primary_key && !options.key?(:default)
- options[:auto_increment] = true
- options[:limit] = 8
- end
+ options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)
super
end
@@ -59,33 +56,25 @@ module ActiveRecord
end
end
- class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
- attr_accessor :charset, :unsigned
- end
-
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
- def new_column_definition(name, type, options) # :nodoc:
- column = super
- case column.type
+ def new_column_definition(name, type, **options) # :nodoc:
+ case type
+ when :virtual
+ type = options[:type]
when :primary_key
- column.type = :integer
- column.auto_increment = true
+ type = :integer
+ options[:limit] ||= 8
+ options[:auto_increment] = true
+ options[:primary_key] = true
when /\Aunsigned_(?<type>.+)\z/
- column.type = $~[:type].to_sym
- column.unsigned = true
+ type = $~[:type].to_sym
+ options[:unsigned] = true
end
- column.unsigned ||= options[:unsigned]
- column.charset = options[:charset]
- column
- end
-
- private
- def create_column_definition(name, type)
- MySQL::ColumnDefinition.new(name, type)
- end
+ super
+ end
end
class Table < ActiveRecord::ConnectionAdapters::Table
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index 2065816501..ad4a069d73 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -1,19 +1,17 @@
module ActiveRecord
module ConnectionAdapters
module MySQL
- module ColumnDumper
- def column_spec_for_primary_key(column)
- spec = super
- if column.type == :integer && !column.auto_increment?
- spec[:default] = schema_default(column) || "nil"
- end
- spec[:unsigned] = "true" if column.unsigned?
- spec
- end
-
+ module ColumnDumper # :nodoc:
def prepare_column_options(column)
spec = super
spec[:unsigned] = "true" if column.unsigned?
+
+ if supports_virtual_columns? && column.virtual?
+ spec[:as] = extract_expression_for_virtual_column(column)
+ spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra)
+ spec = { type: schema_type(column).inspect }.merge!(spec)
+ end
+
spec
end
@@ -24,7 +22,11 @@ module ActiveRecord
private
def default_primary_key?(column)
- super && column.auto_increment?
+ super && column.auto_increment? && !column.unsigned?
+ end
+
+ def explicit_primary_key_default?(column)
+ column.type == :integer && !column.auto_increment?
end
def schema_type(column)
@@ -46,6 +48,21 @@ module ActiveRecord
column.collation.inspect if column.collation != @table_collation_cache[table_name]
end
end
+
+ def extract_expression_for_virtual_column(column)
+ if mariadb?
+ create_table_info = create_table_info(column.table_name)
+ if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)} AS \((?<expression>.+?)\) #{column.extra}/m =~ create_table_info
+ $~[:expression].inspect
+ end
+ else
+ sql = "SELECT generation_expression FROM information_schema.columns" \
+ " WHERE table_schema = #{quote(@config[:database])}" \
+ " AND table_name = #{quote(column.table_name)}" \
+ " AND column_name = #{quote(column.name)}"
+ select_value(sql, "SCHEMA").inspect
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index 9a2017b443..705e6063dc 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -7,18 +7,14 @@ module ActiveRecord
PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
end
- def select_value(arel, name = nil, binds = [])
- arel, binds = binds_from_relation arel, binds
- sql = to_sql(arel, binds)
- execute_and_clear(sql, name, binds) do |result|
+ def select_value(arel, name = nil, binds = []) # :nodoc:
+ select_result(arel, name, binds) do |result|
result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
end
end
- def select_values(arel, name = nil, binds = [])
- arel, binds = binds_from_relation arel, binds
- sql = to_sql(arel, binds)
- execute_and_clear(sql, name, binds) do |result|
+ def select_values(arel, name = nil, binds = []) # :nodoc:
+ select_result(arel, name, binds) do |result|
if result.nfields > 0
result.column_values(0)
else
@@ -29,8 +25,8 @@ module ActiveRecord
# Executes a SELECT query and returns an array of rows. Each row is an
# array of field values.
- def select_rows(sql, name = nil, binds = [])
- execute_and_clear(sql, name, binds) do |result|
+ def select_rows(arel, name = nil, binds = []) # :nodoc:
+ select_result(arel, name, binds) do |result|
result.values
end
end
@@ -179,6 +175,14 @@ module ActiveRecord
def suppress_composite_primary_key(pk)
pk unless pk.is_a?(Array)
end
+
+ def select_result(arel, name, binds)
+ arel, binds = binds_from_relation(arel, binds)
+ sql = to_sql(arel, binds)
+ execute_and_clear(sql, name, binds) do |result|
+ yield result
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index 0e526f6201..4098250f3e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -11,6 +11,7 @@ require "active_record/connection_adapters/postgresql/oid/inet"
require "active_record/connection_adapters/postgresql/oid/json"
require "active_record/connection_adapters/postgresql/oid/jsonb"
require "active_record/connection_adapters/postgresql/oid/money"
+require "active_record/connection_adapters/postgresql/oid/oid"
require "active_record/connection_adapters/postgresql/oid/point"
require "active_record/connection_adapters/postgresql/oid/legacy_point"
require "active_record/connection_adapters/postgresql/oid/range"
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb
new file mode 100644
index 0000000000..9c2ac08b30
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb
@@ -0,0 +1,13 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ module OID # :nodoc:
+ class Oid < Type::Integer # :nodoc:
+ def type
+ :oid
+ end
+ end
+ end
+ 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 2c714f4018..54d5d0902e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/string/filters"
-
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
index 2d2fede4e8..564e82a4ac 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb
@@ -5,8 +5,9 @@ module ActiveRecord
class SpecializedString < Type::String # :nodoc:
attr_reader :type
- def initialize(type)
+ def initialize(type, **options)
@type = type
+ super(options)
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index 3783925954..6663448a99 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -55,6 +55,10 @@ module ActiveRecord
end
end
+ def quoted_binary(value) # :nodoc:
+ "'#{escape_bytea(value.to_s)}'"
+ end
+
def quote_default_expression(value, column) # :nodoc:
if value.is_a?(Proc)
value.call
@@ -76,8 +80,6 @@ module ActiveRecord
def _quote(value)
case value
- when Type::Binary::Data
- "'#{escape_bytea(value.to_s)}'"
when OID::Xml::Data
"xml '#{quote_string(value.to_s)}'"
when OID::Bit::Data
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
new file mode 100644
index 0000000000..e1d5089115
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb
@@ -0,0 +1,15 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module PostgreSQL
+ class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
+ private
+ def add_column_options!(sql, options)
+ if options[:collation]
+ sql << " COLLATE \"#{options[:collation]}\""
+ end
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
index 4afb4733eb..11ea1e5144 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb
@@ -42,6 +42,7 @@ module ActiveRecord
# a record (as primary keys cannot be +nil+). This might be done via the
# +SecureRandom.uuid+ method and a +before_save+ callback, for instance.
def primary_key(name, type = :primary_key, **options)
+ options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)
if type == :uuid
options[:default] = options.fetch(:default, "gen_random_uuid()")
elsif options.delete(:auto_increment) == true && %i(integer bigint).include?(type)
@@ -87,6 +88,10 @@ module ActiveRecord
args.each { |name| column(name, :inet, options) }
end
+ def interval(*args, **options)
+ args.each { |name| column(name, :interval, options) }
+ end
+
def int4range(*args, **options)
args.each { |name| column(name, :int4range, options) }
end
@@ -119,6 +124,10 @@ module ActiveRecord
args.each { |name| column(name, :numrange, options) }
end
+ def oid(*args, **options)
+ args.each { |name| column(name, :oid, options) }
+ end
+
def point(*args, **options)
args.each { |name| column(name, :point, options) }
end
@@ -172,24 +181,8 @@ module ActiveRecord
end
end
- class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
- attr_accessor :array
- end
-
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
-
- def new_column_definition(name, type, options) # :nodoc:
- column = super
- column.array = options[:array]
- column
- end
-
- private
-
- def create_column_definition(name, type)
- PostgreSQL::ColumnDefinition.new name, type
- end
end
class Table < ActiveRecord::ConnectionAdapters::Table
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index 7808d37deb..5555a46b6b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -1,15 +1,7 @@
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
- module ColumnDumper
- def column_spec_for_primary_key(column)
- spec = super
- if schema_type(column) == :uuid
- spec[:default] ||= "nil"
- end
- spec
- end
-
+ module ColumnDumper # :nodoc:
# Adds +:array+ option to the default set
def prepare_column_options(column)
spec = super
@@ -28,6 +20,10 @@ module ActiveRecord
schema_type(column) == :bigserial
end
+ def explicit_primary_key_default?(column)
+ column.type == :uuid || (column.type == :integer && !column.serial?)
+ end
+
def schema_type(column)
return super unless column.serial?
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 85836fc575..a61d920a73 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -3,22 +3,6 @@ require "active_support/core_ext/string/strip"
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
- class SchemaCreation < AbstractAdapter::SchemaCreation
- private
-
- def visit_ColumnDefinition(o)
- o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array)
- super
- end
-
- def add_column_options!(sql, options)
- if options[:collation]
- sql << " COLLATE \"#{options[:collation]}\""
- end
- super
- end
- end
-
module SchemaStatements
# Drops the database specified on the +name+ attribute
# and creates it again using the provided +options+.
@@ -148,7 +132,12 @@ module ActiveRecord
end
# Verifies existence of an index with a given name.
- def index_name_exists?(table_name, index_name, default)
+ def index_name_exists?(table_name, index_name, default = nil)
+ unless default.nil?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing default to #index_name_exists? is deprecated without replacement.
+ MSG
+ end
table = Utils.extract_schema_qualified_name(table_name.to_s)
index = Utils.extract_schema_qualified_name(index_name.to_s)
@@ -166,7 +155,13 @@ module ActiveRecord
end
# Returns an array of indexes for the given table.
- def indexes(table_name, name = nil)
+ def indexes(table_name, name = nil) # :nodoc:
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
table = Utils.extract_schema_qualified_name(table_name.to_s)
result = query(<<-SQL, "SCHEMA")
@@ -435,17 +430,18 @@ module ActiveRecord
end
def primary_keys(table_name) # :nodoc:
+ name = Utils.extract_schema_qualified_name(table_name.to_s)
select_values(<<-SQL.strip_heredoc, "SCHEMA")
- WITH pk_constraint AS (
- SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint
- WHERE contype = 'p'
- AND conrelid = #{quote(quote_table_name(table_name))}::regclass
- ), cons AS (
- SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint
- )
- SELECT attr.attname FROM pg_attribute attr
- INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum
- ORDER BY cons.rownum
+ SELECT column_name
+ FROM information_schema.key_column_usage kcu
+ JOIN information_schema.table_constraints tc
+ ON kcu.table_name = tc.table_name
+ AND kcu.table_schema = tc.table_schema
+ AND kcu.constraint_name = tc.constraint_name
+ WHERE constraint_type = 'PRIMARY KEY'
+ AND kcu.table_name = #{quote(name.identifier)}
+ AND kcu.table_schema = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}
+ ORDER BY kcu.ordinal_position
SQL
end
@@ -480,7 +476,7 @@ module ActiveRecord
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[:limit], options[:precision], options[:scale], options[:array])
+ sql_type = type_to_sql(type, options)
sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}"
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
@@ -488,7 +484,7 @@ module ActiveRecord
if options[:using]
sql << " USING #{options[:using]}"
elsif options[:cast_as]
- cast_as_type = type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale], options[:array])
+ cast_as_type = type_to_sql(options[:cast_as], options)
sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})"
end
execute sql
@@ -624,7 +620,7 @@ module ActiveRecord
end
# Maps logical Rails types to PostgreSQL-specific data types.
- def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil)
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc:
sql = \
case type.to_s
when "binary"
@@ -649,7 +645,7 @@ module ActiveRecord
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.")
end
else
- super(type, limit, precision, scale)
+ super
end
sql << "[]" if array && type != :primary_key
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 0ebd907cc0..c89e29ba44 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -9,6 +9,7 @@ require "active_record/connection_adapters/postgresql/explain_pretty_printer"
require "active_record/connection_adapters/postgresql/oid"
require "active_record/connection_adapters/postgresql/quoting"
require "active_record/connection_adapters/postgresql/referential_integrity"
+require "active_record/connection_adapters/postgresql/schema_creation"
require "active_record/connection_adapters/postgresql/schema_definitions"
require "active_record/connection_adapters/postgresql/schema_dumper"
require "active_record/connection_adapters/postgresql/schema_statements"
@@ -108,6 +109,8 @@ module ActiveRecord
bit: { name: "bit" },
bit_varying: { name: "bit varying" },
money: { name: "money" },
+ interval: { name: "interval" },
+ oid: { name: "oid" },
}
OID = PostgreSQL::OID #:nodoc:
@@ -233,7 +236,9 @@ module ActiveRecord
# Clears the prepared statements cache.
def clear_cache!
- @statements.clear
+ @lock.synchronize do
+ @statements.clear
+ end
end
def truncate(table_name, name = nil)
@@ -281,11 +286,6 @@ module ActiveRecord
true
end
- # Does PostgreSQL support finding primary key on non-Active Record tables?
- def supports_primary_key? #:nodoc:
- true
- end
-
def set_standard_conforming_strings
execute("SET standard_conforming_strings = on", "SCHEMA")
end
@@ -404,6 +404,10 @@ module ActiveRecord
@connection.server_version
end
+ def default_index_type?(index) # :nodoc:
+ index.using == :btree || super
+ end
+
private
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
@@ -438,7 +442,7 @@ module ActiveRecord
end
end
- def get_oid_type(oid, fmod, column_name, sql_type = "")
+ def get_oid_type(oid, fmod, column_name, sql_type = "".freeze)
if !type_map.key?(oid)
load_additional_types(type_map, [oid])
end
@@ -455,7 +459,7 @@ module ActiveRecord
register_class_with_limit m, "int2", Type::Integer
register_class_with_limit m, "int4", Type::Integer
register_class_with_limit m, "int8", Type::Integer
- m.alias_type "oid", "int2"
+ m.register_type "oid", OID::Oid.new
m.register_type "float4", Type::Float.new
m.alias_type "float8", "float4"
m.register_type "text", Type::Text.new
@@ -490,8 +494,10 @@ module ActiveRecord
m.register_type "polygon", OID::SpecializedString.new(:polygon)
m.register_type "circle", OID::SpecializedString.new(:circle)
- # FIXME: why are we keeping these types as strings?
- m.alias_type "interval", "varchar"
+ m.register_type "interval" do |_, _, sql_type|
+ precision = extract_precision(sql_type)
+ OID::SpecializedString.new(:interval, precision: precision)
+ end
register_class_with_precision m, "time", Type::Time
register_class_with_precision m, "timestamp", OID::DateTime
@@ -633,8 +639,10 @@ module ActiveRecord
if in_transaction?
raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
else
- # outside of transactions we can simply flush this query and retry
- @statements.delete sql_key(sql)
+ @lock.synchronize do
+ # outside of transactions we can simply flush this query and retry
+ @statements.delete sql_key(sql)
+ end
retry
end
end
@@ -670,19 +678,21 @@ module ActiveRecord
# Prepare the statement if it hasn't been prepared, return
# the statement key.
def prepare_statement(sql)
- sql_key = sql_key(sql)
- unless @statements.key? sql_key
- nextkey = @statements.next_key
- begin
- @connection.prepare nextkey, sql
- rescue => e
- raise translate_exception_class(e, sql)
+ @lock.synchronize do
+ sql_key = sql_key(sql)
+ unless @statements.key? sql_key
+ nextkey = @statements.next_key
+ begin
+ @connection.prepare nextkey, sql
+ rescue => e
+ raise translate_exception_class(e, sql)
+ end
+ # Clear the queue
+ @connection.get_last_result
+ @statements[sql_key] = nextkey
end
- # Clear the queue
- @connection.get_last_result
- @statements[sql_key] = nextkey
+ @statements[sql_key]
end
- @statements[sql_key]
end
# Connects to a PostgreSQL server and sets up the adapter depending on the
@@ -759,11 +769,11 @@ module ActiveRecord
query(<<-end_sql, "SCHEMA")
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
- (SELECT c.collname FROM pg_collation c, pg_type t
- WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
- col_description(a.attrelid, a.attnum) AS comment
- FROM pg_attribute a LEFT JOIN pg_attrdef d
- ON a.attrelid = d.adrelid AND a.attnum = d.adnum
+ c.collname, col_description(a.attrelid, a.attnum) AS comment
+ FROM pg_attribute a
+ LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
+ LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation
WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass
AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index f01ed67b0f..7276a65098 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -18,15 +18,11 @@ module ActiveRecord
quoted_date(value)
end
- private
+ def quoted_binary(value)
+ "x'#{value.hex}'"
+ end
- def _quote(value)
- if value.is_a?(Type::Binary::Data)
- "x'#{value.hex}'"
- else
- super
- end
- end
+ private
def _type_cast(value)
case value
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
index 70c0d28830..bc798d1dbb 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
@@ -1,15 +1,8 @@
module ActiveRecord
module ConnectionAdapters
module SQLite3
- class SchemaCreation < AbstractAdapter::SchemaCreation
+ class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
private
-
- def column_options(o)
- options = super
- options[:null] = false if o.primary_key
- options
- end
-
def add_column_options!(sql, options)
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
index d0b38dff4c..e157e4b218 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb
@@ -3,7 +3,7 @@ module ActiveRecord
module SQLite3
module ColumnMethods
def primary_key(name, type = :primary_key, **options)
- if options.delete(:auto_increment) == true && %i(integer bigint).include?(type)
+ if %i(integer bigint).include?(type) && (options.delete(:auto_increment) == true || !options.key?(:default))
type = :primary_key
end
@@ -13,6 +13,11 @@ module ActiveRecord
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
+
+ def references(*args, **options)
+ super(*args, type: :integer, **options)
+ end
+ alias :belongs_to :references
end
class Table < ActiveRecord::ConnectionAdapters::Table
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb
index c027fef83c..eec018eda3 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb
@@ -1,12 +1,16 @@
module ActiveRecord
module ConnectionAdapters
module SQLite3
- module ColumnDumper
+ module ColumnDumper # :nodoc:
private
def default_primary_key?(column)
schema_type(column) == :integer
end
+
+ def explicit_primary_key_default?(column)
+ column.bigint?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 297e2997a9..16ef195bfc 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -95,6 +95,8 @@ module ActiveRecord
@active = nil
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
+
+ configure_connection
end
def supports_ddl_transactions?
@@ -120,12 +122,12 @@ module ActiveRecord
true
end
- def supports_primary_key? #:nodoc:
+ def requires_reloading?
true
end
- def requires_reloading?
- true
+ def supports_foreign_keys_in_create?
+ sqlite_version >= "3.6.19"
end
def supports_views?
@@ -185,6 +187,19 @@ module ActiveRecord
true
end
+ # REFERENTIAL INTEGRITY ====================================
+
+ def disable_referential_integrity # :nodoc:
+ old = select_value("PRAGMA foreign_keys")
+
+ begin
+ execute("PRAGMA foreign_keys = OFF")
+ yield
+ ensure
+ execute("PRAGMA foreign_keys = #{old}")
+ end
+ end
+
#--
# DATABASE STATEMENTS ======================================
#++
@@ -316,6 +331,12 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc:
+ if name
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing name to #indexes is deprecated without replacement.
+ MSG
+ end
+
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
sql = <<-SQL
SELECT sql
@@ -418,6 +439,24 @@ module ActiveRecord
rename_column_indexes(table_name, column.name, new_column_name)
end
+ def add_reference(table_name, ref_name, **options) # :nodoc:
+ super(table_name, ref_name, type: :integer, **options)
+ end
+ alias :add_belongs_to :add_reference
+
+ def foreign_keys(table_name)
+ fk_info = select_all("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
+ fk_info.map do |row|
+ options = {
+ column: row["from"],
+ primary_key: row["to"],
+ on_delete: extract_foreign_key_action(row["on_delete"]),
+ on_update: extract_foreign_key_action(row["on_update"])
+ }
+ ForeignKeyDefinition.new(table_name, row["table"], options)
+ end
+ end
+
private
def table_structure(table_name)
@@ -519,6 +558,8 @@ module ActiveRecord
RecordNotUnique.new(message)
when /.* may not be NULL/, /NOT NULL constraint failed: .*/
NotNullViolation.new(message)
+ when /FOREIGN KEY constraint failed/i
+ InvalidForeignKey.new(message)
else
super
end
@@ -568,6 +609,18 @@ module ActiveRecord
def create_table_definition(*args)
SQLite3::TableDefinition.new(*args)
end
+
+ def extract_foreign_key_action(specifier)
+ case specifier
+ when "CASCADE"; :cascade
+ when "SET NULL"; :nullify
+ when "RESTRICT"; :restrict
+ end
+ end
+
+ def configure_connection
+ execute("PRAGMA foreign_keys = ON", "SCHEMA")
+ end
end
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 5d0f82130d..0028dc0edb 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -196,12 +196,12 @@ module ActiveRecord
end
def find_by(*args) # :nodoc:
- return super if scope_attributes? || !(Hash === args.first) || reflect_on_all_aggregations.any?
+ return super if scope_attributes? || reflect_on_all_aggregations.any?
hash = args.first
- return super if hash.values.any? { |v|
- v.nil? || Array === v || Hash === v || Relation === v
+ return super if !(Hash === hash) || hash.values.any? { |v|
+ v.nil? || Array === v || Hash === v || Relation === v || Base === v
}
# We can't cache Post.find_by(author: david) ...yet
@@ -308,7 +308,7 @@ module ActiveRecord
relation = Relation.create(self, arel_table, predicate_builder)
if finder_needs_type_condition? && !ignore_default_scope?
- relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
+ relation.where(type_condition).create_with(inheritance_column.to_s => sti_name)
else
relation
end
@@ -450,7 +450,7 @@ module ActiveRecord
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
def hash
if id
- self.class.hash ^ self.id.hash
+ self.class.hash ^ id.hash
else
super
end
@@ -472,7 +472,7 @@ module ActiveRecord
# Allows sort on objects
def <=>(other_object)
if other_object.is_a?(self.class)
- self.to_key <=> other_object.to_key
+ to_key <=> other_object.to_key
else
super
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 93b9371206..cbd71a3779 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -105,7 +105,8 @@ module ActiveRecord
end
if touch
- updates << sanitize_sql_for_assignment(touch_updates(touch))
+ touch_updates = touch_updates(touch)
+ updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty?
end
unscoped.where(primary_key => id).update_all updates.join(", ")
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index de1b0d63bc..e79167d568 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -885,7 +885,7 @@ module ActiveRecord
#
# The keys must be the fixture names, that coincide with the short paths to the fixture files.
def set_fixture_class(class_names = {})
- self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys)
+ self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
end
def fixtures(*fixture_set_names)
@@ -970,6 +970,7 @@ module ActiveRecord
@fixture_connections = enlist_fixture_connections
@fixture_connections.each do |connection|
connection.begin_transaction joinable: false
+ connection.pool.lock_thread = true
end
# When connections are established in the future, begin a transaction too
@@ -985,6 +986,7 @@ module ActiveRecord
if connection && !@fixture_connections.include?(connection)
connection.begin_transaction joinable: false
+ connection.pool.lock_thread = true
@fixture_connections << connection
end
end
@@ -1007,6 +1009,7 @@ module ActiveRecord
ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
@fixture_connections.each do |connection|
connection.rollback_transaction if connection.transaction_open?
+ connection.pool.lock_thread = false
end
@fixture_connections.clear
else
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index e73cb4fc12..263e2a5f7f 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -59,7 +59,16 @@ module ActiveRecord
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
# the locked record.
def lock!(lock = true)
- reload(lock: lock) if persisted?
+ if persisted?
+ if changed?
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Locking a record with unpersisted changes is deprecated and will raise an
+ exception in Rails 5.2. Use `save` to persist the changes, or `reload` to
+ discard them explicitly.
+ MSG
+ end
+ reload(lock: lock)
+ end
self
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 4b8d8d9105..ea101946f4 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -81,7 +81,7 @@ module ActiveRecord
RED
when /transaction\s*\Z/i
CYAN
- else
+ else
MAGENTA
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index ed0c81b639..40f6226315 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -525,7 +525,7 @@ module ActiveRecord
raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \
"Please specify the Rails release the migration was written for:\n" \
"\n" \
- " class #{self.class.name} < ActiveRecord::Migration[4.2]"
+ " class #{subclass} < ActiveRecord::Migration[4.2]"
end
end
@@ -692,7 +692,7 @@ module ActiveRecord
connection.respond_to?(:reverting) && connection.reverting
end
- class ReversibleBlockHelper < Struct.new(:reverting) # :nodoc:
+ ReversibleBlockHelper = Struct.new(:reverting) do # :nodoc:
def up
yield unless reverting
end
@@ -938,7 +938,7 @@ module ActiveRecord
# MigrationProxy is used to defer loading of the actual migration classes
# until they are needed
- class MigrationProxy < Struct.new(:name, :version, :filename, :scope)
+ MigrationProxy = Struct.new(:name, :version, :filename, :scope) do
def initialize(name, version, filename, scope)
super
@migration = nil
@@ -1107,8 +1107,8 @@ module ActiveRecord
validate(@migrations)
- Base.connection.initialize_schema_migrations_table
- Base.connection.initialize_internal_metadata_table
+ ActiveRecord::SchemaMigration.create_table
+ ActiveRecord::InternalMetadata.create_table
end
def current_version
@@ -1170,9 +1170,10 @@ module ActiveRecord
def run_without_lock
migration = migrations.detect { |m| m.version == @target_version }
raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
- execute_migration_in_transaction(migration, @direction)
+ result = execute_migration_in_transaction(migration, @direction)
record_environment
+ result
end
# Used for running multiple migrations up to or down to a certain value.
@@ -1181,11 +1182,12 @@ module ActiveRecord
raise UnknownMigrationVersionError.new(@target_version)
end
- runnable.each do |migration|
+ result = runnable.each do |migration|
execute_migration_in_transaction(migration, @direction)
end
record_environment
+ result
end
# Stores the current environment in the database.
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 2904634eb7..85032ce470 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -14,23 +14,62 @@ module ActiveRecord
V5_1 = Current
class V5_0 < V5_1
+ module TableDefinition
+ def references(*args, **options)
+ super(*args, type: :integer, **options)
+ end
+ alias :belongs_to :references
+ end
+
def create_table(table_name, options = {})
if adapter_name == "PostgreSQL"
- if options[:id] == :uuid && !options[:default]
+ if options[:id] == :uuid && !options.key?(:default)
options[:default] = "uuid_generate_v4()"
end
end
+ unless adapter_name == "Mysql2" && options[:id] == :bigint
+ if [:integer, :bigint].include?(options[:id]) && !options.key?(:default)
+ options[:default] = nil
+ end
+ end
+
# Since 5.1 Postgres adapter uses bigserial type for primary
# keys by default and MySQL uses bigint. This compat layer makes old migrations utilize
# serial/int type instead -- the way it used to work before 5.1.
- if options[:id].blank?
+ unless options.key?(:id)
options[:id] = :integer
- options[:auto_increment] = true
end
- super
+ if block_given?
+ super(table_name, options) do |t|
+ class << t
+ prepend TableDefinition
+ end
+ yield t
+ end
+ else
+ super
+ end
end
+
+ def change_table(table_name, options = {})
+ if block_given?
+ super(table_name, options) do |t|
+ class << t
+ prepend TableDefinition
+ end
+ yield t
+ end
+ else
+ super
+ end
+ end
+
+ def add_reference(table_name, ref_name, **options)
+ super(table_name, ref_name, type: :integer, **options)
+ end
+ alias :add_belongs_to :add_reference
end
class V4_2 < V5_0
@@ -106,13 +145,13 @@ module ActiveRecord
def index_name_for_remove(table_name, options = {})
index_name = index_name(table_name, options)
- unless index_name_exists?(table_name, index_name, true)
+ unless index_name_exists?(table_name, index_name)
if options.is_a?(Hash) && options.has_key?(:name)
options_without_column = options.dup
options_without_column.delete :column
index_name_without_column = index_name(table_name, options_without_column)
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
+ return index_name_without_column if index_name_exists?(table_name, index_name_without_column)
end
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 2a28c6bf6d..54216caaaf 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -432,6 +432,7 @@ module ActiveRecord
connection.schema_cache.clear_data_source_cache!(table_name)
reload_schema_from_cache
+ initialize_find_by_cache
end
private
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index e983026961..01ecd79b8f 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -393,7 +393,7 @@ module ActiveRecord
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
# then the existing record will be marked for destruction.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
if attributes.respond_to?(:permitted?)
attributes = attributes.to_h
end
@@ -452,7 +452,7 @@ module ActiveRecord
# { id: '2', _destroy: true }
# ])
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
- options = self.nested_attributes_options[association_name]
+ options = nested_attributes_options[association_name]
if attributes_collection.respond_to?(:permitted?)
attributes_collection = attributes_collection.to_h
end
@@ -562,7 +562,7 @@ module ActiveRecord
def call_reject_if(association_name, attributes)
return false if will_be_destroyed?(association_name, attributes)
- case callback = self.nested_attributes_options[association_name][:reject_if]
+ case callback = nested_attributes_options[association_name][:reject_if]
when Symbol
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
when Proc
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 63d8a201f0..7ceb7d1a55 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -148,6 +148,8 @@ module ActiveRecord
#
# Attributes marked as readonly are silently ignored if the record is
# being updated.
+ #
+ # Unless an error is raised, returns true.
def save!(*args)
create_or_update(*args) || raise(RecordNotSaved.new("Failed to save the record", self))
end
@@ -338,14 +340,16 @@ module ActiveRecord
self
end
- # Wrapper around #increment that saves the record. This method differs from
- # its non-bang version in that it passes through the attribute setter.
- # Saving is not subjected to validation checks. Returns +true+ if the
- # record could be saved.
- def increment!(attribute, by = 1)
+ # Wrapper around #increment that writes the update to the database.
+ # Only +attribute+ is updated; the record itself is not saved.
+ # This means that any other modified attributes will still be dirty.
+ # Validations and callbacks are skipped. Supports the `touch` option from
+ # +update_counters+, see that for more.
+ # Returns +self+.
+ def increment!(attribute, by = 1, touch: nil)
increment(attribute, by)
change = public_send(attribute) - (attribute_in_database(attribute.to_s) || 0)
- self.class.update_counters(id, attribute => change)
+ self.class.update_counters(id, attribute => change, touch: touch)
clear_attribute_change(attribute) # eww
self
end
@@ -357,12 +361,14 @@ module ActiveRecord
increment(attribute, -by)
end
- # Wrapper around #decrement that saves the record. This method differs from
- # its non-bang version in the sense that it passes through the attribute setter.
- # Saving is not subjected to validation checks. Returns +true+ if the
- # record could be saved.
- def decrement!(attribute, by = 1)
- increment!(attribute, -by)
+ # Wrapper around #decrement that writes the update to the database.
+ # Only +attribute+ is updated; the record itself is not saved.
+ # This means that any other modified attributes will still be dirty.
+ # Validations and callbacks are skipped. Supports the `touch` option from
+ # +update_counters+, see that for more.
+ # Returns +self+.
+ def decrement!(attribute, by = 1, touch: nil)
+ increment!(attribute, -by, touch: touch)
end
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 2701c5bca9..0276d41494 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -87,8 +87,8 @@ module ActiveRecord
if File.file?(filename)
cache = YAML.load(File.read(filename))
if cache.version == ActiveRecord::Migrator.current_version
- self.connection.schema_cache = cache
- self.connection_pool.schema_cache = cache.dup
+ 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}."
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index cf1c0fb423..246d330b76 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -309,14 +309,6 @@ db_namespace = namespace :db do
end
namespace :test do
-
- task :deprecated do
- Rake.application.top_level_tasks.grep(/^db:test:/).each do |task|
- $stderr.puts "WARNING: #{task} is deprecated. The Rails test helper now maintains " \
- "your test schema automatically, see the release notes for details."
- end
- end
-
# desc "Recreate the test database from the current schema"
task load: %w(db:test:purge) do
case ActiveRecord::Base.schema_format
@@ -345,22 +337,6 @@ db_namespace = namespace :db do
ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"]
end
- # desc "Recreate the test database from a fresh schema"
- task clone: %w(db:test:deprecated environment) do
- case ActiveRecord::Base.schema_format
- when :ruby
- db_namespace["test:clone_schema"].invoke
- when :sql
- db_namespace["test:clone_structure"].invoke
- end
- end
-
- # desc "Recreate the test database from a fresh schema.rb file"
- task clone_schema: %w(db:test:deprecated db:schema:dump db:test:load_schema)
-
- # desc "Recreate the test database from a fresh structure.sql file"
- task clone_structure: %w(db:test:deprecated db:structure:dump db:test:load_structure)
-
# desc "Empty the test database"
task purge: %w(environment load_config check_protected_environments) do
ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"]
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 8ff265bdfa..6274996ab8 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -11,7 +11,7 @@ module ActiveRecord
# Attributes listed as readonly will be used to create a new record but update operations will
# ignore these fields.
def attr_readonly(*attributes)
- self._attr_readonly = Set.new(attributes.map(&:to_s)) + (self._attr_readonly || [])
+ self._attr_readonly = Set.new(attributes.map(&:to_s)) + (_attr_readonly || [])
end
# Returns an array of all the attributes that have been specified as readonly.
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index f3e81ee1e2..61a2279292 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,5 +1,6 @@
require "thread"
require "active_support/core_ext/string/filters"
+require "active_support/deprecation"
module ActiveRecord
# = Active Record Reflection
@@ -175,8 +176,19 @@ module ActiveRecord
JoinKeys.new(foreign_key, active_record_primary_key)
end
+ # Returns a list of scopes that should be applied for this Reflection
+ # object when querying the database.
+ def scopes
+ scope ? [scope] : []
+ end
+
+ def scope_chain
+ chain.map(&:scopes)
+ end
+ deprecate :scope_chain
+
def constraints
- scope_chain.flatten
+ chain.map(&:scopes).flatten
end
def counter_cache_column
@@ -321,7 +333,7 @@ module ActiveRecord
end
end
- # Holds all the meta-data about an aggregation as it was specified in the
+ # Holds all the metadata about an aggregation as it was specified in the
# Active Record class.
class AggregateReflection < MacroReflection #:nodoc:
def mapping
@@ -330,7 +342,7 @@ module ActiveRecord
end
end
- # Holds all the meta-data about an association as it was specified in the
+ # Holds all the metadata about an association as it was specified in the
# Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
# Returns the target association's class.
@@ -364,6 +376,17 @@ module ActiveRecord
@constructable = calculate_constructable(macro, options)
@association_scope_cache = {}
@scope_lock = Mutex.new
+
+ if options[:class_name] && options[:class_name].class == Class
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing a class to the `class_name` is deprecated and will raise
+ an ArgumentError in Rails 5.2. It eagerloads more classes than
+ necessary and potentially creates circular dependencies.
+
+ Please pass the class name as a string:
+ `#{macro} :#{name}, class_name: '#{options[:class_name]}'`
+ MSG
+ end
end
def association_scope_cache(conn, owner)
@@ -398,7 +421,7 @@ module ActiveRecord
end
def association_primary_key_type
- klass.type_for_attribute(association_primary_key)
+ klass.type_for_attribute(association_primary_key.to_s)
end
def active_record_primary_key
@@ -450,12 +473,6 @@ module ActiveRecord
false
end
- # An array of arrays of scopes. Each item in the outside array corresponds to a reflection
- # in the #chain.
- def scope_chain
- scope ? [[scope]] : [[]]
- end
-
def has_scope?
scope
end
@@ -698,7 +715,7 @@ module ActiveRecord
end
end
- # Holds all the meta-data about a :through association as it was specified
+ # Holds all the metadata about a :through association as it was specified
# in the Active Record class.
class ThroughReflection < AbstractReflection #:nodoc:
attr_reader :delegate_reflection
@@ -785,45 +802,12 @@ module ActiveRecord
through_reflection.clear_association_scope_cache
end
- # Consider the following example:
- #
- # class Person
- # has_many :articles
- # has_many :comment_tags, through: :articles
- # end
- #
- # class Article
- # has_many :comments
- # has_many :comment_tags, through: :comments, source: :tags
- # end
- #
- # class Comment
- # has_many :tags
- # end
- #
- # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
- # but only Comment.tags will be represented in the #chain. So this method creates an array
- # of scopes corresponding to the chain.
- def scope_chain
- @scope_chain ||= begin
- scope_chain = source_reflection.scope_chain.map(&:dup)
-
- # Add to it the scope from this reflection (if any)
- scope_chain.first << scope if scope
-
- through_scope_chain = through_reflection.scope_chain.map(&:dup)
-
- if options[:source_type]
- type = foreign_type
- source_type = options[:source_type]
- through_scope_chain.first << lambda { |object|
- where(type => source_type)
- }
- end
+ def scopes
+ source_reflection.scopes + super
+ end
- # Recursively fill out the rest of the array from the through reflection
- scope_chain + through_scope_chain
- end
+ def source_type_scope
+ through_reflection.klass.where(foreign_type => options[:source_type])
end
def has_scope?
@@ -851,7 +835,7 @@ module ActiveRecord
end
def association_primary_key_type
- klass.type_for_attribute(association_primary_key)
+ 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.
@@ -931,6 +915,14 @@ module ActiveRecord
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
end
+ if parent_reflection.nil?
+ reflections = active_record.reflections.keys.map(&:to_sym)
+
+ if reflections.index(through_reflection.name) > reflections.index(name)
+ raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
+ end
+ end
+
check_validity_of_inverse!
end
@@ -961,8 +953,7 @@ module ActiveRecord
end
end
- protected
-
+ private
def actual_source_reflection # FIXME: this is a horrible name
source_reflection.send(:actual_source_reflection)
end
@@ -973,7 +964,6 @@ module ActiveRecord
def inverse_name; delegate_reflection.send(:inverse_name); end
- private
def derive_class_name
# get the class_name of the belongs_to association of the through reflection
options[:source_type] || source_reflection.class_name
@@ -991,6 +981,15 @@ module ActiveRecord
@previous_reflection = previous_reflection
end
+ def scopes
+ scopes = @previous_reflection.scopes
+ if @previous_reflection.options[:source_type]
+ scopes + [@previous_reflection.source_type_scope]
+ else
+ scopes
+ end
+ end
+
def klass
@reflection.klass
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index ccd75ec5d2..61ee09bcc8 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -418,8 +418,7 @@ module ActiveRecord
records.each { |record| record.update(attributes) }
else
if ActiveRecord::Base === id
- id = id.id
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ raise ArgumentError, <<-MSG.squish
You are passing an instance of ActiveRecord::Base to `update`.
Please pass the id of the object by calling `.id`.
MSG
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 4b2987ac6d..76031515fd 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -260,7 +260,7 @@ module ActiveRecord
end
def act_on_ignored_order(error_on_ignore)
- raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order : error_on_ignore)
+ raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
if raise_error
raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 827688a663..35c670f1a1 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -193,7 +193,7 @@ module ActiveRecord
# If #count is used with #distinct (i.e. `relation.distinct.count`) it is
# considered distinct.
- distinct = self.distinct_value
+ distinct = distinct_value
if operation == "count"
column_name ||= select_for_count
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 3c1dea8c6c..d3ba724507 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -38,6 +38,7 @@ module ActiveRecord
delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
:[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
+ :to_sentence, :to_formatted_s,
:shuffle, :split, :index, to: :records
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
@@ -113,7 +114,7 @@ module ActiveRecord
arel.respond_to?(method, include_private)
end
- protected
+ private
def method_missing(method, *args, &block)
if @klass.respond_to?(method)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 6663bdb244..4548944fe6 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -17,8 +17,8 @@ module ActiveRecord
# 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'd need to provide an explicit QueryMethods#order
- # option if you want the results are sorted.
+ # provide since database rows are unordered. You will need to provide an explicit QueryMethods#order
+ # option if you want the results to be sorted.
#
# ==== Find with lock
#
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 78c046b07f..4ee413c805 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -3,7 +3,6 @@ require "active_record/relation/query_attribute"
require "active_record/relation/where_clause"
require "active_record/relation/where_clause_factory"
require "active_model/forbidden_attributes_protection"
-require "active_support/core_ext/string/filters"
module ActiveRecord
module QueryMethods
@@ -657,7 +656,7 @@ module ActiveRecord
end
self.where_clause = self.where_clause.or(other.where_clause)
- self.having_clause = self.having_clause.or(other.having_clause)
+ self.having_clause = having_clause.or(other.having_clause)
self
end
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index ef0d059d1c..417b24c7bb 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -25,10 +25,7 @@ module ActiveRecord
end
def except(*columns)
- WhereClause.new(
- predicates_except(columns),
- binds_except(columns),
- )
+ WhereClause.new(*except_predicates_and_binds(columns))
end
def or(other)
@@ -134,20 +131,35 @@ module ActiveRecord
end
end
- def predicates_except(columns)
- 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)
- columns.include?(subrelation.name.to_s)
+ def except_predicates_and_binds(columns)
+ except_binds = []
+ binds_index = 0
+
+ predicates = self.predicates.reject do |node|
+ except = \
+ 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
+ binds_contains = node.grep(Arel::Nodes::BindParam).size
+ subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right)
+ columns.include?(subrelation.name.to_s)
+ end
+
+ if except && binds_contains > 0
+ (binds_index...(binds_index + binds_contains)).each do |i|
+ except_binds[i] = true
+ end
+
+ binds_index += binds_contains
end
+
+ except
end
- end
- def binds_except(columns)
- binds.reject do |attr|
- columns.include?(attr.name)
+ binds = self.binds.reject.with_index do |_, i|
+ except_binds[i]
end
+
+ [predicates, binds]
end
def predicates_with_wrapped_sql_literals
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index 737bc278bd..04bee73e8f 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -15,9 +15,12 @@ module ActiveRecord
attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
attributes.stringify_keys!
- attributes, binds = predicate_builder.create_binds(attributes)
-
- parts = predicate_builder.build_from_hash(attributes)
+ if perform_case_sensitive?(options = other.last)
+ parts, binds = build_for_case_sensitive(attributes, options)
+ else
+ attributes, binds = predicate_builder.create_binds(attributes)
+ parts = predicate_builder.build_from_hash(attributes)
+ end
when Arel::Nodes::Node
parts = [opts]
else
@@ -32,6 +35,43 @@ module ActiveRecord
protected
attr_reader :klass, :predicate_builder
+
+ private
+
+ def perform_case_sensitive?(options)
+ options && options.key?(:case_sensitive)
+ end
+
+ def build_for_case_sensitive(attributes, options)
+ parts, binds = [], []
+ table = klass.arel_table
+
+ attributes.each do |attribute, value|
+ if reflection = klass._reflect_on_association(attribute)
+ attribute = reflection.foreign_key.to_s
+ value = value[reflection.klass.primary_key] unless value.nil?
+ end
+
+ if value.nil?
+ parts << table[attribute].eq(value)
+ else
+ column = klass.column_for_attribute(attribute)
+
+ binds << predicate_builder.send(:build_bind_param, attribute, value)
+ value = Arel::Nodes::BindParam.new
+
+ predicate = if options[:case_sensitive]
+ klass.connection.case_sensitive_comparison(table, attribute, column, value)
+ else
+ klass.connection.case_insensitive_comparison(table, attribute, column, value)
+ end
+
+ parts << predicate
+ end
+ end
+
+ [parts, binds]
+ end
end
end
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 647834b12e..427c0019c6 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -46,7 +46,7 @@ module ActiveRecord
#
# 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 = self.table_name) # :doc:
+ 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)
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index 7a2bc9c8af..5104427339 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -48,7 +48,7 @@ module ActiveRecord
instance_eval(&block)
if info[:version].present?
- initialize_schema_migrations_table
+ ActiveRecord::SchemaMigration.create_table
connection.assume_migrated_upto_version(info[:version], migrations_paths)
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 12289511b7..15533f0151 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -188,7 +188,7 @@ HEADER
index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present?
index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present?
index_parts << "where: #{index.where.inspect}" if index.where
- index_parts << "using: #{index.using.inspect}" if index.using
+ index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index)
index_parts << "type: #{index.type.inspect}" if index.type
index_parts << "comment: #{index.comment.inspect}" if index.comment
index_parts
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
index 7606961e2e..115799cc20 100644
--- a/activerecord/lib/active_record/secure_token.rb
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -27,7 +27,7 @@ module ActiveRecord
# Load securerandom only when has_secure_token is used.
require "active_support/core_ext/securerandom"
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token }
- before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?") }
+ before_create { send("#{attribute}=", self.class.generate_unique_secure_token) unless send("#{attribute}?") }
end
def generate_unique_secure_token
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 5a408e7b8e..db2bd0b55e 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -9,7 +9,7 @@ module ActiveRecord #:nodoc:
end
def serializable_hash(options = nil)
- options = options.try(:clone) || {}
+ options = options.try(:dup) || {}
options[:except] = Array(options[:except]).map(&:to_s)
options[:except] |= Array(self.class.inheritance_column)
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index d4be20d999..006afe7495 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -78,7 +78,7 @@ module ActiveRecord
module ClassMethods
def store(store_attribute, options = {})
- serialize store_attribute, IndifferentCoder.new(options[:coder])
+ serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
end
@@ -177,12 +177,12 @@ module ActiveRecord
end
class IndifferentCoder # :nodoc:
- def initialize(coder_or_class_name)
+ def initialize(attr_name, coder_or_class_name)
@coder =
if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)
coder_or_class_name
else
- ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object)
+ ActiveRecord::Coders::YAMLColumn.new(attr_name, coder_or_class_name || Object)
end
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index b618e5cfcd..71efc1829a 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -44,7 +44,7 @@ module ActiveRecord
end
def associated_table(table_name)
- association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize)
+ association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.to_s.singularize)
if !association && table_name == arel_table.name
return self
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 1423e6008f..82604a915f 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -1,5 +1,3 @@
-require "active_support/core_ext/string/filters"
-
module ActiveRecord
module Tasks # :nodoc:
class DatabaseAlreadyExists < StandardError; end # :nodoc:
@@ -269,8 +267,8 @@ module ActiveRecord
if seed_loader
seed_loader.load_seed
else
- raise "You tried to load seed data, but no seed loader is specified. Please specify seed " +
- "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" +
+ raise "You tried to load seed data, but no seed loader is specified. Please specify seed " \
+ "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" \
"Seed loader should respond to load_seed method"
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 56b75540e3..08417aaa0f 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -11,7 +11,6 @@ module ActiveRecord
:before_commit_without_transaction_enrollment,
:commit_without_transaction_enrollment,
:rollback_without_transaction_enrollment,
- terminator: deprecated_false_terminator,
scope: [:kind, :name]
end
diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb
index ac9134bfcb..6af05c1860 100644
--- a/activerecord/lib/active_record/type/serialized.rb
+++ b/activerecord/lib/active_record/type/serialized.rb
@@ -43,7 +43,7 @@ module ActiveRecord
def assert_valid_value(value)
if coder.respond_to?(:assert_valid_value)
- coder.assert_valid_value(value)
+ coder.assert_valid_value(value, action: "serialize")
end
end
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index ca5eda2f84..7cfd55f516 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -57,7 +57,7 @@ module ActiveRecord
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
# proc or string should return or evaluate to a +true+ or +false+ value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See ActiveModel::Validation#validates! for more information.
+ # See ActiveModel::Validations#validates! for more information.
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 9e8edfbfaf..154cf5f1a4 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -50,37 +50,7 @@ module ActiveRecord
end
def build_relation(klass, attribute, value)
- if reflection = klass._reflect_on_association(attribute)
- attribute = reflection.foreign_key
- value = value.attributes[reflection.klass.primary_key] unless value.nil?
- end
-
- if value.nil?
- return klass.unscoped.where!(attribute => value)
- end
-
- # the attribute may be an aliased attribute
- if klass.attribute_alias?(attribute)
- attribute = klass.attribute_alias(attribute)
- end
-
- attribute_name = attribute.to_s
-
- table = klass.arel_table
- column = klass.columns_hash[attribute_name]
- cast_type = klass.type_for_attribute(attribute_name)
-
- comparison = if !options[:case_sensitive]
- # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
- klass.connection.case_insensitive_comparison(table, attribute, column, value)
- else
- klass.connection.case_sensitive_comparison(table, attribute, column, value)
- end
- klass.unscoped.tap do |scope|
- parts = [comparison]
- binds = [Relation::QueryAttribute.new(attribute_name, value, cast_type)]
- scope.where_clause += Relation::WhereClause.new(parts, binds)
- end
+ klass.unscoped.where!({ attribute => value }, options)
end
def scope_relation(record, relation)
diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb
index 4263c11ffc..43075077b9 100644
--- a/activerecord/lib/rails/generators/active_record/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration.rb
@@ -20,6 +20,14 @@ module ActiveRecord
key_type = options[:primary_key_type]
", id: :#{key_type}" if key_type
end
+
+ def db_migrate_path
+ if defined?(Rails) && Rails.application
+ Rails.application.config.paths["db/migrate"].to_ary.first
+ else
+ "db/migrate"
+ end
+ end
end
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index 8511531af7..1f1c47499b 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -10,7 +10,7 @@ module ActiveRecord
def create_migration_file
set_local_assigns!
validate_file_name!
- migration_template @migration_template, "db/migrate/#{file_name}.rb"
+ migration_template @migration_template, File.join(db_migrate_path, "#{file_name}.rb")
end
# TODO Change this to private once we've dropped Ruby 2.2 support.
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 61a8d3c100..5cec07d2e3 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -17,7 +17,7 @@ module ActiveRecord
def create_migration_file
return unless options[:migration] && options[:parent].nil?
attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
- migration_template "../../migration/templates/create_table_migration.rb", "db/migrate/create_#{table_name}.rb"
+ migration_template "../../migration/templates/create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb")
end
def create_model_file
@@ -41,7 +41,7 @@ module ActiveRecord
# FIXME: Change this file to a symlink once RubyGems 2.5.0 is required.
def generate_application_record
- if self.behavior == :invoke && !application_record_exist?
+ if behavior == :invoke && !application_record_exist?
template "application_record.rb", application_record_file_name
end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 9828e682ef..0c9d1dff9d 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -31,7 +31,6 @@ module ActiveRecord
end
def test_tables
- tables = nil
tables = @connection.tables
assert_includes tables, "accounts"
assert_includes tables, "authors"
@@ -185,34 +184,6 @@ module ActiveRecord
end
unless current_adapter?(:SQLite3Adapter)
- def test_foreign_key_violations_are_translated_to_specific_exception
- error = assert_raises(ActiveRecord::InvalidForeignKey) do
- # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
- if @connection.prefetch_primary_key?
- id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
- @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
- else
- @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
- end
- end
-
- assert_not_nil error.cause
- end
-
- def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
- klass_has_fk = Class.new(ActiveRecord::Base) do
- self.table_name = "fk_test_has_fk"
- end
-
- error = assert_raises(ActiveRecord::InvalidForeignKey) do
- has_fk = klass_has_fk.new
- has_fk.fk_id = 1231231231
- has_fk.save(validate: false)
- end
-
- assert_not_nil error.cause
- end
-
def test_value_limit_violations_are_translated_to_specific_exception
error = assert_raises(ActiveRecord::ValueTooLong) do
Event.create(title: "abcdefgh")
@@ -223,30 +194,13 @@ module ActiveRecord
def test_numeric_value_out_of_ranges_are_translated_to_specific_exception
error = assert_raises(ActiveRecord::RangeError) do
- Book.connection.create("INSERT INTO books(author_id) VALUES (2147483648)")
+ Book.connection.create("INSERT INTO books(author_id) VALUES (9223372036854775808)")
end
assert_not_nil error.cause
end
end
- def test_disable_referential_integrity
- assert_nothing_raised do
- @connection.disable_referential_integrity do
- # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
- if @connection.prefetch_primary_key?
- id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
- @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
- else
- @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
- end
- # should delete created record as otherwise disable_referential_integrity will try to enable constraints after executed block
- # and will fail (at least on Oracle)
- @connection.execute "DELETE FROM fk_test_has_fk"
- end
- end
- end
-
def test_select_all_always_return_activerecord_result
result = @connection.select_all "SELECT * FROM posts"
assert result.is_a?(ActiveRecord::Result)
@@ -290,6 +244,59 @@ module ActiveRecord
end
end
+ class AdapterForeignKeyTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false
+ klass_has_fk = Class.new(ActiveRecord::Base) do
+ self.table_name = "fk_test_has_fk"
+ end
+
+ error = assert_raises(ActiveRecord::InvalidForeignKey) do
+ has_fk = klass_has_fk.new
+ has_fk.fk_id = 1231231231
+ has_fk.save(validate: false)
+ end
+
+ assert_not_nil error.cause
+ end
+
+ def test_foreign_key_violations_are_translated_to_specific_exception
+ error = assert_raises(ActiveRecord::InvalidForeignKey) do
+ insert_into_fk_test_has_fk
+ end
+
+ assert_not_nil error.cause
+ end
+
+ def test_disable_referential_integrity
+ assert_nothing_raised do
+ @connection.disable_referential_integrity do
+ insert_into_fk_test_has_fk
+ # should delete created record as otherwise disable_referential_integrity will try to enable constraints
+ # after executed block and will fail (at least on Oracle)
+ @connection.execute "DELETE FROM fk_test_has_fk"
+ end
+ end
+ end
+
+ private
+
+ def insert_into_fk_test_has_fk
+ # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
+ if @connection.prefetch_primary_key?
+ id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
+ @connection.execute "INSERT INTO fk_test_has_fk (id,fk_id) VALUES (#{id_value},0)"
+ else
+ @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ end
+ end
+ end
+
class AdapterTestWithoutTransaction < ActiveRecord::TestCase
self.use_transactional_tests = false
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
index 2a528b2cb1..67e1efde27 100644
--- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -92,8 +92,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase
assert_equal expected, actual
end
- expected = "ALTER TABLE `peaple` ADD INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
- actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t|
+ expected = "ALTER TABLE `people` ADD INDEX `index_people_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY"
+ actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|
t.index :last_name, length: 10, using: :btree, algorithm: :copy
end
assert_equal expected, actual
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index c1de2218e2..1f94472390 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -66,9 +66,10 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
def test_execute_after_disconnect
@connection.disconnect!
- assert_raise(ActiveRecord::StatementInvalid) do
+ error = assert_raise(ActiveRecord::StatementInvalid) do
@connection.execute("SELECT 1")
end
+ assert_kind_of Mysql2::Error, error.cause
end
def test_quote_after_disconnect
@@ -119,7 +120,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase
end
end
- def test_passing_arbitary_flags_to_adapter
+ def test_passing_arbitrary_flags_to_adapter
run_without_connection do |orig_connection|
ActiveRecord::Base.establish_connection(orig_connection.merge(flags: Mysql2::Client::COMPRESS))
assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags]
diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
index 135789a57d..c131a5169c 100644
--- a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb
@@ -6,23 +6,27 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
end
test "microsecond precision for MySQL gte 5.6.4" do
- stub_version "5.6.4"
- assert_microsecond_precision
+ stub_version "5.6.4" do
+ assert_microsecond_precision
+ end
end
test "no microsecond precision for MySQL lt 5.6.4" do
- stub_version "5.6.3"
- assert_no_microsecond_precision
+ stub_version "5.6.3" do
+ assert_no_microsecond_precision
+ end
end
test "microsecond precision for MariaDB gte 5.3.0" do
- stub_version "5.5.5-10.1.8-MariaDB-log"
- assert_microsecond_precision
+ stub_version "5.5.5-10.1.8-MariaDB-log" do
+ assert_microsecond_precision
+ end
end
test "no microsecond precision for MariaDB lt 5.3.0" do
- stub_version "5.2.9-MariaDB"
- assert_no_microsecond_precision
+ stub_version "5.2.9-MariaDB" do
+ assert_no_microsecond_precision
+ end
end
private
@@ -41,5 +45,8 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase
def stub_version(full_version_string)
@connection.stubs(:full_version).returns(full_version_string)
@connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
+ yield
+ ensure
+ @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb b/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb
deleted file mode 100644
index 5d3125c2be..0000000000
--- a/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require "cases/helper"
-
-class MysqlLegacyMigrationTest < ActiveRecord::Mysql2TestCase
- self.use_transactional_tests = false
-
- class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0]
- def change
- create_table :legacy_integer_pk do |table|
- table.string :foo
- end
-
- create_table :override_pk, id: :bigint do |table|
- table.string :bar
- end
- end
- end
-
- def setup
- super
- @connection = ActiveRecord::Base.connection
-
- @migration_verbose_old = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migrations = [GenerateTableWithoutBigint.new(nil, 1)]
-
- ActiveRecord::Migrator.new(:up, migrations).migrate
- end
-
- def teardown
- ActiveRecord::Migration.verbose = @migration_verbose_old
- @connection.drop_table("legacy_integer_pk")
- @connection.drop_table("override_pk")
- ActiveRecord::SchemaMigration.delete_all rescue nil
- super
- end
-
- def test_create_table_uses_integer_as_pkey_by_default
- col = column(:legacy_integer_pk, :id)
- assert_equal "int(11)", sql_type_for(col)
- assert col.auto_increment?
- end
-
- def test_create_tables_respects_pk_column_type_override
- col = column(:override_pk, :id)
- assert_equal "bigint(20)", sql_type_for(col)
- end
-
- private
-
- def column(table_name, column_name)
- ActiveRecord::Base.connection
- .columns(table_name.to_s)
- .detect { |c| c.name == column_name.to_s }
- end
-
- def sql_type_for(col)
- col && col.sql_type
- 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 fa54aac6b3..605baa9905 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb
@@ -16,7 +16,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
table_name = ActiveRecord::SchemaMigration.table_name
connection.drop_table table_name, if_exists: true
- connection.initialize_schema_migrations_table
+ ActiveRecord::SchemaMigration.create_table
assert connection.column_exists?(table_name, :version, :string, collation: "utf8_general_ci")
end
@@ -27,7 +27,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase
table_name = ActiveRecord::InternalMetadata.table_name
connection.drop_table table_name, if_exists: true
- connection.initialize_internal_metadata_table
+ ActiveRecord::InternalMetadata.create_table
assert connection.column_exists?(table_name, :key, :string, collation: "utf8_general_ci")
end
diff --git a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
index bee42d48f1..d6e7f29a5c 100644
--- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb
@@ -8,7 +8,7 @@ class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase
assert_equal "blob", type_to_sql(:binary)
end
- def type_to_sql(*args)
- ActiveRecord::Base.connection.type_to_sql(*args)
+ def type_to_sql(type, limit = nil)
+ ActiveRecord::Base.connection.type_to_sql(type, limit: limit)
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
new file mode 100644
index 0000000000..442a4fb7b5
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb
@@ -0,0 +1,59 @@
+require "cases/helper"
+require "support/schema_dumping_helper"
+
+if ActiveRecord::Base.connection.supports_virtual_columns?
+ class Mysql2VirtualColumnTest < ActiveRecord::Mysql2TestCase
+ include SchemaDumpingHelper
+
+ self.use_transactional_tests = false
+
+ class VirtualColumn < ActiveRecord::Base
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table :virtual_columns, force: true do |t|
+ t.string :name
+ t.virtual :upper_name, type: :string, as: "UPPER(`name`)"
+ t.virtual :name_length, type: :integer, as: "LENGTH(`name`)", stored: true
+ end
+ VirtualColumn.create(name: "Rails")
+ end
+
+ def teardown
+ @connection.drop_table :virtual_columns, if_exists: true
+ VirtualColumn.reset_column_information
+ end
+
+ def test_virtual_column
+ column = VirtualColumn.columns_hash["upper_name"]
+ assert_predicate column, :virtual?
+ assert_match %r{\bVIRTUAL\b}, column.extra
+ assert_equal "RAILS", VirtualColumn.take.upper_name
+ end
+
+ def test_stored_column
+ column = VirtualColumn.columns_hash["name_length"]
+ assert_predicate column, :virtual?
+ assert_match %r{\b(?:STORED|PERSISTENT)\b}, column.extra
+ assert_equal 5, VirtualColumn.take.name_length
+ end
+
+ def test_change_table
+ @connection.change_table :virtual_columns do |t|
+ t.virtual :lower_name, type: :string, as: "LOWER(name)"
+ end
+ VirtualColumn.reset_column_information
+ column = VirtualColumn.columns_hash["lower_name"]
+ assert_predicate column, :virtual?
+ assert_match %r{\bVIRTUAL\b}, column.extra
+ assert_equal "rails", VirtualColumn.take.lower_name
+ end
+
+ def test_schema_dumping
+ output = dump_table_schema("virtual_columns")
+ assert_match(/t\.virtual\s+"upper_name",\s+type: :string,\s+as: "UPPER\(`name`\)"$/i, output)
+ assert_match(/t\.virtual\s+"name_length",\s+type: :integer,\s+as: "LENGTH\(`name`\)",\s+stored: true$/i, output)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
index 5c207116c4..505c297cd4 100644
--- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb
@@ -32,9 +32,9 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase
end
def test_binary_columns_are_limitless_the_upper_limit_is_one_GB
- assert_equal "bytea", @connection.type_to_sql(:binary, 100_000)
+ assert_equal "bytea", @connection.type_to_sql(:binary, limit: 100_000)
assert_raise ActiveRecord::ActiveRecordError do
- @connection.type_to_sql :binary, 4294967295
+ @connection.type_to_sql(:binary, limit: 4294967295)
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index e916d15f7f..3cbd4ca212 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -95,7 +95,7 @@ module ActiveRecord
end
def test_indexes_logs_name
- @connection.indexes("items", "hello")
+ assert_deprecated { @connection.indexes("items", "hello") }
assert_equal "SCHEMA", @subscriber.logged[0][1]
end
@@ -156,7 +156,7 @@ module ActiveRecord
secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})")
ActiveRecord::Base.connection_pool.checkin(secondary_connection)
elsif ARTest.config["with_manual_interventions"]
- puts "Kill the connection now (e.g. by restarting the PostgreSQL " +
+ puts "Kill the connection now (e.g. by restarting the PostgreSQL " \
'server with the "-m fast" option) and then press enter.'
$stdin.gets
else
@@ -175,7 +175,7 @@ module ActiveRecord
new_connection_pid = @connection.query("select pg_backend_pid()")
assert_not_equal original_connection_pid, new_connection_pid,
- "umm -- looks like you didn't break the connection, because we're still " +
+ "umm -- looks like you didn't break the connection, because we're still " \
"successfully querying with the same connection pid."
# Repair all fixture connections so other tests won't break.
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 0ac8b7339b..0725fde5ae 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -28,12 +28,12 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase
end
def test_data_type_of_time_types
- assert_equal :string, @first_time.column_for_attribute(:time_interval).type
- assert_equal :string, @first_time.column_for_attribute(:scaled_time_interval).type
+ assert_equal :interval, @first_time.column_for_attribute(:time_interval).type
+ assert_equal :interval, @first_time.column_for_attribute(:scaled_time_interval).type
end
def test_data_type_of_oid_types
- assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
+ assert_equal :oid, @first_oid.column_for_attribute(:obj_id).type
end
def test_time_values
@@ -61,9 +61,9 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase
end
def test_text_columns_are_limitless_the_upper_limit_is_one_GB
- assert_equal "text", @connection.type_to_sql(:text, 100_000)
+ assert_equal "text", @connection.type_to_sql(:text, limit: 100_000)
assert_raise ActiveRecord::ActiveRecordError do
- @connection.type_to_sql :text, 4294967295
+ @connection.type_to_sql(:text, limit: 4294967295)
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
index 19b00258b6..b9e177e6ec 100644
--- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb
@@ -30,7 +30,7 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase
record = PostgresqlInfinity.new(float: "-Infinity")
assert_equal(-Float::INFINITY, record.float)
record = PostgresqlInfinity.new(float: "NaN")
- assert_send [record.float, :nan?]
+ assert record.float.nan?, "Expected #{record.float} to be NaN"
end
test "update_all with infinity on a float column" do
diff --git a/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb b/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb
deleted file mode 100644
index 082fe95053..0000000000
--- a/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require "cases/helper"
-
-class PostgresqlLegacyMigrationTest < ActiveRecord::PostgreSQLTestCase
- class GenerateTableWithoutBigserial < ActiveRecord::Migration[5.0]
- def change
- create_table :legacy_integer_pk do |table|
- table.string :foo
- end
-
- create_table :override_pk, id: :bigint do |table|
- table.string :bar
- end
- end
- end
-
- def setup
- super
-
- @migration_verbose_old = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migrations = [GenerateTableWithoutBigserial.new(nil, 1)]
- ActiveRecord::Migrator.new(:up, migrations).migrate
- end
-
- def teardown
- ActiveRecord::Migration.verbose = @migration_verbose_old
-
- super
- end
-
- def test_create_table_uses_serial_as_pkey_by_default
- col = column(:legacy_integer_pk, :id)
- assert_equal "integer", sql_type_for(col)
- assert col.serial?
- end
-
- def test_create_tables_respects_pk_column_type_override
- col = column(:override_pk, :id)
- assert_equal "bigint", sql_type_for(col)
- end
-
- private
-
- def column(table_name, column_name)
- ActiveRecord::Base.connection.
- columns(table_name.to_s).
- detect { |c| c.name == column_name.to_s }
- end
-
- def sql_type_for(col)
- col && col.sql_type
- end
-end
diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
index 834354dcc9..bfb2b7c27a 100644
--- a/activerecord/test/cases/adapters/postgresql/numbers_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb
@@ -31,7 +31,7 @@ class PostgresqlNumberTest < ActiveRecord::PostgreSQLTestCase
assert_equal 123456.789, first.double
assert_equal(-::Float::INFINITY, second.single)
assert_equal ::Float::INFINITY, second.double
- assert_send [third.double, :nan?]
+ assert third.double.nan?, "Expected #{third.double} to be NaN"
end
def test_update
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index e6af93a53e..3054f0271f 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -54,12 +54,6 @@ module ActiveRecord
end
end
- def test_primary_key_raises_error_if_table_not_found
- assert_raises(ActiveRecord::StatementInvalid) do
- @connection.primary_key("unobtainium")
- end
- end
-
def test_exec_insert_with_returning_disabled
connection = connection_without_insert_returning
result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], "id", "postgresql_partitioned_table_parent_id_seq")
@@ -263,9 +257,12 @@ module ActiveRecord
def test_index_with_opclass
with_example_table do
- @connection.add_index "ex", "data varchar_pattern_ops", name: "with_opclass"
- index = @connection.indexes("ex").find { |idx| idx.name == "with_opclass" }
+ @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.remove_index "ex", "data varchar_pattern_ops"
+ assert_not @connection.indexes("ex").find { |idx| idx.name == "index_ex_on_data_varchar_pattern_ops" }
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 237e9ff6a5..7b065ff320 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -301,13 +301,13 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
def test_index_name_exists
with_schema_search_path(SCHEMA_NAME) do
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME, true)
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME, true)
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME, true)
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME, true)
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true)
- assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true)
- assert_not @connection.index_name_exists?(TABLE_NAME, "missing_index", true)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME)
+ assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME)
+ assert_not @connection.index_name_exists?(TABLE_NAME, "missing_index")
end
end
@@ -366,14 +366,6 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase
end
end
- def test_primary_key_raises_error_if_table_not_found_on_schema_search_path
- with_schema_search_path(SCHEMA2_NAME) do
- assert_raises(ActiveRecord::StatementInvalid) do
- @connection.primary_key(PK_TABLE_NAME)
- end
- end
- end
-
def test_pk_and_sequence_for_with_schema_specified
pg_name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
[
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index f34d50e25c..6aa6a79705 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -234,25 +234,23 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
end
end
- if ActiveRecord::Base.connection.supports_pgcrypto_uuid?
- def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
- @verbose_was = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migration = Class.new(ActiveRecord::Migration[4.2]) do
- def version; 101 end
- def migrate(x)
- create_table("pg_uuids_4", id: :uuid)
- end
- end.new
- ActiveRecord::Migrator.new(:up, [migration]).migrate
-
- schema = dump_table_schema "pg_uuids_4"
- assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
- ensure
- drop_table "pg_uuids_4"
- ActiveRecord::Migration.verbose = @verbose_was
- end
+ def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ migration = Class.new(ActiveRecord::Migration[5.0]) do
+ def version; 101 end
+ def migrate(x)
+ create_table("pg_uuids_4", id: :uuid)
+ end
+ end.new
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ schema = dump_table_schema "pg_uuids_4"
+ assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema)
+ ensure
+ drop_table "pg_uuids_4"
+ ActiveRecord::Migration.verbose = @verbose_was
end
end
end
@@ -285,6 +283,25 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase
schema = dump_table_schema "pg_uuids"
assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema)
end
+
+ def test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+
+ migration = Class.new(ActiveRecord::Migration[5.0]) do
+ def version; 101 end
+ def migrate(x)
+ create_table("pg_uuids_4", id: :uuid, default: nil)
+ end
+ end.new
+ ActiveRecord::Migrator.new(:up, [migration]).migrate
+
+ schema = dump_table_schema "pg_uuids_4"
+ assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: nil/, schema)
+ ensure
+ drop_table "pg_uuids_4"
+ ActiveRecord::Migration.verbose = @verbose_was
+ end
end
end
@@ -330,7 +347,6 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase
assert_raise ActiveRecord::RecordNotFound do
UuidPost.find(123456)
end
-
end
def test_find_by_with_uuid
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index 91967c1e33..e1cfd703e8 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -41,8 +41,8 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase
test_copy_table("comments", "comments_with_index") do
@connection.add_index("comments_with_index", ["post_id", "type"])
test_copy_table("comments_with_index", "comments_with_index2") do
- assert_equal table_indexes_without_name("comments_with_index"),
- table_indexes_without_name("comments_with_index2")
+ assert_nil table_indexes_without_name("comments_with_index")
+ assert_nil table_indexes_without_name("comments_with_index2")
end
end
end
@@ -59,7 +59,8 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase
copied_id = @connection.columns("goofy_string_id2").detect { |col| col.name == "id" }
assert_equal original_id.type, copied_id.type
assert_equal original_id.sql_type, copied_id.sql_type
- assert_equal original_id.limit, copied_id.limit
+ assert_nil original_id.limit
+ assert_nil copied_id.limit
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb b/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb
deleted file mode 100644
index fcca8d66b5..0000000000
--- a/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require "cases/helper"
-
-class SqliteLegacyMigrationTest < ActiveRecord::SQLite3TestCase
- self.use_transactional_tests = false
-
- class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0]
- def change
- create_table :legacy_integer_pk do |table|
- table.string :foo
- end
-
- create_table :override_pk, id: :bigint do |table|
- table.string :bar
- end
- end
- end
-
- def setup
- super
- @connection = ActiveRecord::Base.connection
-
- @migration_verbose_old = ActiveRecord::Migration.verbose
- ActiveRecord::Migration.verbose = false
-
- migrations = [GenerateTableWithoutBigint.new(nil, 1)]
-
- ActiveRecord::Migrator.new(:up, migrations).migrate
- end
-
- def teardown
- ActiveRecord::Migration.verbose = @migration_verbose_old
- @connection.drop_table("legacy_integer_pk")
- @connection.drop_table("override_pk")
- ActiveRecord::SchemaMigration.delete_all rescue nil
- super
- end
-
- def test_create_table_uses_integer_as_pkey_by_default
- col = column(:legacy_integer_pk, :id)
- assert_equal "INTEGER", sql_type_for(col)
- assert primary_key?(:legacy_integer_pk, "id"), "id is not primary key"
- end
-
- private
-
- def column(table_name, column_name)
- ActiveRecord::Base.connection
- .columns(table_name.to_s)
- .detect { |c| c.name == column_name.to_s }
- end
-
- def sql_type_for(col)
- col && col.sql_type
- end
-
- def primary_key?(table_name, column)
- ActiveRecord::Base.connection.execute("PRAGMA table_info(#{table_name})").find { |col| col["name"] == column }["pk"] == 1
- end
-end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index a6109348cc..a6afb7816b 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -287,7 +287,7 @@ module ActiveRecord
def test_indexes_logs_name
with_example_table do
assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do
- @conn.indexes("ex", "hello")
+ assert_deprecated { @conn.indexes("ex", "hello") }
end
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
index aebcce3691..37ff973397 100644
--- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
@@ -3,7 +3,6 @@ require "cases/helper"
class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase
if Process.respond_to?(:fork)
def test_cache_is_per_pid
-
cache = ActiveRecord::ConnectionAdapters::SQLite3Adapter::StatementPool.new(10)
cache["foo"] = "bar"
assert_equal "bar", cache["foo"]
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index ff1bf8acd4..11f4aae5b3 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -739,18 +739,25 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_invalid_association_reference
- assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ e = assert_raise(ActiveRecord::AssociationNotFoundError) {
Post.all.merge!(includes: :monkeys).find(6)
}
- assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message)
+
+ e = assert_raise(ActiveRecord::AssociationNotFoundError) {
Post.all.merge!(includes: [ :monkeys ]).find(6)
}
- assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message)
+
+ e = assert_raise(ActiveRecord::AssociationNotFoundError) {
Post.all.merge!(includes: [ "monkeys" ]).find(6)
}
- assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
+ assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message)
+
+ e = assert_raise(ActiveRecord::AssociationNotFoundError) {
Post.all.merge!(includes: [ :monkeys, :elephants ]).find(6)
}
+ assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message)
end
def test_eager_has_many_through_with_order
@@ -933,7 +940,11 @@ class EagerAssociationTest < ActiveRecord::TestCase
d2 = find_all_ordered(Firm, :account)
d1.each_index do |i|
assert_equal(d1[i], d2[i])
- assert_equal(d1[i].account, d2[i].account)
+ if d1[i].account.nil?
+ assert_nil(d2[i].account)
+ else
+ assert_equal(d1[i].account, d2[i].account)
+ end
end
end
@@ -943,7 +954,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
d2 = find_all_ordered(Client, firm_types)
d1.each_index do |i|
assert_equal(d1[i], d2[i])
- firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) }
+ firm_types.each do |type|
+ if (expected = d1[i].send(type)).nil?
+ assert_nil(d2[i].send(type))
+ else
+ assert_equal(expected, d2[i].send(type))
+ end
+ end
end
end
def test_eager_with_valid_association_as_string_not_symbol
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 54fb61d6a5..d6b595d7e7 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
@@ -86,8 +86,10 @@ class DeveloperWithSymbolClassName < Developer
has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys
end
-class DeveloperWithConstantClassName < Developer
- has_and_belongs_to_many :projects, class_name: ProjectWithSymbolsForKeys
+ActiveSupport::Deprecation.silence do
+ class DeveloperWithConstantClassName < Developer
+ has_and_belongs_to_many :projects, class_name: ProjectWithSymbolsForKeys
+ end
end
class DeveloperWithExtendOption < Developer
@@ -743,8 +745,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_scoped_grouped_having
- assert_equal 2, projects(:active_record).well_payed_salary_groups.to_a.size
- assert projects(:active_record).well_payed_salary_groups.all? { |g| g.salary > 10000 }
+ assert_equal 2, projects(:active_record).well_paid_salary_groups.to_a.size
+ assert projects(:active_record).well_paid_salary_groups.all? { |g| g.salary > 10000 }
end
def test_get_ids
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index cbecfa84ff..ede3a44090 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -611,21 +611,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_update_all_on_association_accessed_before_save
firm = Firm.new(name: "Firm")
- clients_proxy_id = firm.clients.object_id
firm.clients << Client.first
firm.save!
assert_equal firm.clients.count, firm.clients.update_all(description: "Great!")
- assert_not_equal clients_proxy_id, firm.clients.object_id
end
def test_update_all_on_association_accessed_before_save_with_explicit_foreign_key
- # We can use the same cached proxy object because the id is available for the scope
firm = Firm.new(name: "Firm", id: 100)
- clients_proxy_id = firm.clients.object_id
firm.clients << Client.first
firm.save!
assert_equal firm.clients.count, firm.clients.update_all(description: "Great!")
- assert_equal clients_proxy_id, firm.clients.object_id
end
def test_belongs_to_sanity
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 47c6480a8e..ea52fb5a67 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -28,6 +28,9 @@ require "models/member"
require "models/membership"
require "models/club"
require "models/organization"
+require "models/user"
+require "models/family"
+require "models/family_tree"
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags,
@@ -880,7 +883,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
book.subscriber_ids = []
assert_equal [], book.subscribers.reload
end
-
end
def test_collection_singular_ids_setter_with_changed_primary_key
@@ -1231,4 +1233,23 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
ensure
TenantMembership.current_member = nil
end
+
+ def test_has_many_through_with_scope_should_respect_table_alias
+ family = Family.create!
+ users = 3.times.map { User.create! }
+ FamilyTree.create!(member: users[0], family: family)
+ FamilyTree.create!(member: users[1], family: family)
+ FamilyTree.create!(member: users[2], family: family, token: "wat")
+
+ assert_equal 2, users[0].family_members.to_a.size
+ assert_equal 0, users[2].family_members.to_a.size
+ end
+
+ def test_incorrectly_ordered_through_associations
+ assert_raises(ActiveRecord::HasManyThroughOrderError) do
+ DeveloperWithIncorrectlyOrderedHasManyThrough.create(
+ companies: [Company.create]
+ )
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index ed22a9802f..7c11d2e7fc 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -476,7 +476,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal ships(:black_pearl), pirate.ship
assert_equal pirate.id, pirate.ship.pirate_id
- assert_equal "Failed to remove the existing associated ship. " +
+ assert_equal "Failed to remove the existing associated ship. " \
"The record failed to save after its foreign key was set to nil.", error.message
end
@@ -648,6 +648,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
class SpecialBook < ActiveRecord::Base
self.table_name = "books"
belongs_to :author, class_name: "SpecialAuthor"
+ has_one :subscription, class_name: "SpecialSupscription", foreign_key: "subscriber_id"
end
class SpecialAuthor < ActiveRecord::Base
@@ -655,11 +656,27 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
has_one :book, class_name: "SpecialBook", foreign_key: "author_id"
end
- def test_assocation_enum_works_properly
+ class SpecialSupscription < ActiveRecord::Base
+ self.table_name = "subscriptions"
+ belongs_to :book, class_name: "SpecialBook"
+ end
+
+ def test_association_enum_works_properly
author = SpecialAuthor.create!(name: "Test")
book = SpecialBook.create!(status: "published")
author.book = book
refute_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count
end
+
+ def test_association_enum_works_properly_with_nested_join
+ author = SpecialAuthor.create!(name: "Test")
+ book = SpecialBook.create!(status: "published")
+ author.book = book
+
+ where_clause = { books: { subscriptions: { subscriber_id: nil } } }
+ assert_nothing_raised do
+ SpecialAuthor.joins(book: :subscription).where.not(where_clause)
+ end
+ end
end
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 432c3526a5..38a729d2d4 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -86,6 +86,13 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_nil @member.club
end
+ def test_set_record_after_delete_association
+ @member.club = nil
+ @member.club = clubs(:moustache_club)
+ @member.reload
+ assert_equal clubs(:moustache_club), @member.club
+ end
+
def test_has_one_through_polymorphic
assert_equal clubs(:moustache_club), @member.sponsor_club
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 6fe6ee6783..287b3e9ebc 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -443,7 +443,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert man.equal?(man.interests.first.man), "Two inverses should lead back to the same object that was originally held"
assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held"
- assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match before the name is changed"
+ assert_nil man.interests.find(interest.id).man.name, "The name of the man should match before the name is changed"
man.name = "Ben Bitdiddle"
assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the parent name is changed"
man.interests.find(interest.id).man.name = "Alyssa P. Hacker"
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index a223b4338f..26056f6f63 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -220,11 +220,6 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_equal david.projects, david.projects.scope
end
- test "proxy object is cached" do
- david = developers(:david)
- assert david.projects.equal?(david.projects)
- end
-
test "inverses get set of subsets of the association" do
man = Man.create
man.interests.create
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index 978dd93fa4..1fc63a49d4 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -3,7 +3,7 @@ require "cases/helper"
module ActiveRecord
module AttributeMethods
class ReadTest < ActiveRecord::TestCase
- class FakeColumn < Struct.new(:name)
+ FakeColumn = Struct.new(:name) do
def type; :integer; end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 3dc0c0ce53..4d24a980dc 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -866,6 +866,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase
assert subklass.method_defined?(:id), "subklass is missing id method"
end
+ test "define_attribute_method works with both symbol and string" do
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_nothing_raised { klass.define_attribute_method(:foo) }
+ assert_nothing_raised { klass.define_attribute_method("bar") }
+ end
+
test "read_attribute with nil should not asplode" do
assert_nil Topic.new.read_attribute(nil)
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 6d31b7a091..2203aa1788 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -37,7 +37,7 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
private
def should_be_cool
- unless self.first_name == "cool"
+ unless first_name == "cool"
errors.add :first_name, "not cool"
end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index a611cc208c..979a59f566 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -98,6 +98,13 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil Edge.primary_key
end
+ def test_primary_key_and_references_columns_should_be_identical_type
+ pk = Author.columns_hash["id"]
+ ref = Post.columns_hash["author_id"]
+
+ assert_equal pk.bigint?, ref.bigint?
+ end
+
def test_many_mutations
car = Car.new name: "<3<3<3"
car.engines_count = 0
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index ffd5c1395d..1813534b62 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -13,9 +13,9 @@ require "models/speedometer"
require "models/ship_part"
require "models/treasure"
require "models/developer"
+require "models/post"
require "models/comment"
require "models/rating"
-require "models/post"
class NumericData < ActiveRecord::Base
self.table_name = "numeric_data"
@@ -166,14 +166,14 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_limit_should_apply_before_count
- accounts = Account.limit(3).where("firm_id IS NOT NULL")
+ accounts = Account.limit(4)
assert_equal 3, accounts.count(:firm_id)
assert_equal 3, accounts.select(:firm_id).count
end
def test_limit_should_apply_before_count_arel_attribute
- accounts = Account.limit(3).where("firm_id IS NOT NULL")
+ accounts = Account.limit(4)
firm_id_attribute = Account.arel_table[:firm_id]
assert_equal 3, accounts.count(firm_id_attribute)
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 11ec6fb2c5..b3c86586d0 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -6,10 +6,6 @@ class CallbackDeveloper < ActiveRecord::Base
self.table_name = "developers"
class << self
- def callback_string(callback_method)
- "history << [#{callback_method.to_sym.inspect}, :string]"
- end
-
def callback_proc(callback_method)
Proc.new { |model| model.history << [callback_method, :proc] }
end
@@ -33,7 +29,6 @@ class CallbackDeveloper < ActiveRecord::Base
ActiveRecord::Callbacks::CALLBACKS.each do |callback_method|
next if callback_method.to_s.start_with?("around_")
define_callback_method(callback_method)
- ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method)) }
send(callback_method, callback_proc(callback_method))
send(callback_method, callback_object(callback_method))
send(callback_method) { |model| model.history << [callback_method, :block] }
@@ -44,11 +39,6 @@ class CallbackDeveloper < ActiveRecord::Base
end
end
-class CallbackDeveloperWithFalseValidation < CallbackDeveloper
- before_validation proc { |model| model.history << [:before_validation, :returning_false]; false }
- before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
-end
-
class CallbackDeveloperWithHaltedValidation < CallbackDeveloper
before_validation proc { |model| model.history << [:before_validation, :throwing_abort]; throw(:abort) }
before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
@@ -125,11 +115,11 @@ class ContextualCallbacksDeveloper < ActiveRecord::Base
after_validation :after_validation_on_create_and_update, on: [ :create, :update ]
def before_validation_on_create_and_update
- history << "before_validation_on_#{self.validation_context}".to_sym
+ history << "before_validation_on_#{validation_context}".to_sym
end
def after_validation_on_create_and_update
- history << "after_validation_on_#{self.validation_context}".to_sym
+ history << "after_validation_on_#{validation_context}".to_sym
end
def history
@@ -137,23 +127,6 @@ class ContextualCallbacksDeveloper < ActiveRecord::Base
end
end
-class CallbackCancellationDeveloper < ActiveRecord::Base
- self.table_name = "developers"
-
- attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called
- attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy
-
- before_save { defined?(@cancel_before_save) ? !@cancel_before_save : false }
- before_create { !@cancel_before_create }
- before_update { !@cancel_before_update }
- before_destroy { !@cancel_before_destroy }
-
- after_save { @after_save_called = true }
- after_update { @after_update_called = true }
- after_create { @after_create_called = true }
- after_destroy { @after_destroy_called = true }
-end
-
class CallbackHaltedDeveloper < ActiveRecord::Base
self.table_name = "developers"
@@ -178,7 +151,6 @@ class CallbacksTest < ActiveRecord::TestCase
david = CallbackDeveloper.new
assert_equal [
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
@@ -189,12 +161,10 @@ class CallbacksTest < ActiveRecord::TestCase
david = CallbackDeveloper.find(1)
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
@@ -206,17 +176,14 @@ class CallbacksTest < ActiveRecord::TestCase
david.valid?
assert_equal [
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_validation, :method ],
- [ :before_validation, :string ],
[ :before_validation, :proc ],
[ :before_validation, :object ],
[ :before_validation, :block ],
[ :after_validation, :method ],
- [ :after_validation, :string ],
[ :after_validation, :proc ],
[ :after_validation, :object ],
[ :after_validation, :block ],
@@ -228,22 +195,18 @@ class CallbacksTest < ActiveRecord::TestCase
david.valid?
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_validation, :method ],
- [ :before_validation, :string ],
[ :before_validation, :proc ],
[ :before_validation, :object ],
[ :before_validation, :block ],
[ :after_validation, :method ],
- [ :after_validation, :string ],
[ :after_validation, :proc ],
[ :after_validation, :object ],
[ :after_validation, :block ],
@@ -254,44 +217,36 @@ class CallbacksTest < ActiveRecord::TestCase
david = CallbackDeveloper.create("name" => "David", "salary" => 1000000)
assert_equal [
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_validation, :method ],
- [ :before_validation, :string ],
[ :before_validation, :proc ],
[ :before_validation, :object ],
[ :before_validation, :block ],
[ :after_validation, :method ],
- [ :after_validation, :string ],
[ :after_validation, :proc ],
[ :after_validation, :object ],
[ :after_validation, :block ],
[ :before_save, :method ],
- [ :before_save, :string ],
[ :before_save, :proc ],
[ :before_save, :object ],
[ :before_save, :block ],
[ :before_create, :method ],
- [ :before_create, :string ],
[ :before_create, :proc ],
[ :before_create, :object ],
[ :before_create, :block ],
[ :after_create, :method ],
- [ :after_create, :string ],
[ :after_create, :proc ],
[ :after_create, :object ],
[ :after_create, :block ],
[ :after_save, :method ],
- [ :after_save, :string ],
[ :after_save, :proc ],
[ :after_save, :object ],
[ :after_save, :block ],
[ :after_commit, :block ],
[ :after_commit, :object ],
[ :after_commit, :proc ],
- [ :after_commit, :string ],
[ :after_commit, :method ]
], david.history
end
@@ -323,49 +278,40 @@ class CallbacksTest < ActiveRecord::TestCase
david.save
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_validation, :method ],
- [ :before_validation, :string ],
[ :before_validation, :proc ],
[ :before_validation, :object ],
[ :before_validation, :block ],
[ :after_validation, :method ],
- [ :after_validation, :string ],
[ :after_validation, :proc ],
[ :after_validation, :object ],
[ :after_validation, :block ],
[ :before_save, :method ],
- [ :before_save, :string ],
[ :before_save, :proc ],
[ :before_save, :object ],
[ :before_save, :block ],
[ :before_update, :method ],
- [ :before_update, :string ],
[ :before_update, :proc ],
[ :before_update, :object ],
[ :before_update, :block ],
[ :after_update, :method ],
- [ :after_update, :string ],
[ :after_update, :proc ],
[ :after_update, :object ],
[ :after_update, :block ],
[ :after_save, :method ],
- [ :after_save, :string ],
[ :after_save, :proc ],
[ :after_save, :object ],
[ :after_save, :block ],
[ :after_commit, :block ],
[ :after_commit, :object ],
[ :after_commit, :proc ],
- [ :after_commit, :string ],
[ :after_commit, :method ]
], david.history
end
@@ -399,29 +345,24 @@ class CallbacksTest < ActiveRecord::TestCase
david.destroy
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_destroy, :method ],
- [ :before_destroy, :string ],
[ :before_destroy, :proc ],
[ :before_destroy, :object ],
[ :before_destroy, :block ],
[ :after_destroy, :method ],
- [ :after_destroy, :string ],
[ :after_destroy, :proc ],
[ :after_destroy, :object ],
[ :after_destroy, :block ],
[ :after_commit, :block ],
[ :after_commit, :object ],
[ :after_commit, :proc ],
- [ :after_commit, :string ],
[ :after_commit, :method ]
], david.history
end
@@ -431,82 +372,16 @@ class CallbacksTest < ActiveRecord::TestCase
CallbackDeveloper.delete(david.id)
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
], david.history
end
- def test_deprecated_before_save_returning_false
- david = ImmutableDeveloper.find(1)
- assert_deprecated do
- assert david.valid?
- assert !david.save
- exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! }
- assert_equal david, exc.record
- assert_equal "Failed to save the record", exc.message
- end
-
- david = ImmutableDeveloper.find(1)
- david.salary = 10_000_000
- assert !david.valid?
- assert !david.save
- assert_raise(ActiveRecord::RecordInvalid) { david.save! }
-
- someone = CallbackCancellationDeveloper.find(1)
- someone.cancel_before_save = true
- assert_deprecated do
- assert someone.valid?
- assert !someone.save
- end
- assert_save_callbacks_not_called(someone)
- end
-
- def test_deprecated_before_create_returning_false
- someone = CallbackCancellationDeveloper.new
- someone.cancel_before_create = true
- assert_deprecated do
- assert someone.valid?
- assert !someone.save
- end
- assert_save_callbacks_not_called(someone)
- end
-
- def test_deprecated_before_update_returning_false
- someone = CallbackCancellationDeveloper.find(1)
- someone.cancel_before_update = true
- assert_deprecated do
- assert someone.valid?
- assert !someone.save
- end
- assert_save_callbacks_not_called(someone)
- end
-
- def test_deprecated_before_destroy_returning_false
- david = ImmutableDeveloper.find(1)
- assert_deprecated do
- assert !david.destroy
- exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! }
- assert_equal david, exc.record
- assert_equal "Failed to destroy the record", exc.message
- end
- assert_not_nil ImmutableDeveloper.find_by_id(1)
-
- someone = CallbackCancellationDeveloper.find(1)
- someone.cancel_before_destroy = true
- assert_deprecated do
- assert !someone.destroy
- assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! }
- end
- assert !someone.after_destroy_called
- end
-
def assert_save_callbacks_not_called(someone)
assert !someone.after_save_called
assert !someone.after_create_called
@@ -564,50 +439,19 @@ class CallbacksTest < ActiveRecord::TestCase
assert !someone.after_destroy_called
end
- def test_callback_returning_false
- david = CallbackDeveloperWithFalseValidation.find(1)
- assert_deprecated { david.save }
- assert_equal [
- [ :after_find, :method ],
- [ :after_find, :string ],
- [ :after_find, :proc ],
- [ :after_find, :object ],
- [ :after_find, :block ],
- [ :after_initialize, :method ],
- [ :after_initialize, :string ],
- [ :after_initialize, :proc ],
- [ :after_initialize, :object ],
- [ :after_initialize, :block ],
- [ :before_validation, :method ],
- [ :before_validation, :string ],
- [ :before_validation, :proc ],
- [ :before_validation, :object ],
- [ :before_validation, :block ],
- [ :before_validation, :returning_false ],
- [ :after_rollback, :block ],
- [ :after_rollback, :object ],
- [ :after_rollback, :proc ],
- [ :after_rollback, :string ],
- [ :after_rollback, :method ],
- ], david.history
- end
-
def test_callback_throwing_abort
david = CallbackDeveloperWithHaltedValidation.find(1)
david.save
assert_equal [
[ :after_find, :method ],
- [ :after_find, :string ],
[ :after_find, :proc ],
[ :after_find, :object ],
[ :after_find, :block ],
[ :after_initialize, :method ],
- [ :after_initialize, :string ],
[ :after_initialize, :proc ],
[ :after_initialize, :object ],
[ :after_initialize, :block ],
[ :before_validation, :method ],
- [ :before_validation, :string ],
[ :before_validation, :proc ],
[ :before_validation, :object ],
[ :before_validation, :block ],
@@ -615,7 +459,6 @@ class CallbacksTest < ActiveRecord::TestCase
[ :after_rollback, :block ],
[ :after_rollback, :object ],
[ :after_rollback, :proc ],
- [ :after_rollback, :string ],
[ :after_rollback, :method ],
], david.history
end
diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb
index b9c6224425..59ef389326 100644
--- a/activerecord/test/cases/coders/yaml_column_test.rb
+++ b/activerecord/test/cases/coders/yaml_column_test.rb
@@ -5,46 +5,48 @@ module ActiveRecord
module Coders
class YAMLColumnTest < ActiveRecord::TestCase
def test_initialize_takes_class
- coder = YAMLColumn.new(Object)
+ coder = YAMLColumn.new("attr_name", Object)
assert_equal Object, coder.object_class
end
def test_type_mismatch_on_different_classes_on_dump
- coder = YAMLColumn.new(Array)
- assert_raises(SerializationTypeMismatch) do
+ coder = YAMLColumn.new("tags", Array)
+ error = assert_raises(SerializationTypeMismatch) do
coder.dump("a")
end
+ assert_equal %{can't dump `tags`: was supposed to be a Array, but was a String. -- "a"}, error.to_s
end
def test_type_mismatch_on_different_classes
- coder = YAMLColumn.new(Array)
- assert_raises(SerializationTypeMismatch) do
+ coder = YAMLColumn.new("tags", Array)
+ error = assert_raises(SerializationTypeMismatch) do
coder.load "--- foo"
end
+ assert_equal %{can't load `tags`: was supposed to be a Array, but was a String. -- "foo"}, error.to_s
end
def test_nil_is_ok
- coder = YAMLColumn.new
+ coder = YAMLColumn.new("attr_name")
assert_nil coder.load "--- "
end
def test_returns_new_with_different_class
- coder = YAMLColumn.new SerializationTypeMismatch
+ coder = YAMLColumn.new("attr_name", SerializationTypeMismatch)
assert_equal SerializationTypeMismatch, coder.load("--- ").class
end
def test_returns_string_unless_starts_with_dash
- coder = YAMLColumn.new
+ coder = YAMLColumn.new("attr_name")
assert_equal "foo", coder.load("foo")
end
def test_load_handles_other_classes
- coder = YAMLColumn.new
+ coder = YAMLColumn.new("attr_name")
assert_equal [], coder.load([])
end
def test_load_doesnt_swallow_yaml_exceptions
- coder = YAMLColumn.new
+ coder = YAMLColumn.new("attr_name")
bad_yaml = "--- {"
assert_raises(Psych::SyntaxError) do
coder.load(bad_yaml)
@@ -52,7 +54,7 @@ module ActiveRecord
end
def test_load_doesnt_handle_undefined_class_or_module
- coder = YAMLColumn.new
+ coder = YAMLColumn.new("attr_name")
missing_class_yaml = '--- !ruby/object:DoesNotExistAndShouldntEver {}\n'
assert_raises(ArgumentError) do
coder.load(missing_class_yaml)
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index a65bb89052..d230700119 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -14,71 +14,19 @@ module ActiveRecord
# Avoid column definitions in create table statements like:
# `title` varchar(255) DEFAULT NULL
def test_should_not_include_default_clause_when_default_is_null
- column = Column.new("title", nil, SqlTypeMetadata.new(limit: 20))
- column_def = ColumnDefinition.new(
- column.name, "string",
- column.limit, column.precision, column.scale, column.default, column.null)
+ column_def = ColumnDefinition.new("title", "string", limit: 20)
assert_equal "title varchar(20)", @viz.accept(column_def)
end
def test_should_include_default_clause_when_default_is_present
- column = Column.new("title", "Hello", SqlTypeMetadata.new(limit: 20))
- column_def = ColumnDefinition.new(
- column.name, "string",
- column.limit, column.precision, column.scale, column.default, column.null)
+ column_def = ColumnDefinition.new("title", "string", limit: 20, default: "Hello")
assert_equal "title varchar(20) DEFAULT 'Hello'", @viz.accept(column_def)
end
def test_should_specify_not_null_if_null_option_is_false
- type_metadata = SqlTypeMetadata.new(limit: 20)
- column = Column.new("title", "Hello", type_metadata, false)
- column_def = ColumnDefinition.new(
- column.name, "string",
- column.limit, column.precision, column.scale, column.default, column.null)
+ column_def = ColumnDefinition.new("title", "string", limit: 20, default: "Hello", null: false)
assert_equal "title varchar(20) DEFAULT 'Hello' NOT NULL", @viz.accept(column_def)
end
-
- if current_adapter?(:Mysql2Adapter)
- def test_should_set_default_for_mysql_binary_data_types
- type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)")
- binary_column = MySQL::Column.new("title", "a", type)
- assert_equal "a", binary_column.default
-
- type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary")
- varbinary_column = MySQL::Column.new("title", "a", type)
- assert_equal "a", varbinary_column.default
- end
-
- def test_should_be_empty_string_default_for_mysql_binary_data_types
- type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)")
- binary_column = MySQL::Column.new("title", "", type, false)
- assert_equal "", binary_column.default
-
- type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary")
- varbinary_column = MySQL::Column.new("title", "", type, false)
- assert_equal "", varbinary_column.default
- end
-
- def test_should_not_set_default_for_blob_and_text_data_types
- text_type = MySQL::TypeMetadata.new(SqlTypeMetadata.new(type: :text))
-
- text_column = MySQL::Column.new("title", nil, text_type)
- assert_nil text_column.default
-
- not_null_text_column = MySQL::Column.new("title", nil, text_type, false)
- assert_nil not_null_text_column.default
- end
-
- def test_has_default_should_return_false_for_blob_and_text_data_types
- binary_type = SqlTypeMetadata.new(sql_type: "blob")
- blob_column = MySQL::Column.new("title", nil, binary_type)
- assert !blob_column.has_default?
-
- text_type = SqlTypeMetadata.new(type: :text)
- text_column = MySQL::Column.new("title", nil, text_type)
- assert !text_column.has_default?
- end
- end
end
end
end
diff --git a/activerecord/test/cases/comment_test.rb b/activerecord/test/cases/comment_test.rb
index a625299e8d..63f67a9a16 100644
--- a/activerecord/test/cases/comment_test.rb
+++ b/activerecord/test/cases/comment_test.rb
@@ -113,7 +113,7 @@ if ActiveRecord::Base.connection.supports_comments?
assert_match %r[t\.string\s+"content",\s+comment: "Whoa, content describes itself!"], output
assert_match %r[t\.integer\s+"rating",\s+comment: "I am running out of imagination"], output
assert_match %r[t\.index\s+.+\s+comment: "\\\"Very important\\\" index that powers all the performance.\\nAnd it's fun!"], output
- assert_match %r[t\.index\s+.+\s+name: "idx_obvious",.+\s+comment: "We need to see obvious comments"], output
+ assert_match %r[t\.index\s+.+\s+name: "idx_obvious",\s+comment: "We need to see obvious comments"], output
end
def test_schema_dump_omits_blank_comments
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 2c33bf22ab..681399c8bb 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -20,6 +20,66 @@ module ActiveRecord
@handler.remove_connection("readonly")
end
+ def test_establish_connection_using_3_levels_config
+ previous_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "default_env"
+
+ config = {
+ "default_env" => {
+ "readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" },
+ "primary" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" }
+ },
+ "another_env" => {
+ "readonly" => { "adapter" => "sqlite3", "database" => "db/bad-readonly.sqlite3" },
+ "primary" => { "adapter" => "sqlite3", "database" => "db/bad-primary.sqlite3" }
+ },
+ "common" => { "adapter" => "sqlite3", "database" => "db/common.sqlite3" }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ @handler.establish_connection(:common)
+ @handler.establish_connection(:primary)
+ @handler.establish_connection(:readonly)
+
+ assert_not_nil pool = @handler.retrieve_connection_pool("readonly")
+ assert_equal "db/readonly.sqlite3", pool.spec.config[:database]
+
+ assert_not_nil pool = @handler.retrieve_connection_pool("primary")
+ assert_equal "db/primary.sqlite3", pool.spec.config[:database]
+
+ assert_not_nil pool = @handler.retrieve_connection_pool("common")
+ assert_equal "db/common.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ ENV["RAILS_ENV"] = previous_env
+ end
+
+ def test_establish_connection_using_two_level_configurations
+ config = { "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" } }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ @handler.establish_connection(:development)
+
+ assert_not_nil pool = @handler.retrieve_connection_pool("development")
+ assert_equal "db/primary.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ end
+
+ def test_establish_connection_using_top_level_key_in_two_level_config
+ config = {
+ "development" => { "adapter" => "sqlite3", "database" => "db/primary.sqlite3" },
+ "development_readonly" => { "adapter" => "sqlite3", "database" => "db/readonly.sqlite3" }
+ }
+ @prev_configs, ActiveRecord::Base.configurations = ActiveRecord::Base.configurations, config
+
+ @handler.establish_connection(:development_readonly)
+
+ assert_not_nil pool = @handler.retrieve_connection_pool("development_readonly")
+ assert_equal "db/readonly.sqlite3", pool.spec.config[:database]
+ ensure
+ ActiveRecord::Base.configurations = @prev_configs
+ end
+
def test_retrieve_connection
assert @handler.retrieve_connection(@spec_name)
end
@@ -89,6 +149,41 @@ module ActiveRecord
rd.close
end
+ def test_pool_from_any_process_for_uses_most_recent_spec
+ skip unless current_adapter?(:SQLite3Adapter)
+
+ file = Tempfile.new "lol.sqlite3"
+
+ rd, wr = IO.pipe
+ rd.binmode
+ wr.binmode
+
+ pid = fork do
+ ActiveRecord::Base.configurations["arunit"]["database"] = file.path
+ ActiveRecord::Base.establish_connection(:arunit)
+
+ pid2 = fork do
+ wr.write ActiveRecord::Base.connection_config[:database]
+ wr.close
+ end
+
+ Process.waitpid pid2
+ end
+
+ Process.waitpid pid
+
+ wr.close
+
+ assert_equal file.path, rd.read
+
+ rd.close
+ ensure
+ if file
+ file.close
+ file.unlink
+ end
+ end
+
def test_a_class_using_custom_pool_and_switching_back_to_primary
klass2 = Class.new(Base) { def self.name; "klass2"; end }
diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
index e2e5445a4e..a348c2d783 100644
--- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb
+++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb
@@ -89,12 +89,20 @@ unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strin
end
def test_decimal_without_scale
- types = %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)}
- types.each do |type|
- cast_type = @connection.type_map.lookup(type)
-
- assert_equal :decimal, cast_type.type
- assert_equal 2, cast_type.cast(2.1)
+ if current_adapter?(:OracleAdapter)
+ {
+ decimal: %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0)},
+ integer: %w{number(2) number(2,0)}
+ }
+ else
+ { decimal: %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)} }
+ end.each do |expected_type, types|
+ types.each do |type|
+ cast_type = @connection.type_map.lookup(type)
+
+ assert_equal expected_type, cast_type.type
+ assert_equal 2, cast_type.cast(2.1)
+ end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 42600e53fd..afd0ac2dd4 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -341,14 +341,18 @@ module ActiveRecord
end
end
+ class ConnectionTestModel < ActiveRecord::Base
+ end
+
def test_connection_notification_is_called
payloads = []
subscription = ActiveSupport::Notifications.subscribe("!connection.active_record") do |name, started, finished, unique_id, payload|
payloads << payload
end
- ActiveRecord::Base.establish_connection :arunit
+ ConnectionTestModel.establish_connection :arunit
+
assert_equal [:config, :connection_id, :spec_name], payloads[0].keys.sort
- assert_equal "primary", payloads[0][:spec_name]
+ assert_equal "ActiveRecord::ConnectionAdapters::ConnectionPoolTest::ConnectionTestModel", payloads[0][:spec_name]
ensure
ActiveSupport::Notifications.unsubscribe(subscription) if subscription
end
@@ -395,7 +399,7 @@ module ActiveRecord
all_threads_in_new_connection.wait
end
rescue Timeout::Error
- flunk "pool unable to establish connections concurrently or implementation has " <<
+ flunk "pool unable to establish connections concurrently or implementation has " \
"changed, this test then needs to patch a different :new_connection method"
ensure
# clean up the threads
@@ -437,7 +441,7 @@ module ActiveRecord
end
end
- def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_aquire_all_connections_proceed_anyway
+ def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_acquire_all_connections_proceed_anyway
@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|
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index c7d0ba32b4..46d7526cc0 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -221,6 +221,15 @@ class CounterCacheTest < ActiveRecord::TestCase
assert_equal previously_updated_at, @topic.updated_at
end
+ test "update counters doesn't touch timestamps with touch: []" do
+ @topic.update_column :updated_at, 5.minutes.ago
+ previously_updated_at = @topic.updated_at
+
+ Topic.update_counters(@topic.id, replies_count: -1, touch: [])
+
+ assert_equal previously_updated_at, @topic.updated_at
+ end
+
test "update counters with touch: true" do
assert_touching @topic, :updated_at do
Topic.update_counters(@topic.id, replies_count: -1, touch: true)
diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb
index a1c3c5af9c..e4a2f9ee17 100644
--- a/activerecord/test/cases/date_time_precision_test.rb
+++ b/activerecord/test/cases/date_time_precision_test.rb
@@ -73,7 +73,7 @@ if subsecond_precision_supported?
assert_match %r{t\.datetime\s+"updated_at",\s+precision: 6,\s+null: false$}, output
end
- if current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter)
def test_datetime_precision_with_zero_should_be_dumped
@connection.create_table(:foos, force: true) do |t|
t.timestamps precision: 0
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index 0e58e65a07..a43c06cd6e 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -301,6 +301,14 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal ["arr", "arr matey!"], pirate.catchphrase_change
end
+ def test_virtual_attribute_will_change
+ assert_deprecated do
+ parrot = Parrot.create!(name: "Ruby")
+ parrot.send(:attribute_will_change!, :cancel_save_from_callback)
+ assert parrot.has_changes_to_save?
+ end
+ end
+
def test_association_assignment_changes_foreign_key
pirate = Pirate.create!(catchphrase: "jarl")
pirate.parrot = Parrot.create!(name: "Lorre")
@@ -569,6 +577,7 @@ class DirtyTest < ActiveRecord::TestCase
end
ensure
ActiveRecord::Base.connection.drop_table :testings rescue nil
+ ActiveRecord::Base.clear_cache!
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index ca22fe969c..deec669935 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -117,7 +117,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal "The Fourth Topic of the day", records[2].title
end
- def test_find_passing_active_record_object_is_deprecated
+ def test_find_passing_active_record_object_is_not_permitted
assert_raises(ArgumentError) do
Topic.find(Topic.last)
end
@@ -167,7 +167,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal false, relation.exists?(false)
end
- def test_exists_passing_active_record_object_is_not_permited
+ def test_exists_passing_active_record_object_is_not_permitted
assert_raises(ArgumentError) do
Topic.exists?(Topic.new)
end
@@ -339,6 +339,11 @@ class FinderTest < ActiveRecord::TestCase
assert_equal author.post, Post.find_by(author_id: Author.where(id: author))
end
+ def test_find_by_and_where_consistency_with_active_record_instance
+ author = authors(:david)
+ assert_equal Post.where(author_id: author).take, Post.find_by(author_id: author)
+ end
+
def test_take
assert_equal topics(:first), Topic.take
end
@@ -858,13 +863,13 @@ class FinderTest < ActiveRecord::TestCase
end
def test_bind_variables_with_quotes
- Company.create("name" => "37signals' go'es agains")
- assert Company.where(["name = ?", "37signals' go'es agains"]).first
+ Company.create("name" => "37signals' go'es against")
+ assert Company.where(["name = ?", "37signals' go'es against"]).first
end
def test_named_bind_variables_with_quotes
- Company.create("name" => "37signals' go'es agains")
- assert Company.where(["name = :name", { name: "37signals' go'es agains" }]).first
+ Company.create("name" => "37signals' go'es against")
+ assert Company.where(["name = :name", { name: "37signals' go'es against" }]).first
end
def test_named_bind_variables
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index dd48053823..afe761cb55 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -7,6 +7,7 @@ require "models/binary"
require "models/book"
require "models/bulb"
require "models/category"
+require "models/post"
require "models/comment"
require "models/company"
require "models/computer"
@@ -19,7 +20,6 @@ require "models/matey"
require "models/other_dog"
require "models/parrot"
require "models/pirate"
-require "models/post"
require "models/randomly_named_c1"
require "models/reply"
require "models/ship"
@@ -640,6 +640,8 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
def test_transaction_created_on_connection_notification
connection = stub(transaction_open?: false)
connection.expects(:begin_transaction).with(joinable: false)
+ pool = connection.stubs(:pool).returns(ActiveRecord::ConnectionAdapters::ConnectionPool.new(ActiveRecord::Base.connection_pool.spec))
+ pool.stubs(:lock_thread=).with(false)
fire_connection_notification(connection)
end
@@ -647,12 +649,16 @@ class TransactionalFixturesOnConnectionNotification < ActiveRecord::TestCase
# Mocha is not thread-safe so define our own stub to test
connection = Class.new do
attr_accessor :rollback_transaction_called
+ attr_accessor :pool
def transaction_open?; true; end
def begin_transaction(*args); end
def rollback_transaction(*args)
@rollback_transaction_called = true
end
end.new
+ connection.pool = Class.new do
+ def lock_thread=(lock_thread); false; end
+ end.new
fire_connection_notification(connection)
teardown_fixtures
assert(connection.rollback_transaction_called, "Expected <mock connection>#rollback_transaction to be called but was not")
@@ -1012,7 +1018,7 @@ end
class FixtureClassNamesTest < ActiveRecord::TestCase
def setup
- @saved_cache = self.fixture_class_names.dup
+ @saved_cache = fixture_class_names.dup
end
def teardown
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 1ddcbf0e4f..5a3b8e3fb5 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -6,7 +6,6 @@ require "active_record"
require "cases/test_case"
require "active_support/dependencies"
require "active_support/logger"
-require "active_support/core_ext/string/strip"
require "support/config"
require "support/connection"
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index 155e858822..5a1d066aef 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -101,6 +101,17 @@ class JsonSerializationTest < ActiveRecord::TestCase
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end
+ def test_uses_serializable_hash_with_frozen_hash
+ def @contact.serializable_hash(options = nil)
+ super({ only: %w(name) }.freeze)
+ end
+
+ json = @contact.to_json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_no_match %r{awesome}, json
+ assert_no_match %r{age}, json
+ end
+
def test_uses_serializable_hash_with_only_option
def @contact.serializable_hash(options = nil)
super(only: %w(name))
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 95fb670dac..23095618a4 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -18,7 +18,7 @@ class LockWithoutDefault < ActiveRecord::Base; end
class LockWithCustomColumnWithoutDefault < ActiveRecord::Base
self.table_name = :lock_without_defaults_cust
- self.column_defaults # to test @column_defaults caching.
+ column_defaults # to test @column_defaults caching.
self.locking_column = :custom_lock_version
end
@@ -536,7 +536,10 @@ unless in_memory_db?
Person.transaction do
person = Person.find 1
old, person.first_name = person.first_name, "fooman"
- person.lock!
+ # Locking a dirty record is deprecated
+ assert_deprecated do
+ person.lock!
+ end
assert_equal old, person.first_name
end
end
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
index 8a4242cf1d..ec817a579b 100644
--- a/activerecord/test/cases/migration/change_table_test.rb
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -101,12 +101,7 @@ module ActiveRecord
def test_primary_key_creates_primary_key_column
with_change_table do |t|
- if current_adapter?(:Mysql2Adapter)
- @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, { first: true, auto_increment: true, limit: 8, primary_key: true }]
- else
- @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, primary_key: true, first: true]
- end
-
+ @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, primary_key: true, first: true]
t.primary_key :id, first: true
end
end
diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb
index e5a7412bc3..7a80bfb899 100644
--- a/activerecord/test/cases/migration/compatibility_test.rb
+++ b/activerecord/test/cases/migration/compatibility_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "support/schema_dumping_helper"
module ActiveRecord
class Migration
@@ -103,10 +104,118 @@ module ActiveRecord
end
def test_legacy_migrations_raises_exception_when_inherited
- assert_raises(StandardError) do
- Class.new(ActiveRecord::Migration)
+ e = assert_raises(StandardError) do
+ class_eval("class LegacyMigration < ActiveRecord::Migration; end")
end
+ assert_match(/LegacyMigration < ActiveRecord::Migration\[4\.2\]/, e.message)
end
end
end
end
+
+class LegacyPrimaryKeyTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ self.use_transactional_tests = false
+
+ class LegacyPrimaryKey < ActiveRecord::Base
+ end
+
+ def setup
+ @migration = nil
+ @verbose_was = ActiveRecord::Migration.verbose
+ ActiveRecord::Migration.verbose = false
+ end
+
+ def teardown
+ @migration.migrate(:down) if @migration
+ ActiveRecord::Migration.verbose = @verbose_was
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ LegacyPrimaryKey.reset_column_information
+ end
+
+ def test_legacy_primary_key_should_be_auto_incremented
+ @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ def change
+ create_table :legacy_primary_keys do |t|
+ t.references :legacy_ref
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ legacy_pk = LegacyPrimaryKey.columns_hash["id"]
+ assert_not legacy_pk.bigint?
+ assert_not legacy_pk.null
+
+ legacy_ref = LegacyPrimaryKey.columns_hash["legacy_ref_id"]
+ assert_not legacy_ref.bigint?
+
+ record1 = LegacyPrimaryKey.create!
+ assert_not_nil record1.id
+
+ record1.destroy
+
+ record2 = LegacyPrimaryKey.create!
+ assert_not_nil record2.id
+ assert_operator record2.id, :>, record1.id
+ end
+
+ def test_legacy_integer_primary_key_should_not_be_auto_incremented
+ skip if current_adapter?(:SQLite3Adapter)
+
+ @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ def change
+ create_table :legacy_primary_keys, id: :integer do |t|
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ assert_raises(ActiveRecord::NotNullViolation) do
+ LegacyPrimaryKey.create!
+ end
+
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", id: :integer, default: nil}, schema
+ end
+
+ if current_adapter?(:Mysql2Adapter)
+ def test_legacy_bigint_primary_key_should_be_auto_incremented
+ @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ def change
+ create_table :legacy_primary_keys, id: :bigint
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ legacy_pk = LegacyPrimaryKey.columns_hash["id"]
+ assert legacy_pk.bigint?
+ assert legacy_pk.auto_increment?
+
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", (?!id: :bigint, default: nil)}, schema
+ end
+ else
+ def test_legacy_bigint_primary_key_should_not_be_auto_incremented
+ @migration = Class.new(ActiveRecord::Migration[5.0]) {
+ def change
+ create_table :legacy_primary_keys, id: :bigint do |t|
+ end
+ end
+ }.new
+
+ @migration.migrate(:up)
+
+ assert_raises(ActiveRecord::NotNullViolation) do
+ LegacyPrimaryKey.create!
+ end
+
+ schema = dump_table_schema "legacy_primary_keys"
+ assert_match %r{create_table "legacy_primary_keys", id: :bigint, default: nil}, schema
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb
index 9be6667aa1..7762d37915 100644
--- a/activerecord/test/cases/migration/foreign_key_test.rb
+++ b/activerecord/test/cases/migration/foreign_key_test.rb
@@ -1,12 +1,30 @@
require "cases/helper"
-require "support/ddl_helper"
require "support/schema_dumping_helper"
+if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
+ module ActiveRecord
+ class Migration
+ class ForeignKeyInCreateTest < ActiveRecord::TestCase
+ def test_foreign_keys
+ foreign_keys = ActiveRecord::Base.connection.foreign_keys("fk_test_has_fk")
+ assert_equal 1, foreign_keys.size
+
+ fk = foreign_keys.first
+ assert_equal "fk_test_has_fk", fk.from_table
+ assert_equal "fk_test_has_pk", fk.to_table
+ assert_equal "fk_id", fk.column
+ assert_equal "pk_id", fk.primary_key
+ assert_equal "fk_name", fk.name unless current_adapter?(:SQLite3Adapter)
+ end
+ end
+ end
+ end
+end
+
if ActiveRecord::Base.connection.supports_foreign_keys?
module ActiveRecord
class Migration
class ForeignKeyTest < ActiveRecord::TestCase
- include DdlHelper
include SchemaDumpingHelper
include ActiveSupport::Testing::Stream
@@ -29,10 +47,8 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
teardown do
- if defined?(@connection)
- @connection.drop_table "astronauts", if_exists: true
- @connection.drop_table "rockets", if_exists: true
- end
+ @connection.drop_table "astronauts", if_exists: true
+ @connection.drop_table "rockets", if_exists: true
end
def test_foreign_keys
@@ -76,20 +92,23 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
end
def test_add_foreign_key_with_non_standard_primary_key
- with_example_table @connection, "space_shuttles", "pk BIGINT PRIMARY KEY" do
- @connection.add_foreign_key(:astronauts, :space_shuttles,
- column: "rocket_id", primary_key: "pk", name: "custom_pk")
+ @connection.create_table :space_shuttles, id: false, force: true do |t|
+ t.bigint :pk, primary_key: true
+ end
- foreign_keys = @connection.foreign_keys("astronauts")
- assert_equal 1, foreign_keys.size
+ @connection.add_foreign_key(:astronauts, :space_shuttles,
+ column: "rocket_id", primary_key: "pk", name: "custom_pk")
- fk = foreign_keys.first
- assert_equal "astronauts", fk.from_table
- assert_equal "space_shuttles", fk.to_table
- assert_equal "pk", fk.primary_key
+ foreign_keys = @connection.foreign_keys("astronauts")
+ assert_equal 1, foreign_keys.size
- @connection.remove_foreign_key :astronauts, name: "custom_pk"
- end
+ fk = foreign_keys.first
+ assert_equal "astronauts", fk.from_table
+ assert_equal "space_shuttles", fk.to_table
+ assert_equal "pk", fk.primary_key
+ ensure
+ @connection.remove_foreign_key :astronauts, name: "custom_pk"
+ @connection.drop_table :space_shuttles
end
def test_add_on_delete_restrict_foreign_key
@@ -229,7 +248,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
create_table("cities") { |t| }
create_table("houses") do |t|
- t.column :city_id, :bigint
+ t.references :city
end
add_foreign_key :houses, :cities, column: "city_id"
@@ -261,7 +280,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
create_table(:schools)
create_table(:classes) do |t|
- t.column :school_id, :bigint
+ t.references :school
end
add_foreign_key :classes, :schools
end
@@ -305,9 +324,11 @@ else
@connection.remove_foreign_key :clubs, :categories
end
- def test_foreign_keys_should_raise_not_implemented
- assert_raises NotImplementedError do
- @connection.foreign_keys("clubs")
+ unless current_adapter?(:SQLite3Adapter)
+ def test_foreign_keys_should_raise_not_implemented
+ assert_raises NotImplementedError do
+ @connection.foreign_keys("clubs")
+ end
end
end
end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
index 0f975026b8..f10fcf1398 100644
--- a/activerecord/test/cases/migration/index_test.rb
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -31,9 +31,10 @@ module ActiveRecord
connection.add_index(table_name, [:foo], name: "old_idx")
connection.rename_index(table_name, "old_idx", "new_idx")
- # if the adapter doesn't support the indexes call, pick defaults that let the test pass
- assert_not connection.index_name_exists?(table_name, "old_idx", false)
- assert connection.index_name_exists?(table_name, "new_idx", true)
+ assert_deprecated do
+ assert_not connection.index_name_exists?(table_name, "old_idx", false)
+ assert connection.index_name_exists?(table_name, "new_idx", true)
+ end
end
def test_rename_index_too_long
@@ -45,8 +46,7 @@ module ActiveRecord
}
assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message)
- # if the adapter doesn't support the indexes call, pick defaults that let the test pass
- assert connection.index_name_exists?(table_name, "old_idx", false)
+ assert connection.index_name_exists?(table_name, "old_idx")
end
def test_double_add_index
@@ -63,7 +63,7 @@ module ActiveRecord
def test_add_index_works_with_long_index_names
connection.add_index(table_name, "foo", name: good_index_name)
- assert connection.index_name_exists?(table_name, good_index_name, false)
+ assert connection.index_name_exists?(table_name, good_index_name)
connection.remove_index(table_name, name: good_index_name)
end
@@ -75,7 +75,7 @@ module ActiveRecord
}
assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message)
- assert_not connection.index_name_exists?(table_name, too_long_index_name, false)
+ assert_not connection.index_name_exists?(table_name, too_long_index_name)
connection.add_index(table_name, "foo", name: good_index_name)
end
@@ -83,7 +83,7 @@ module ActiveRecord
good_index_name = "x" * connection.index_name_length
connection.add_index(table_name, "foo", name: good_index_name, internal: true)
- assert connection.index_name_exists?(table_name, good_index_name, false)
+ assert connection.index_name_exists?(table_name, good_index_name)
connection.remove_index(table_name, name: good_index_name)
end
diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb
index 4957ab8b3d..9418995ea0 100644
--- a/activerecord/test/cases/migration/references_foreign_key_test.rb
+++ b/activerecord/test/cases/migration/references_foreign_key_test.rb
@@ -1,9 +1,9 @@
require "cases/helper"
-if ActiveRecord::Base.connection.supports_foreign_keys?
+if ActiveRecord::Base.connection.supports_foreign_keys_in_create?
module ActiveRecord
class Migration
- class ReferencesForeignKeyTest < ActiveRecord::TestCase
+ class ReferencesForeignKeyInCreateTest < ActiveRecord::TestCase
setup do
@connection = ActiveRecord::Base.connection
@connection.create_table(:testing_parents, force: true)
@@ -42,8 +42,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
test "options hash can be passed" do
@connection.change_table :testing_parents do |t|
- t.bigint :other_id
- t.index :other_id, unique: true
+ t.references :other, index: { unique: true }
end
@connection.create_table :testings do |t|
t.references :testing_parent, foreign_key: { primary_key: :other_id }
@@ -61,6 +60,24 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
assert_equal([["testings", "testing_parents", "parent_id"]],
fks.map { |fk| [fk.from_table, fk.to_table, fk.column] })
end
+ end
+ end
+ end
+end
+
+if ActiveRecord::Base.connection.supports_foreign_keys?
+ module ActiveRecord
+ class Migration
+ class ReferencesForeignKeyTest < ActiveRecord::TestCase
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @connection.create_table(:testing_parents, force: true)
+ end
+
+ teardown do
+ @connection.drop_table "testings", if_exists: true
+ @connection.drop_table "testing_parents", if_exists: true
+ end
test "foreign keys cannot be added to polymorphic relations when creating the table" do
@connection.create_table :testings do |t|
@@ -92,8 +109,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
test "foreign keys accept options when changing the table" do
@connection.change_table :testing_parents do |t|
- t.bigint :other_id
- t.index :other_id, unique: true
+ t.references :other, index: { unique: true }
end
@connection.create_table :testings
@connection.change_table :testings do |t|
@@ -177,18 +193,15 @@ if ActiveRecord::Base.connection.supports_foreign_keys?
test "multiple foreign keys can be added to the same table" do
@connection.create_table :testings do |t|
- t.bigint :col_1
- t.bigint :col_2
-
- t.foreign_key :testing_parents, column: :col_1
- t.foreign_key :testing_parents, column: :col_2
+ t.references :parent1, foreign_key: { to_table: :testing_parents }
+ t.references :parent2, foreign_key: { to_table: :testing_parents }
end
- fks = @connection.foreign_keys("testings")
+ fks = @connection.foreign_keys("testings").sort_by(&:column)
fk_definitions = fks.map { |fk| [fk.from_table, fk.to_table, fk.column] }
- assert_equal([["testings", "testing_parents", "col_1"],
- ["testings", "testing_parents", "col_2"]], fk_definitions)
+ assert_equal([["testings", "testing_parents", "parent1_id"],
+ ["testings", "testing_parents", "parent2_id"]], fk_definitions)
end
end
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 082cfd3242..de16ecf442 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -43,10 +43,10 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Base.table_name_prefix = ""
ActiveRecord::Base.table_name_suffix = ""
- ActiveRecord::Base.connection.initialize_schema_migrations_table
- ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
+ ActiveRecord::SchemaMigration.create_table
+ ActiveRecord::SchemaMigration.delete_all
- %w(things awesome_things prefix_things_suffix p_awesome_things_s ).each do |table|
+ %w(things awesome_things prefix_things_suffix p_awesome_things_s).each do |table|
Thing.connection.drop_table(table) rescue nil
end
Thing.reset_column_information
@@ -399,6 +399,7 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migrator.migrations_paths = old_path
ENV["RAILS_ENV"] = original_rails_env
ENV["RACK_ENV"] = original_rack_env
+ ActiveRecord::Migrator.up(migrations_path)
end
def test_migration_sets_internal_metadata_even_when_fully_migrated
@@ -425,6 +426,7 @@ class MigrationTest < ActiveRecord::TestCase
ActiveRecord::Migrator.migrations_paths = old_path
ENV["RAILS_ENV"] = original_rails_env
ENV["RACK_ENV"] = original_rack_env
+ ActiveRecord::Migrator.up(migrations_path)
end
def test_internal_metadata_stores_environment_when_other_data_exists
@@ -887,7 +889,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
assert_equal :datetime, column(:birthdate).type
end
- protected
+ private
def with_bulk_change_table
# Reset columns/indexes cache as we're changing the table
@@ -914,7 +916,6 @@ if ActiveRecord::Base.connection.supports_bulk_alter?
@indexes ||= Person.connection.indexes("delete_me")
end
end # AlterTableMigrationsTest
-
end
class CopyMigrationsTest < ActiveRecord::TestCase
@@ -1132,4 +1133,13 @@ class CopyMigrationsTest < ActiveRecord::TestCase
def test_unknown_migration_version_should_raise_an_argument_error
assert_raise(ArgumentError) { ActiveRecord::Migration[1.0] }
end
+
+ def test_deprecate_initialize_internal_tables
+ assert_deprecated { ActiveRecord::Base.connection.initialize_schema_migrations_table }
+ assert_deprecated { ActiveRecord::Base.connection.initialize_internal_metadata_table }
+ end
+
+ def test_deprecate_migration_keys
+ assert_deprecated { ActiveRecord::Base.connection.migration_keys }
+ end
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
index bb9835394b..20d70b75ac 100644
--- a/activerecord/test/cases/migrator_test.rb
+++ b/activerecord/test/cases/migrator_test.rb
@@ -45,10 +45,11 @@ class MigratorTest < ActiveRecord::TestCase
end
def test_migrator_with_duplicate_names
- assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do
+ e = assert_raises(ActiveRecord::DuplicateMigrationNameError) do
list = [ActiveRecord::Migration.new("Chunky"), ActiveRecord::Migration.new("Chunky")]
ActiveRecord::Migrator.new(:up, list)
end
+ assert_match(/Multiple migrations have the name Chunky/, e.message)
end
def test_migrator_with_duplicate_versions
@@ -290,6 +291,27 @@ class MigratorTest < ActiveRecord::TestCase
assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
end
+ def test_migrator_output_when_running_multiple_migrations
+ _, migrator = migrator_class(3)
+
+ result = migrator.migrate("valid")
+ assert_equal(3, result.count)
+
+ # Nothing migrated from duplicate run
+ result = migrator.migrate("valid")
+ assert_equal(0, result.count)
+
+ result = migrator.rollback("valid")
+ assert_equal(1, result.count)
+ end
+
+ def test_migrator_output_when_running_single_migration
+ _, migrator = migrator_class(1)
+ result = migrator.run(:up, "valid", 1)
+
+ assert_equal(1, result.version)
+ end
+
def test_migrator_rollback
_, migrator = migrator_class(3)
diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
index 350a966d40..b9d2acbed2 100644
--- a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
+++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb
@@ -120,14 +120,14 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase
assert_assignment_affects_records_in_target(:birds_with_add)
end
- test("Assignment updates records in target when not loaded" +
+ test("Assignment updates records in target when not loaded" \
" and callback loads target") do
assert_not @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
- test("Assignment updates records in target when loaded" +
+ test("Assignment updates records in target when loaded" \
" and callback loads target") do
@pirate.birds_with_add_load.load_target
@pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 3f1da82cb4..5b7e2fd008 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -139,6 +139,14 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal initial_credit + 2, a1.reload.credit_limit
end
+ def test_increment_updates_timestamps
+ topic = topics(:first)
+ topic.update_columns(updated_at: 5.minutes.ago)
+ previous_updated_at = topic.updated_at
+ topic.increment!(:replies_count, touch: true)
+ assert_operator previous_updated_at, :<, topic.reload.updated_at
+ end
+
def test_destroy_all
conditions = "author_name = 'Mary'"
topics_by_mary = Topic.all.merge!(where: conditions, order: "id").to_a
@@ -230,6 +238,14 @@ class PersistenceTest < ActiveRecord::TestCase
assert_equal 41, accounts(:signals37, :reload).credit_limit
end
+ def test_decrement_updates_timestamps
+ topic = topics(:first)
+ topic.update_columns(updated_at: 5.minutes.ago)
+ previous_updated_at = topic.updated_at
+ topic.decrement!(:replies_count, touch: true)
+ assert_operator previous_updated_at, :<, topic.reload.updated_at
+ end
+
def test_create
topic = Topic.new
topic.title = "New Topic"
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 1d72899102..03c8644229 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -7,6 +7,7 @@ require "models/movie"
require "models/keyboard"
require "models/mixed_case_monkey"
require "models/dashboard"
+require "models/non_primary_key"
class PrimaryKeysTest < ActiveRecord::TestCase
fixtures :topics, :subscribers, :movies, :mixed_case_monkeys
@@ -89,6 +90,12 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_equal("John Doe", subscriberReloaded.name)
end
+ def test_id_column_that_is_not_primary_key
+ NonPrimaryKey.create!(id: 100)
+ actual = NonPrimaryKey.find_by(id: 100)
+ assert_match %r{<NonPrimaryKey id: 100}, actual.inspect
+ end
+
def test_find_with_more_than_one_string_key
assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length
end
@@ -113,38 +120,45 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_delete_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.delete(1) }
end
+
def test_update_counters_should_quote_pkey_and_quote_counter_columns
assert_nothing_raised { MixedCaseMonkey.update_counters(1, fleaCount: 99) }
end
+
def test_find_with_one_id_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find(1) }
end
+
def test_find_with_multiple_ids_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find([1, 2]) }
end
+
def test_instance_update_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find(1).save }
end
+
def test_instance_destroy_should_quote_pkey
assert_nothing_raised { MixedCaseMonkey.find(1).destroy }
end
- if ActiveRecord::Base.connection.supports_primary_key?
- def test_primary_key_returns_value_if_it_exists
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = "developers"
- end
+ def test_deprecate_supports_primary_key
+ assert_deprecated { ActiveRecord::Base.connection.supports_primary_key? }
+ end
- assert_equal "id", klass.primary_key
+ def test_primary_key_returns_value_if_it_exists
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "developers"
end
- def test_primary_key_returns_nil_if_it_does_not_exist
- klass = Class.new(ActiveRecord::Base) do
- self.table_name = "developers_projects"
- end
+ assert_equal "id", klass.primary_key
+ end
- assert_nil klass.primary_key
+ def test_primary_key_returns_nil_if_it_does_not_exist
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "developers_projects"
end
+
+ assert_nil klass.primary_key
end
def test_quoted_primary_key_after_set_primary_key
@@ -224,13 +238,13 @@ class PrimaryKeyWithAutoIncrementTest < ActiveRecord::TestCase
@connection.drop_table(:auto_increments, if_exists: true)
end
- def test_primary_key_with_auto_increment
- @connection.create_table(:auto_increments, id: :integer, auto_increment: true, force: true)
+ def test_primary_key_with_integer
+ @connection.create_table(:auto_increments, id: :integer, force: true)
assert_auto_incremented
end
- def test_primary_key_with_auto_increment_and_bigint
- @connection.create_table(:auto_increments, id: :bigint, auto_increment: true, force: true)
+ def test_primary_key_with_bigint
+ @connection.create_table(:auto_increments, id: :bigint, force: true)
assert_auto_incremented
end
@@ -291,6 +305,10 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
t.string :region
t.integer :code
end
+ @connection.create_table(:barcodes_reverse, primary_key: ["code", "region"], force: true) do |t|
+ t.string :region
+ t.integer :code
+ end
end
def teardown
@@ -301,6 +319,11 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
assert_equal ["region", "code"], @connection.primary_keys("barcodes")
end
+ def test_composite_primary_key_out_of_order
+ skip if current_adapter?(:SQLite3Adapter)
+ assert_equal ["code", "region"], @connection.primary_keys("barcodes_reverse")
+ end
+
def test_primary_key_issues_warning
model = Class.new(ActiveRecord::Base) do
def self.table_name
@@ -313,76 +336,106 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase
assert_match(/WARNING: Active Record does not support composite primary key\./, warning)
end
- def test_collectly_dump_composite_primary_key
+ def test_dumping_composite_primary_key
schema = dump_table_schema "barcodes"
assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema
end
+
+ def test_dumping_composite_primary_key_out_of_order
+ skip if current_adapter?(:SQLite3Adapter)
+ schema = dump_table_schema "barcodes_reverse"
+ assert_match %r{create_table "barcodes_reverse", primary_key: \["code", "region"\]}, schema
+ end
end
-if current_adapter?(:Mysql2Adapter)
- class PrimaryKeyIntegerNilDefaultTest < ActiveRecord::TestCase
- include SchemaDumpingHelper
+class PrimaryKeyIntegerNilDefaultTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
- self.use_transactional_tests = false
+ self.use_transactional_tests = false
- def setup
- @connection = ActiveRecord::Base.connection
- @connection.create_table(:int_defaults, id: :integer, default: nil, force: true)
- end
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
- def teardown
- @connection.drop_table :int_defaults, if_exists: true
- end
+ def teardown
+ @connection.drop_table :int_defaults, if_exists: true
+ end
- test "primary key with integer allows default override via nil" do
- column = @connection.columns(:int_defaults).find { |c| c.name == "id" }
- assert_equal :integer, column.type
- assert_not column.auto_increment?
- end
+ def test_schema_dump_primary_key_integer_with_default_nil
+ skip if current_adapter?(:SQLite3Adapter)
+ @connection.create_table(:int_defaults, id: :integer, default: nil, force: true)
+ schema = dump_table_schema "int_defaults"
+ assert_match %r{create_table "int_defaults", id: :integer, default: nil}, schema
+ end
- test "schema dump primary key with int default nil" do
- schema = dump_table_schema "int_defaults"
- assert_match %r{create_table "int_defaults", id: :integer, default: nil}, schema
- end
+ def test_schema_dump_primary_key_bigint_with_default_nil
+ @connection.create_table(:int_defaults, id: :bigint, default: nil, force: true)
+ schema = dump_table_schema "int_defaults"
+ assert_match %r{create_table "int_defaults", id: :bigint, default: nil}, schema
end
end
-class PrimaryKeyIntegerTest < ActiveRecord::TestCase
- include SchemaDumpingHelper
+if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
+ class PrimaryKeyIntegerTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
- self.use_transactional_tests = false
+ self.use_transactional_tests = false
- class Widget < ActiveRecord::Base
- end
+ class Widget < ActiveRecord::Base
+ end
- setup do
- @connection = ActiveRecord::Base.connection
- @connection.create_table(:widgets, force: true)
- end
+ setup do
+ @connection = ActiveRecord::Base.connection
+ @pk_type = current_adapter?(:PostgreSQLAdapter) ? :serial : :integer
+ end
- teardown do
- @connection.drop_table :widgets, if_exists: true
- Widget.reset_column_information
- end
+ teardown do
+ @connection.drop_table :widgets, if_exists: true
+ end
- if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
- test "schema dump primary key with bigserial" do
- schema = dump_table_schema "widgets"
- assert_match %r{create_table "widgets", force: :cascade}, schema
+ test "primary key column type with serial/integer" do
+ @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?
end
- end
- test "primary key column type" do
- column_type = Widget.type_for_attribute(Widget.primary_key)
- assert_equal :integer, column_type.type
+ test "primary key with serial/integer are automatically numbered" do
+ @connection.create_table(:widgets, id: @pk_type, force: true)
+ widget = Widget.create!
+ assert_not_nil widget.id
+ end
- if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter)
- assert_equal 8, column_type.limit
+ test "schema dump primary key with serial/integer" do
+ @connection.create_table(:widgets, id: @pk_type, force: true)
+ schema = dump_table_schema "widgets"
+ assert_match %r{create_table "widgets", id: :#{@pk_type}, force: :cascade}, schema
end
if current_adapter?(:Mysql2Adapter)
- column = @connection.columns(:widgets).find { |c| c.name == "id" }
- assert column.auto_increment?
+ 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_equal :integer, column.type
+ assert_not column.bigint?
+ assert column.unsigned?
+
+ schema = dump_table_schema "widgets"
+ assert_match %r{create_table "widgets", id: :integer, unsigned: true, force: :cascade}, schema
+ end
+
+ 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_equal :integer, column.type
+ assert column.bigint?
+ assert column.unsigned?
+
+ schema = dump_table_schema "widgets"
+ assert_match %r{create_table "widgets", id: :bigint, unsigned: true, force: :cascade}, schema
+ end
end
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 4a49bfe9b1..494663eb04 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -50,32 +50,36 @@ class QueryCacheTest < ActiveRecord::TestCase
assert_cache :off
end
- def test_query_cache_across_threads
- ActiveRecord::Base.connection_pool.connections.each do |conn|
- assert_cache :off, conn
- end
-
- assert !ActiveRecord::Base.connection.nil?
- assert_cache :off
-
- middleware {
- assert_cache :clean
-
- Task.find 1
- assert_cache :dirty
+ private def with_temporary_connection_pool
+ old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
+ new_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new ActiveRecord::Base.connection_pool.spec
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = new_pool
- thread_1_connection = ActiveRecord::Base.connection
- ActiveRecord::Base.clear_active_connections!
- assert_cache :off, thread_1_connection
+ yield
+ ensure
+ ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = old_pool
+ end
- started = Concurrent::Event.new
- checked = Concurrent::Event.new
+ def test_query_cache_across_threads
+ with_temporary_connection_pool do
+ begin
+ if in_memory_db?
+ # Separate connections to an in-memory database create an entirely new database,
+ # with an empty schema etc, so we just stub out this schema on the fly.
+ ActiveRecord::Base.connection_pool.with_connection do |connection|
+ connection.create_table :tasks do |t|
+ t.datetime :starting
+ t.datetime :ending
+ end
+ end
+ ActiveRecord::FixtureSet.create_fixtures(self.class.fixture_path, ["tasks"], {}, ActiveRecord::Base)
+ end
- thread_2_connection = nil
- thread = Thread.new {
- thread_2_connection = ActiveRecord::Base.connection
+ ActiveRecord::Base.connection_pool.connections.each do |conn|
+ assert_cache :off, conn
+ end
- assert_equal thread_2_connection, thread_1_connection
+ assert !ActiveRecord::Base.connection.nil?
assert_cache :off
middleware {
@@ -84,29 +88,51 @@ class QueryCacheTest < ActiveRecord::TestCase
Task.find 1
assert_cache :dirty
- started.set
- checked.wait
-
+ thread_1_connection = ActiveRecord::Base.connection
ActiveRecord::Base.clear_active_connections!
- }.call({})
- }
+ assert_cache :off, thread_1_connection
+
+ started = Concurrent::Event.new
+ checked = Concurrent::Event.new
+
+ thread_2_connection = nil
+ thread = Thread.new {
+ thread_2_connection = ActiveRecord::Base.connection
+
+ assert_equal thread_2_connection, thread_1_connection
+ assert_cache :off
- started.wait
+ middleware {
+ assert_cache :clean
- thread_1_connection = ActiveRecord::Base.connection
- assert_not_equal thread_1_connection, thread_2_connection
- assert_cache :dirty, thread_2_connection
- checked.set
- thread.join
+ Task.find 1
+ assert_cache :dirty
- assert_cache :off, thread_2_connection
- }.call({})
+ started.set
+ checked.wait
- ActiveRecord::Base.connection_pool.connections.each do |conn|
- assert_cache :off, conn
+ ActiveRecord::Base.clear_active_connections!
+ }.call({})
+ }
+
+ started.wait
+
+ thread_1_connection = ActiveRecord::Base.connection
+ assert_not_equal thread_1_connection, thread_2_connection
+ assert_cache :dirty, thread_2_connection
+ checked.set
+ thread.join
+
+ assert_cache :off, thread_2_connection
+ }.call({})
+
+ ActiveRecord::Base.connection_pool.connections.each do |conn|
+ assert_cache :off, conn
+ end
+ ensure
+ ActiveRecord::Base.clear_all_connections!
+ end
end
- ensure
- ActiveRecord::Base.clear_all_connections!
end
def test_middleware_delegates
@@ -260,19 +286,51 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_cache_is_not_available_when_using_a_not_connected_connection
- spec_name = Task.connection_specification_name
- conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2")
- ActiveRecord::Base.connection_handler.establish_connection(conf)
- Task.connection_specification_name = "test2"
- refute Task.connected?
+ with_temporary_connection_pool do
+ spec_name = Task.connection_specification_name
+ conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2")
+ ActiveRecord::Base.connection_handler.establish_connection(conf)
+ Task.connection_specification_name = "test2"
+ refute Task.connected?
- Task.cache do
- Task.connection # warmup postgresql connection setup queries
- assert_queries(2) { Task.find(1); Task.find(1) }
+ Task.cache do
+ begin
+ if in_memory_db?
+ Task.connection.create_table :tasks do |t|
+ t.datetime :starting
+ t.datetime :ending
+ end
+ ActiveRecord::FixtureSet.create_fixtures(self.class.fixture_path, ["tasks"], {}, ActiveRecord::Base)
+ end
+ Task.connection # warmup postgresql connection setup queries
+ assert_queries(2) { Task.find(1); Task.find(1) }
+ ensure
+ ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
+ Task.connection_specification_name = spec_name
+ end
+ end
+ end
+ end
+
+ def test_query_cache_executes_new_queries_within_block
+ ActiveRecord::Base.connection.enable_query_cache!
+
+ # Warm up the cache by running the query
+ assert_queries(1) do
+ assert_equal 0, Post.where(title: "test").to_a.count
+ end
+
+ # Check that if the same query is run again, no queries are executed
+ assert_queries(0) do
+ assert_equal 0, Post.where(title: "test").to_a.count
+ end
+
+ ActiveRecord::Base.connection.uncached do
+ # Check that new query is executed, avoiding the cache
+ assert_queries(1) do
+ assert_equal 0, Post.where(title: "test").to_a.count
+ end
end
- ensure
- ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name)
- Task.connection_specification_name = spec_name
end
def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries
@@ -328,37 +386,44 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_query_cache_does_not_establish_connection_if_unconnected
- ActiveRecord::Base.clear_active_connections!
- refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
+ with_temporary_connection_pool do
+ ActiveRecord::Base.clear_active_connections!
+ refute 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"
- }.call({})
+ middleware {
+ refute 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"
+ refute 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
- ActiveRecord::Base.clear_active_connections!
- refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
+ with_temporary_connection_pool do
+ ActiveRecord::Base.clear_active_connections!
+ refute ActiveRecord::Base.connection_handler.active_connections? # sanity check
- middleware {
- assert ActiveRecord::Base.connection.query_cache_enabled, "QueryCache did not get lazily enabled"
- }.call({})
+ middleware {
+ assert ActiveRecord::Base.connection.query_cache_enabled, "QueryCache did not get lazily enabled"
+ }.call({})
+ end
end
def test_query_caching_is_local_to_the_current_thread
- ActiveRecord::Base.clear_active_connections!
+ with_temporary_connection_pool do
+ ActiveRecord::Base.clear_active_connections!
+
+ middleware {
+ assert ActiveRecord::Base.connection_pool.query_cache_enabled
+ assert ActiveRecord::Base.connection.query_cache_enabled
- middleware {
- assert ActiveRecord::Base.connection_pool.query_cache_enabled
- assert ActiveRecord::Base.connection.query_cache_enabled
+ Thread.new {
+ refute ActiveRecord::Base.connection_pool.query_cache_enabled
+ refute ActiveRecord::Base.connection.query_cache_enabled
+ }.join
+ }.call({})
- Thread.new {
- refute ActiveRecord::Base.connection_pool.query_cache_enabled
- refute ActiveRecord::Base.connection.query_cache_enabled
- }.join
- }.call({})
+ end
end
private
@@ -388,6 +453,10 @@ end
class QueryCacheExpiryTest < ActiveRecord::TestCase
fixtures :tasks, :posts, :categories, :categories_posts
+ def teardown
+ Task.connection.clear_query_cache
+ end
+
def test_cache_gets_cleared_after_migration
# warm the cache
Post.find(1)
@@ -463,4 +532,16 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
end
end
end
+
+ test "threads use the same connection" do
+ @connection_1 = ActiveRecord::Base.connection.object_id
+
+ thread_a = Thread.new do
+ @connection_2 = ActiveRecord::Base.connection.object_id
+ end
+
+ thread_a.join
+
+ assert_equal @connection_1, @connection_2
+ end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 0ef51272b9..c1c2efb9c8 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -258,7 +258,9 @@ class ReflectionTest < ActiveRecord::TestCase
[Post.reflect_on_association(:first_taggings).scope],
[Author.reflect_on_association(:misc_posts).scope]
]
- actual = Author.reflect_on_association(:misc_post_first_blue_tags).scope_chain
+ actual = assert_deprecated do
+ Author.reflect_on_association(:misc_post_first_blue_tags).scope_chain
+ end
assert_equal expected, actual
expected = [
@@ -270,7 +272,9 @@ class ReflectionTest < ActiveRecord::TestCase
[],
[]
]
- actual = Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
+ actual = assert_deprecated do
+ Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
+ end
assert_equal expected, actual
end
@@ -331,6 +335,15 @@ 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 }
@@ -395,15 +408,27 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_through_reflection_scope_chain_does_not_modify_other_reflections
- orig_conds = Post.reflect_on_association(:first_blue_tags_2).scope_chain.inspect
- Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
- assert_equal orig_conds, Post.reflect_on_association(:first_blue_tags_2).scope_chain.inspect
+ orig_conds = assert_deprecated do
+ Post.reflect_on_association(:first_blue_tags_2).scope_chain
+ end.inspect
+ assert_deprecated do
+ Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain
+ end
+ assert_equal orig_conds, assert_deprecated {
+ Post.reflect_on_association(:first_blue_tags_2).scope_chain
+ }.inspect
end
def test_symbol_for_class_name
assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass
end
+ def test_class_for_class_name
+ assert_deprecated do
+ assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { class_name: Client }, Firm), :validate?
+ end
+ end
+
def test_join_table
category = Struct.new(:table_name, :pluralize_table_names).new("categories", true)
product = Struct.new(:table_name, :pluralize_table_names).new("products", true)
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index d2382b9bb2..49d4aeafc9 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -32,7 +32,8 @@ module ActiveRecord
:exclude?, :find_all, :flat_map, :group_by, :include?, :length,
:map, :none?, :one?, :partition, :reject, :reverse,
:sample, :second, :sort, :sort_by, :third,
- :to_ary, :to_set, :to_xml, :to_yaml, :join
+ :to_ary, :to_set, :to_xml, :to_yaml, :join,
+ :in_groups, :in_groups_of, :to_sentence, :to_formatted_s
]
ARRAY_DELEGATES.each do |method|
diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb
index 2cbbc775ce..4f92f71a09 100644
--- a/activerecord/test/cases/relation/mutation_test.rb
+++ b/activerecord/test/cases/relation/mutation_test.rb
@@ -3,7 +3,7 @@ require "models/post"
module ActiveRecord
class RelationMutationTest < ActiveRecord::TestCase
- class FakeKlass < Struct.new(:table_name, :name)
+ FakeKlass = Struct.new(:table_name, :name) do
extend ActiveRecord::Delegation::DelegateCache
inherited self
@@ -108,7 +108,7 @@ module ActiveRecord
end
test "#reorder!" do
- @relation = self.relation.order("foo")
+ @relation = relation.order("foo")
assert relation.reorder!("bar").equal?(relation)
assert_equal ["bar"], relation.order_values
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index d5af0cc9a5..1241bb54a4 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -8,7 +8,7 @@ module ActiveRecord
class RelationTest < ActiveRecord::TestCase
fixtures :posts, :comments, :authors
- class FakeKlass < Struct.new(:table_name, :name)
+ FakeKlass = Struct.new(:table_name, :name) do
extend ActiveRecord::Delegation::DelegateCache
inherited self
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 9519fec0c4..0c94e891eb 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1617,9 +1617,9 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "David", topic2.reload.author_name
end
- def test_update_on_relation_passing_active_record_object_is_deprecated
+ def test_update_on_relation_passing_active_record_object_is_not_permitted
topic = Topic.create!(title: "Foo", author_name: nil)
- assert_deprecated(/update/) do
+ assert_raises(ArgumentError) do
Topic.where(id: topic.id).update(topic, title: "Bar")
end
end
@@ -1935,6 +1935,18 @@ class RelationTest < ActiveRecord::TestCase
assert !Post.all.respond_to?(:by_lifo)
end
+ def test_unscope_with_subquery
+ p1 = Post.where(id: 1)
+ p2 = Post.where(id: 2)
+
+ assert_not_equal p1, p2
+
+ comments = Comment.where(post: p1).unscope(where: :post_id).where(post: p2)
+
+ assert_not_equal p1.first.comments, comments
+ assert_equal p2.first.comments, comments
+ end
+
def test_unscope_removes_binds
left = Post.where(id: Arel::Nodes::BindParam.new)
column = Post.columns_hash["id"]
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index bea78d2a95..9584318e86 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -178,24 +178,20 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dumps_index_columns_in_right_order
- index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_index/).first.strip
if current_adapter?(:PostgreSQLAdapter)
- assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }, using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", order: { rating: :desc }', index_definition
elsif current_adapter?(:Mysql2Adapter)
- assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }, using: :btree', index_definition
+ assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", length: { type: 10 }', index_definition
else
assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition
end
end
def test_schema_dumps_partial_indices
- index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip
- if current_adapter?(:PostgreSQLAdapter)
- assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition
- elsif current_adapter?(:Mysql2Adapter)
- assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition
- elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
- assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_partial_index/).first.strip
+ if current_adapter?(:PostgreSQLAdapter, :SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index?
+ assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)"', index_definition
else
assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition
end
@@ -248,9 +244,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
def test_schema_dumps_index_type
- output = standard_dump
- assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
- assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
+ output = dump_table_schema "key_tests"
+ assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext$}, output
+ assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza"$}, output
end
end
@@ -261,23 +257,34 @@ class SchemaDumperTest < ActiveRecord::TestCase
if current_adapter?(:PostgreSQLAdapter)
def test_schema_dump_includes_bigint_default
- output = standard_dump
+ output = dump_table_schema "defaults"
assert_match %r{t\.bigint\s+"bigint_default",\s+default: 0}, output
end
def test_schema_dump_includes_limit_on_array_type
- output = standard_dump
+ output = dump_table_schema "bigint_array"
assert_match %r{t\.bigint\s+"big_int_data_points\",\s+array: true}, output
end
def test_schema_dump_allows_array_of_decimal_defaults
- output = standard_dump
+ output = dump_table_schema "bigint_array"
assert_match %r{t\.decimal\s+"decimal_array_default",\s+default: \["1.23", "3.45"\],\s+array: true}, output
end
def test_schema_dump_expression_indices
- index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
- assert_equal 't.index "lower((name)::text)", name: "company_expression_index", using: :btree', index_definition
+ index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
+ assert_equal 't.index "lower((name)::text)", name: "company_expression_index"', index_definition
+ end
+
+ def test_schema_dump_interval_type
+ output = dump_table_schema "postgresql_times"
+ assert_match %r{t\.interval\s+"time_interval"$}, output
+ assert_match %r{t\.interval\s+"scaled_time_interval",\s+precision: 6$}, output
+ end
+
+ def test_schema_dump_oid_type
+ output = dump_table_schema "postgresql_oids"
+ assert_match %r{t\.oid\s+"obj_id"$}, output
end
if ActiveRecord::Base.connection.supports_extensions?
@@ -341,9 +348,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
create_table("dogs") do |t|
t.column :name, :string
- t.column :owner_id, :bigint
+ t.references :owner
t.index [:name]
- t.foreign_key :dog_owners, column: "owner_id" if supports_foreign_keys?
+ t.foreign_key :dog_owners, column: "owner_id"
end
end
def down
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 3a04f4bf7d..14fb2fbbfa 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -10,6 +10,8 @@ require "concurrent/atomic/cyclic_barrier"
class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts, :comments
+ self.use_transactional_tests = false
+
def test_default_scope
expected = Developer.all.merge!(order: "salary DESC").to_a.collect(&:salary)
received = DeveloperOrderedBySalary.all.collect(&:salary)
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index a469da0a5b..673392b4c4 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -240,6 +240,20 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal [], light.long_state
end
+ def test_unexpected_serialized_type
+ Topic.serialize :content, Hash
+ topic = Topic.create!(content: { zomg: true })
+
+ Topic.serialize :content, Array
+
+ topic.reload
+ error = assert_raise(ActiveRecord::SerializationTypeMismatch) do
+ topic.content
+ end
+ expected = "can't load `content`: was supposed to be a Array, but was a Hash. -- {:zomg=>true}"
+ assert_equal expected, error.to_s
+ end
+
def test_serialized_column_should_unserialize_after_update_column
t = Topic.create(content: "first")
assert_equal("first", t.content)
diff --git a/activerecord/test/cases/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb
index f45f63c68e..fab3648564 100644
--- a/activerecord/test/cases/statement_cache_test.rb
+++ b/activerecord/test/cases/statement_cache_test.rb
@@ -105,5 +105,31 @@ module ActiveRecord
refute_equal book, other_book
end
+
+ def test_find_by_does_not_use_statement_cache_if_table_name_is_changed
+ book = Book.create(name: "my book")
+
+ Book.find_by(name: book.name) # warming the statement cache.
+
+ # changing the table name should change the query that is not cached.
+ Book.table_name = :birds
+ assert_nil Book.find_by(name: book.name)
+ ensure
+ Book.table_name = :books
+ end
+
+ def test_find_does_not_use_statement_cache_if_table_name_is_changed
+ book = Book.create(name: "my book")
+
+ Book.find(book.id) # warming the statement cache.
+
+ # changing the table name should change the query that is not cached.
+ Book.table_name = :birds
+ assert_raise ActiveRecord::RecordNotFound do
+ Book.find(book.id)
+ end
+ ensure
+ Book.table_name = :books
+ end
end
end
diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb
index f7c53b5801..5653fd83fd 100644
--- a/activerecord/test/cases/tasks/database_tasks_test.rb
+++ b/activerecord/test/cases/tasks/database_tasks_test.rb
@@ -100,6 +100,8 @@ module ActiveRecord
@configurations = { "development" => { "database" => "my-db" } }
ActiveRecord::Base.stubs(:configurations).returns(@configurations)
+ # To refrain from connecting to a newly created empty DB in sqlite3_mem tests
+ ActiveRecord::Base.connection_handler.stubs(:establish_connection)
end
def test_ignores_configurations_without_databases
diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb
index 03f6c234e8..09c585167e 100644
--- a/activerecord/test/cases/time_precision_test.rb
+++ b/activerecord/test/cases/time_precision_test.rb
@@ -68,7 +68,7 @@ if subsecond_precision_supported?
assert_match %r{t\.time\s+"finish",\s+precision: 6$}, output
end
- if current_adapter?(:PostgreSQLAdapter)
+ if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter)
def test_time_precision_with_zero_should_be_dumped
@connection.create_table(:foos, force: true) do |t|
t.time :start, precision: 0
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 7766a74612..39b40e3411 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -422,7 +422,7 @@ class TimestampTest < ActiveRecord::TestCase
self.table_name = "people"
before_create do
- self.born_at = self.created_at
+ self.born_at = created_at
end
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 9b1cca8583..111495c481 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -206,16 +206,6 @@ class TransactionTest < ActiveRecord::TestCase
assert_equal posts_count, author.posts.reload.size
end
- def test_cancellation_from_returning_false_in_before_filter
- def @first.before_save_for_transaction
- false
- end
-
- assert_deprecated do
- @first.save
- end
- end
-
def test_cancellation_from_before_destroy_rollbacks_in_destroy
add_cancelling_before_destroy_with_db_side_effect_to_topic @first
nbooks_before_destroy = Book.count
@@ -279,7 +269,11 @@ class TransactionTest < ActiveRecord::TestCase
e = assert_raises(RuntimeError) { new_topic.save }
assert_equal "Make the transaction rollback", e.message
assert_equal new_record_snapshot, !new_topic.persisted?, "The topic should have its old persisted value"
- assert_equal id_snapshot, new_topic.id, "The topic should have its old id"
+ if id_snapshot.nil?
+ assert_nil new_topic.id, "The topic should have its old id"
+ else
+ assert_equal id_snapshot, new_topic.id, "The topic should have its old id"
+ end
assert_equal id_present, new_topic.has_attribute?(Topic.primary_key)
end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 6d22638592..277280b42e 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -372,7 +372,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
e2 = Event.create(title: "abcdefgh")
assert_not e2.valid?, "Created an event whose title is not unique"
- elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
assert_raise(ActiveRecord::ValueTooLong) do
Event.create(title: "abcdefgh")
end
@@ -391,7 +391,7 @@ class UniquenessValidationTest < ActiveRecord::TestCase
e2 = Event.create(title: "一二三四五六七八")
assert_not e2.valid?, "Created an event whose title is not unique"
- elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
+ elsif current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter)
assert_raise(ActiveRecord::ValueTooLong) do
Event.create(title: "一二三四五六七八")
end
diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb
index d055968a56..07288568e8 100644
--- a/activerecord/test/cases/view_test.rb
+++ b/activerecord/test/cases/view_test.rb
@@ -154,7 +154,7 @@ if ActiveRecord::Base.connection.supports_views?
end
# sqlite dose not support CREATE, INSERT, and DELETE for VIEW
- if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)
+ if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)
class UpdateableViewTest < ActiveRecord::TestCase
self.use_transactional_tests = false
fixtures :books
@@ -200,7 +200,7 @@ if ActiveRecord::Base.connection.supports_views?
end
end
end
- end # end of `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)`
+ end # end of `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)`
end # end of `if ActiveRecord::Base.connection.supports_views?`
if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) &&
diff --git a/activerecord/test/fixtures/subscribers.yml b/activerecord/test/fixtures/subscribers.yml
index c6a8c2fa24..0f6e0cd48e 100644
--- a/activerecord/test/fixtures/subscribers.yml
+++ b/activerecord/test/fixtures/subscribers.yml
@@ -6,6 +6,6 @@ second:
nick: webster132
name: David Heinemeier Hansson
-thrid:
+third:
nick: swistak
name: Marcin Raczkowski \ No newline at end of file
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 4561b3132b..20e37710e7 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -216,14 +216,12 @@ class Account < ActiveRecord::Base
validate :check_empty_credit_limit
- protected
+ private
def check_empty_credit_limit
errors.add("credit_limit", :blank) if credit_limit.blank?
end
- private
-
def private_method
"Sir, yes sir!"
end
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index 60af7c2247..3d40cb1ace 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -56,7 +56,7 @@ class GpsLocation
end
def ==(other)
- self.latitude == other.latitude && self.longitude == other.longitude
+ latitude == other.latitude && longitude == other.longitude
end
end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index ea4f719517..654830ba11 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -260,3 +260,9 @@ class CachedDeveloper < ActiveRecord::Base
self.table_name = "developers"
self.cache_timestamp_format = :number
end
+
+class DeveloperWithIncorrectlyOrderedHasManyThrough < ActiveRecord::Base
+ self.table_name = "developers"
+ has_many :companies, through: :contracts
+ has_many :contracts, foreign_key: :developer_id
+end
diff --git a/activerecord/test/models/family.rb b/activerecord/test/models/family.rb
new file mode 100644
index 0000000000..5ae5a78c95
--- /dev/null
+++ b/activerecord/test/models/family.rb
@@ -0,0 +1,4 @@
+class Family < ActiveRecord::Base
+ has_many :family_trees, -> { where(token: nil) }
+ has_many :members, through: :family_trees
+end
diff --git a/activerecord/test/models/family_tree.rb b/activerecord/test/models/family_tree.rb
new file mode 100644
index 0000000000..cd9829fedd
--- /dev/null
+++ b/activerecord/test/models/family_tree.rb
@@ -0,0 +1,4 @@
+class FamilyTree < ActiveRecord::Base
+ belongs_to :member, class_name: "User", foreign_key: "member_id"
+ belongs_to :family
+end
diff --git a/activerecord/test/models/non_primary_key.rb b/activerecord/test/models/non_primary_key.rb
new file mode 100644
index 0000000000..1cafb09608
--- /dev/null
+++ b/activerecord/test/models/non_primary_key.rb
@@ -0,0 +1,2 @@
+class NonPrimaryKey < ActiveRecord::Base
+end
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index 5009f8f54b..4fbd986e40 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -11,7 +11,7 @@ class Project < ActiveRecord::Base
after_add: Proc.new { |o, r| o.developers_log << "after_adding#{r.id || '<new>'}" },
before_remove: Proc.new { |o, r| o.developers_log << "before_removing#{r.id}" },
after_remove: Proc.new { |o, r| o.developers_log << "after_removing#{r.id}" }
- has_and_belongs_to_many :well_payed_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, class_name: "Developer"
+ has_and_belongs_to_many :well_paid_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, class_name: "Developer"
belongs_to :firm
has_one :lead_developer, through: :firm, inverse_of: :contracted_projects
diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb
index 47649e0a77..5089a795f4 100644
--- a/activerecord/test/models/user.rb
+++ b/activerecord/test/models/user.rb
@@ -5,8 +5,12 @@ class User < ActiveRecord::Base
has_secure_token :auth_token
has_and_belongs_to_many :jobs_pool,
- class_name: Job,
+ class_name: "Job",
join_table: "jobs_pool"
+
+ has_one :family_tree, -> { where(token: nil) }, foreign_key: "member_id"
+ has_one :family, through: :family_tree
+ has_many :family_members, through: :family, source: :members
end
class UserWithNotification < User
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 15ba2d67ab..860c63b27c 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -23,16 +23,24 @@ ActiveRecord::Schema.define do
t.string :char2, limit: 50, default: "a varchar field"
t.text :char3, default: "a text field"
t.bigint :bigint_default, default: -> { "0::bigint" }
- t.text :multiline_default, default: '--- []
+ t.text :multiline_default, default: "--- []
-'
+"
end
- %w(postgresql_times postgresql_oids postgresql_timestamp_with_zones
- postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
- drop_table table_name, if_exists: true
+ create_table :postgresql_times, force: true do |t|
+ t.interval :time_interval
+ t.interval :scaled_time_interval, precision: 6
end
+ create_table :postgresql_oids, force: true do |t|
+ t.oid :obj_id
+ end
+
+ drop_table "postgresql_timestamp_with_zones", if_exists: true
+ drop_table "postgresql_partitioned_table", if_exists: true
+ drop_table "postgresql_partitioned_table_parent", if_exists: true
+
execute "DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE"
execute "CREATE SEQUENCE companies_nonstd_seq START 101 OWNED BY companies.id"
execute "ALTER TABLE companies ALTER COLUMN id SET DEFAULT nextval('companies_nonstd_seq')"
@@ -45,21 +53,6 @@ ActiveRecord::Schema.define do
end
execute <<_SQL
- CREATE TABLE postgresql_times (
- id SERIAL PRIMARY KEY,
- time_interval INTERVAL,
- scaled_time_interval INTERVAL(6)
- );
-_SQL
-
- execute <<_SQL
- CREATE TABLE postgresql_oids (
- id SERIAL PRIMARY KEY,
- obj_id OID
- );
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_timestamp_with_zones (
id SERIAL PRIMARY KEY,
time TIMESTAMP WITH TIME ZONE
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index ba6f5de894..08bef08abc 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -54,8 +54,8 @@ ActiveRecord::Schema.define do
create_table :authors, force: true do |t|
t.string :name, null: false
- t.bigint :author_address_id
- t.integer :author_address_extra_id
+ t.references :author_address
+ t.references :author_address_extra
t.string :organization_id
t.string :owned_essay_id
end
@@ -88,7 +88,7 @@ ActiveRecord::Schema.define do
end
create_table :books, force: true do |t|
- t.integer :author_id
+ t.references :author
t.string :format
t.column :name, :string
t.column :status, :integer, default: 0
@@ -201,7 +201,7 @@ ActiveRecord::Schema.define do
t.integer :account_id
t.string :description, default: ""
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 [:firm_id, :type], name: "company_partial_index", where: "(rating > 10)"
t.index :name, name: "company_name_index", using: :btree
t.index "lower(name)", name: "company_expression_index" if supports_expression_index?
end
@@ -306,7 +306,7 @@ ActiveRecord::Schema.define do
end
create_table :engines, force: true do |t|
- t.bigint :car_id
+ t.references :car, index: false
end
create_table :entrants, force: true do |t|
@@ -329,6 +329,15 @@ ActiveRecord::Schema.define do
create_table :eyes, force: true do |t|
end
+ create_table :families, force: true do |t|
+ end
+
+ create_table :family_trees, force: true do |t|
+ t.references :family
+ t.references :member
+ t.string :token
+ end
+
create_table :funny_jokes, force: true do |t|
t.string :name
end
@@ -655,8 +664,8 @@ ActiveRecord::Schema.define do
end
create_table :posts, force: true do |t|
- t.integer :author_id
- t.string :title, null: false
+ t.references :author
+ t.string :title, null: false
# use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in
# Oracle SELECT WHERE clause which causes many unit test failures
if current_adapter?(:OracleAdapter)
@@ -1005,16 +1014,14 @@ ActiveRecord::Schema.define do
create_table :records, force: true do |t|
end
- if supports_foreign_keys?
- # fk_test_has_fk should be before fk_test_has_pk
- create_table :fk_test_has_fk, force: true do |t|
- t.bigint :fk_id, null: false
+ disable_referential_integrity do
+ create_table :fk_test_has_pk, primary_key: "pk_id", force: :cascade do |t|
end
- create_table :fk_test_has_pk, force: true, primary_key: "pk_id" do |t|
+ create_table :fk_test_has_fk, force: true do |t|
+ t.references :fk, null: false
+ t.foreign_key :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id"
end
-
- add_foreign_key :fk_test_has_fk, :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id"
end
create_table :overloaded_types, force: true do |t|
@@ -1032,6 +1039,10 @@ ActiveRecord::Schema.define do
create_table :test_with_keyword_column_name, force: true do |t|
t.string :desc
end
+
+ create_table :non_primary_keys, force: true, id: false do |t|
+ t.integer :id
+ end
end
Course.connection.create_table :courses, force: true do |t|
diff --git a/activerecord/test/schema/sqlite_specific_schema.rb b/activerecord/test/schema/sqlite_specific_schema.rb
deleted file mode 100644
index cc7c36fe2b..0000000000
--- a/activerecord/test/schema/sqlite_specific_schema.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-ActiveRecord::Schema.define do
- execute "DROP TABLE fk_test_has_fk" rescue nil
- execute "DROP TABLE fk_test_has_pk" rescue nil
- execute <<_SQL
- CREATE TABLE 'fk_test_has_pk' (
- 'pk_id' INTEGER NOT NULL PRIMARY KEY
- );
-_SQL
-
- execute <<_SQL
- CREATE TABLE 'fk_test_has_fk' (
- 'id' INTEGER NOT NULL PRIMARY KEY,
- 'fk_id' INTEGER NOT NULL,
-
- FOREIGN KEY ('fk_id') REFERENCES 'fk_test_has_pk'('pk_id')
- );
-_SQL
-end
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 873a39dbf6..7962427032 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,75 @@
+* Cache `ActiveSupport::TimeWithZone#to_datetime` before freezing.
+
+ *Adam Rice*
+
+* Deprecate `.halt_callback_chains_on_return_false`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated behavior that halts callbacks when the return is false.
+
+ *Rafael Mendonça França*
+
+* Deprecate passing string to `:if` and `:unless` conditional options
+ on `set_callback` and `skip_callback`.
+
+ *Ryuta Kamizono*
+
+* Raise `ArgumentError` when passing string to define callback.
+
+ *Ryuta Kamizono*
+
+* Updated Unicode version to 9.0.0
+
+ Now we can handle new emojis such like "👩‍👩‍👧‍👦" ("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}").
+
+ version 8.0.0
+
+ "👩‍👩‍👧‍👦".mb_chars.grapheme_length # => 4
+ "👩‍👩‍👧‍👦".mb_chars.reverse # => "👦👧‍👩‍👩‍"
+
+ version 9.0.0
+
+ "👩‍👩‍👧‍👦".mb_chars.grapheme_length # => 1
+ "👩‍👩‍👧‍👦".mb_chars.reverse # => "👩‍👩‍👧‍👦"
+
+ *Fumiaki MATSUSHIMA*
+
+* Changed `ActiveSupport::Inflector#transliterate` to raise `ArgumentError` when it receives
+ anything except a string.
+
+ *Kevin McPhillips*
+
+* Fixed bugs that `StringInquirer#respond_to_missing?` and
+ `ArrayInquirer#respond_to_missing?` do not fallback to `super`.
+
+ *Akira Matsuda*
+
+* Fix inconsistent results when parsing large durations and constructing durations from code
+
+ ActiveSupport::Duration.parse('P3Y') == 3.years # It should be true
+
+ Duration parsing made independent from any moment of time:
+ Fixed length in seconds is assigned to each duration part during parsing.
+
+ Changed duration of months and years in seconds to more accurate and logical:
+
+ 1. The value of 365.2425 days in Gregorian year is more accurate
+ as it accounts for every 400th non-leap year.
+
+ 2. Month's length is bound to year's duration, which makes
+ sensible comparisons like `12.months == 1.year` to be `true`
+ and nonsensical ones like `30.days == 1.month` to be `false`.
+
+ Calculations on times and dates with durations shouldn't be affected as
+ duration's numeric value isn't used in calculations, only parts are used.
+
+ Methods on `Numeric` like `2.days` now use these predefined durations
+ to avoid duplicating of duration constants through the codebase and
+ eliminate creation of intermediate durations.
+
+ *Andrey Novikov, Andrew White*
+
* Change return value of `Rational#duplicable?`, `ComplexClass#duplicable?`
to false.
@@ -9,76 +81,76 @@
*Yuji Yaginuma*
-* Remove deprecated class `ActiveSupport::Concurrency::Latch`
+* Remove deprecated class `ActiveSupport::Concurrency::Latch`.
*Andrew White*
-* Remove deprecated separator argument from `parameterize`
+* Remove deprecated separator argument from `parameterize`.
*Andrew White*
-* Remove deprecated method `Numeric#to_formatted_s`
+* Remove deprecated method `Numeric#to_formatted_s`.
*Andrew White*
-* Remove deprecated method `alias_method_chain`
+* Remove deprecated method `alias_method_chain`.
*Andrew White*
-* Remove deprecated constant `MissingSourceFile`
+* Remove deprecated constant `MissingSourceFile`.
*Andrew White*
* Remove deprecated methods `Module.qualified_const_defined?`,
- `Module.qualified_const_get` and `Module.qualified_const_set`
+ `Module.qualified_const_get` and `Module.qualified_const_set`.
*Andrew White*
-* Remove deprecated `:prefix` option from `number_to_human_size`
+* Remove deprecated `:prefix` option from `number_to_human_size`.
*Andrew White*
-* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`
+* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`.
*Andrew White*
-* Remove deprecated file `active_support/core_ext/time/marshal.rb`
+* Remove deprecated file `active_support/core_ext/time/marshal.rb`.
*Andrew White*
-* Remove deprecated file `active_support/core_ext/struct.rb`
+* Remove deprecated file `active_support/core_ext/struct.rb`.
*Andrew White*
-* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb`
+* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb`.
*Andrew White*
-* Remove deprecated method `Module.local_constants`
+* Remove deprecated method `Module.local_constants`.
*Andrew White*
-* Remove deprecated file `active_support/core_ext/kernel/debugger.rb`
+* Remove deprecated file `active_support/core_ext/kernel/debugger.rb`.
*Andrew White*
-* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key`
+* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key`.
*Andrew White*
-* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value`
+* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value`.
*Andrew White*
-* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key`
+* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key`.
*Andrew White*
-* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path`
+* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path`.
*Andrew White*
-* Ensure duration parsing is consistent across DST changes
+* Ensure duration parsing is consistent across DST changes.
Previously `ActiveSupport::Duration.parse` used `Time.current` and
`Time#advance` to calculate the number of seconds in the duration
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 5d912f375c..aa36a01b5b 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -8,6 +8,7 @@ end
require "open-uri"
require "tmpdir"
+require "fileutils"
module ActiveSupport
module Multibyte
@@ -101,9 +102,10 @@ module ActiveSupport
def parse
SOURCES.each do |type, url|
- filename = File.join(Dir.tmpdir, "#{url.split('/').last}")
+ filename = File.join(Dir.tmpdir, UNICODE_VERSION, "#{url.split('/').last}")
unless File.exist?(filename)
$stderr.puts "Downloading #{url.split('/').last}"
+ FileUtils.mkdir_p(File.dirname(filename))
File.open(filename, "wb") do |target|
open(url) do |source|
source.each_line { |line| target.write line }
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 267fa755c6..03e3ce821a 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -80,11 +80,15 @@ module ActiveSupport
cattr_accessor :test_order # :nodoc:
def self.halt_callback_chains_on_return_false
- Callbacks.halt_and_display_warning_on_return_false
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ ActiveSupport.halt_callback_chains_on_return_false is deprecated and will be removed in Rails 5.2.
+ MSG
end
def self.halt_callback_chains_on_return_false=(value)
- Callbacks.halt_and_display_warning_on_return_false = value
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ ActiveSupport.halt_callback_chains_on_return_false= is deprecated and will be removed in Rails 5.2.
+ MSG
end
def self.to_time_preserves_timezone
diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb
index 85122e39b2..befa1746c6 100644
--- a/activesupport/lib/active_support/array_inquirer.rb
+++ b/activesupport/lib/active_support/array_inquirer.rb
@@ -20,7 +20,7 @@ module ActiveSupport
# variants.any?(:phone, :tablet) # => true
# variants.any?('phone', 'desktop') # => true
# variants.any?(:desktop, :watch) # => false
- def any?(*candidates, &block)
+ def any?(*candidates)
if candidates.none?
super
else
@@ -32,7 +32,7 @@ module ActiveSupport
private
def respond_to_missing?(name, include_private = false)
- name[-1] == "?"
+ (name[-1] == "?") || super
end
def method_missing(name, *args)
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index 169a58ecd1..e47c90597f 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -12,7 +12,7 @@ module ActiveSupport
# is to exclude the output of a noisy library from the backtrace, so that you
# can focus on the rest.
#
- # bc = BacktraceCleaner.new
+ # bc = ActiveSupport::BacktraceCleaner.new
# bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix
# bc.add_silencer { |line| line =~ /puma|rubygems/ } # skip any lines from puma or rubygems
# bc.clean(exception.backtrace) # perform the cleanup
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index d1bbd2f405..4d8c2046e8 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -6,7 +6,6 @@ require "active_support/core_ext/numeric/bytes"
require "active_support/core_ext/numeric/time"
require "active_support/core_ext/object/to_param"
require "active_support/core_ext/string/inflections"
-require "active_support/core_ext/string/strip"
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
@@ -71,8 +70,8 @@ module ActiveSupport
# each of elements in the array will be turned into parameters/keys and
# concatenated into a single key. For example:
#
- # expand_cache_key([:foo, :bar]) # => "foo/bar"
- # expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar"
+ # ActiveSupport::Cache.expand_cache_key([:foo, :bar]) # => "foo/bar"
+ # ActiveSupport::Cache.expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar"
#
# The +key+ argument can also respond to +cache_key+ or +to_param+.
def expand_cache_key(key, namespace = nil)
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index e6c79f2a38..ea71569ca8 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -4,7 +4,6 @@ require "active_support/core_ext/array/extract_options"
require "active_support/core_ext/class/attribute"
require "active_support/core_ext/kernel/reporting"
require "active_support/core_ext/kernel/singleton_class"
-require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/string/filters"
require "active_support/deprecation"
require "thread"
@@ -69,12 +68,6 @@ module ActiveSupport
CALLBACK_FILTER_TYPES = [:before, :after, :around]
- # If true, Active Record and Active Model callbacks returning +false+ will
- # halt the entire callback chain and display a deprecation message.
- # If false, callback chains will only be halted by calling +throw :abort+.
- # Defaults to +true+.
- mattr_accessor(:halt_and_display_warning_on_return_false, instance_writer: false) { true }
-
# Runs the callbacks for the given event.
#
# Calls the before and around callbacks in the order they were set, yields
@@ -286,9 +279,9 @@ module ActiveSupport
class Callback #:nodoc:#
def self.build(chain, filter, kind, options)
if filter.is_a?(String)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing string to define callback is deprecated and will be removed
- in Rails 5.1 without replacement.
+ raise ArgumentError, <<-MSG.squish
+ Passing string to define a callback is not supported. See the `.set_callback`
+ documentation to see supported values.
MSG
end
@@ -643,9 +636,8 @@ module ActiveSupport
# set_callback :save, :before_method
#
# The callback can be specified as a symbol naming an instance method; as a
- # proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
- # object that responds to a certain method determined by the <tt>:scope</tt>
- # argument to +define_callbacks+.
+ # proc, lambda, or block; or as an object that responds to a certain method
+ # determined by the <tt>:scope</tt> argument to +define_callbacks+.
#
# If a proc, lambda, or block is given, its body is evaluated in the context
# of the current object. It can also optionally accept the current object as
@@ -659,16 +651,24 @@ module ActiveSupport
#
# ===== Options
#
- # * <tt>:if</tt> - A symbol, a string or an array of symbols and strings,
+ # * <tt>:if</tt> - A symbol, a string (deprecated) or an array of symbols,
# each naming an instance method or a proc; the callback will be called
# only when they all return a true value.
- # * <tt>:unless</tt> - A symbol, a string or an array of symbols and
- # strings, each naming an instance method or a proc; the callback will
- # be called only when they all return a false value.
+ # * <tt>:unless</tt> - A symbol, a string (deprecated) or an array of symbols,
+ # each naming an instance method or a proc; the callback will be called
+ # only when they all return a false value.
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
# existing chain rather than appended.
def set_callback(name, *filter_list, &block)
type, filters, options = normalize_callback_params(filter_list, block)
+
+ if options[:if].is_a?(String) || options[:unless].is_a?(String)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing string to :if and :unless conditional options is deprecated
+ and will be removed in Rails 5.2 without replacement.
+ MSG
+ end
+
self_chain = get_callbacks name
mapped = filters.map do |filter|
Callback.build(self_chain, filter, type, options)
@@ -692,6 +692,14 @@ module ActiveSupport
# already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
def skip_callback(name, *filter_list, &block)
type, filters, options = normalize_callback_params(filter_list, block)
+
+ if options[:if].is_a?(String) || options[:unless].is_a?(String)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing string to :if and :unless conditional options is deprecated
+ and will be removed in Rails 5.2 without replacement.
+ MSG
+ end
+
options[:raise] = true unless options.key?(:raise)
__update_callbacks(name) do |target, chain|
@@ -841,30 +849,6 @@ module ActiveSupport
def set_callbacks(name, callbacks) # :nodoc:
self.__callbacks = __callbacks.merge(name.to_sym => callbacks)
end
-
- def deprecated_false_terminator # :nodoc:
- Proc.new do |target, result_lambda|
- terminate = true
- catch(:abort) do
- result = result_lambda.call if result_lambda.is_a?(Proc)
- if Callbacks.halt_and_display_warning_on_return_false && result == false
- display_deprecation_warning_for_false_terminator
- else
- terminate = false
- end
- end
- terminate
- end
- end
-
- private
-
- def display_deprecation_warning_for_false_terminator
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1.
- To explicitly halt the callback chain, please use `throw :abort` instead.
- MSG
- end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb
index 52706c3d7a..f397f658f3 100644
--- a/activesupport/lib/active_support/core_ext.rb
+++ b/activesupport/lib/active_support/core_ext.rb
@@ -1,4 +1,3 @@
-DEPRECATED_FILES = ["#{File.dirname(__FILE__)}/core_ext/struct.rb"]
-(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"] - DEPRECATED_FILES).each do |path|
+(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"]).each do |path|
require path
end
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 37d833887a..fca33c9d69 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -31,7 +31,7 @@ class Array
#
# people = ["David", "Rafael", "Aaron", "Todd"]
# people.without "Aaron", "Todd"
- # => ["David", "Rafael"]
+ # # => ["David", "Rafael"]
#
# Note: This is an optimization of `Enumerable#without` that uses `Array#-`
# instead of `Array#reject` for performance reasons.
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index ea9d85f6e3..0d798e5c4e 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -89,7 +89,7 @@ class Array
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
def split(value = nil)
- arr = self.dup
+ arr = dup
result = []
if block_given?
while (idx = arr.index { |i| yield i })
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 2e64097933..d6f60cac04 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -133,7 +133,7 @@ class Date
# Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there.
def compare_with_coercion(other)
if other.is_a?(Time)
- self.to_datetime <=> other
+ to_datetime <=> other
else
compare_without_coercion(other)
end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index ae08fd90c1..90d7d2947f 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -12,7 +12,7 @@ module Enumerable
#
# [5, 15, 10].sum # => 30
# ['foo', 'bar'].sum # => "foobar"
- # [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5]
+ # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]
#
# The default sum of an empty list is zero. You can override this default:
#
@@ -29,9 +29,9 @@ module Enumerable
# Convert an enumerable to a hash.
#
# people.index_by(&:login)
- # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
+ # # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
# people.index_by { |person| "#{person.first_name} #{person.last_name}" }
- # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
+ # # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
def index_by
if block_given?
result = {}
@@ -67,10 +67,10 @@ module Enumerable
# Returns a copy of the enumerable without the specified elements.
#
# ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd"
- # => ["David", "Rafael"]
+ # # => ["David", "Rafael"]
#
# {foo: 1, bar: 2, baz: 3}.without :bar
- # => {foo: 1, baz: 3}
+ # # => {foo: 1, baz: 3}
def without(*elements)
reject { |element| elements.include?(element) }
end
@@ -78,10 +78,10 @@ module Enumerable
# Convert an enumerable to an array based on the given key.
#
# [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
- # => ["David", "Rafael", "Aaron"]
+ # # => ["David", "Rafael", "Aaron"]
#
# [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)
- # => [[1, "David"], [2, "Rafael"]]
+ # # => [[1, "David"], [2, "Rafael"]]
def pluck(*keys)
if keys.many?
map { |element| keys.map { |key| element[key] } }
diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb
index 4a64872392..74baae3639 100644
--- a/activesupport/lib/active_support/core_ext/integer/time.rb
+++ b/activesupport/lib/active_support/core_ext/integer/time.rb
@@ -18,12 +18,12 @@ class Integer
# # equivalent to Time.now.advance(months: 4, years: 5)
# (4.months + 5.years).from_now
def months
- ActiveSupport::Duration.new(self * 30.days, [[:months, self]])
+ ActiveSupport::Duration.months(self)
end
alias :month :months
def years
- ActiveSupport::Duration.new(self * 365.25.days.to_i, [[:years, self]])
+ ActiveSupport::Duration.years(self)
end
alias :year :years
end
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index 3cf7ea0a97..d273487010 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -1,6 +1,3 @@
-require "active_support/deprecation"
-require "active_support/deprecation/proxy_wrappers"
-
class LoadError
REGEXPS = [
/^no such file to load -- (.+)$/i,
@@ -9,18 +6,6 @@ class LoadError
/^cannot load such file -- (.+)$/i,
]
- unless method_defined?(:path)
- # Returns the path which was unable to be loaded.
- def path
- @path ||= begin
- REGEXPS.find do |regex|
- message =~ regex
- end
- $1
- end
- end
- end
-
# Returns true if the given path name (except perhaps for the ".rb"
# extension) is the missing file which caused the exception to be raised.
def is_missing?(location)
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 90989f4f3d..2c24081eb9 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -7,7 +7,8 @@ require "active_support/core_ext/regexp"
class Module
# Defines a class attribute and creates a class and instance reader methods.
# The underlying class variable is set to +nil+, if it is not previously
- # defined.
+ # defined. All class and instance methods created will be public, even if
+ # this method is called with a private or protected access modifier.
#
# module HairColors
# mattr_reader :hair_colors
@@ -76,7 +77,9 @@ class Module
alias :cattr_reader :mattr_reader
# Defines a class attribute and creates a class and instance writer methods to
- # allow assignment to the attribute.
+ # allow assignment to the attribute. All class and instance methods created
+ # will be public, even if this method is called with a private or protected
+ # access modifier.
#
# module HairColors
# mattr_writer :hair_colors
@@ -142,6 +145,8 @@ class Module
alias :cattr_writer :mattr_writer
# Defines both class and instance accessors for class attributes.
+ # All class and instance methods created will be public, even if
+ # this method is called with a private or protected access modifier.
#
# module HairColors
# mattr_accessor :hair_colors
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 19f692e943..cdf27f49ad 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -20,7 +20,8 @@ class Module
# ==== Options
# * <tt>:to</tt> - Specifies the target object
# * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix
- # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being raised
+ # * <tt>:allow_nil</tt> - if set to true, prevents a +Module::DelegationError+
+ # from being raised
#
# The macro receives one or more method names (specified as symbols or
# strings) and the name of the target object via the <tt>:to</tt> option
@@ -112,18 +113,19 @@ class Module
# invoice.customer_address # => 'Vimmersvej 13'
#
# If the target is +nil+ and does not respond to the delegated method a
- # +NoMethodError+ 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, 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.
#
# class User < ActiveRecord::Base
# has_one :profile
# delegate :age, to: :profile
# end
#
- # User.new.age # raises NoMethodError: undefined method `age'
+ # User.new.age
+ # # => Module::DelegationError: User#age delegated to profile.age, but profile is nil
#
# But if not having a profile yet is fine and should not be an error
# condition:
@@ -258,7 +260,7 @@ class Module
# end
#
# The target can be anything callable within the object. E.g. instance
- # variables, methods, constants ant the likes.
+ # variables, methods, constants and the likes.
def delegate_missing_to(target)
target = target.to_s
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index 0665aa88bc..ca20a6d4c5 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -5,10 +5,12 @@ class Module
#
# M::N.parent_name # => "M"
def parent_name
- if defined? @parent_name
+ if defined?(@parent_name)
@parent_name
else
- @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
+ parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
+ @parent_name = parent_name unless frozen?
+ parent_name
end
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 946f8ddeab..4f6621693e 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -99,31 +99,30 @@ module ActiveSupport::NumericWithFormat
# 1234567.to_s(:human, precision: 1,
# separator: ',',
# significant: false) # => "1,2 Million"
- def to_s(*args)
- format, options = args
- options ||= {}
-
+ def to_s(format = nil, options = nil)
case format
+ when nil
+ super()
+ when Integer, String
+ super(format)
when :phone
- return ActiveSupport::NumberHelper.number_to_phone(self, options)
+ return ActiveSupport::NumberHelper.number_to_phone(self, options || {})
when :currency
- return ActiveSupport::NumberHelper.number_to_currency(self, options)
+ return ActiveSupport::NumberHelper.number_to_currency(self, options || {})
when :percentage
- return ActiveSupport::NumberHelper.number_to_percentage(self, options)
+ return ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
when :delimited
- return ActiveSupport::NumberHelper.number_to_delimited(self, options)
+ return ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
when :rounded
- return ActiveSupport::NumberHelper.number_to_rounded(self, options)
+ return ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
when :human
- return ActiveSupport::NumberHelper.number_to_human(self, options)
+ return ActiveSupport::NumberHelper.number_to_human(self, options || {})
when :human_size
- return ActiveSupport::NumberHelper.number_to_human_size(self, options)
+ return ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
+ when Symbol
+ super()
else
- if is_a?(Float) || format.is_a?(Symbol)
- super()
- else
- super
- end
+ super(format)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 809dfd4e07..2e6c70d418 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -19,7 +19,7 @@ class Numeric
# # equivalent to Time.current.advance(months: 4, years: 5)
# (4.months + 5.years).from_now
def seconds
- ActiveSupport::Duration.new(self, [[:seconds, self]])
+ ActiveSupport::Duration.seconds(self)
end
alias :second :seconds
@@ -27,7 +27,7 @@ class Numeric
#
# 2.minutes # => 2 minutes
def minutes
- ActiveSupport::Duration.new(self * 60, [[:minutes, self]])
+ ActiveSupport::Duration.minutes(self)
end
alias :minute :minutes
@@ -35,7 +35,7 @@ class Numeric
#
# 2.hours # => 2 hours
def hours
- ActiveSupport::Duration.new(self * 3600, [[:hours, self]])
+ ActiveSupport::Duration.hours(self)
end
alias :hour :hours
@@ -43,7 +43,7 @@ class Numeric
#
# 2.days # => 2 days
def days
- ActiveSupport::Duration.new(self * 24.hours, [[:days, self]])
+ ActiveSupport::Duration.days(self)
end
alias :day :days
@@ -51,7 +51,7 @@ class Numeric
#
# 2.weeks # => 2 weeks
def weeks
- ActiveSupport::Duration.new(self * 7.days, [[:weeks, self]])
+ ActiveSupport::Duration.weeks(self)
end
alias :week :weeks
@@ -59,7 +59,7 @@ class Numeric
#
# 2.fortnights # => 4 weeks
def fortnights
- ActiveSupport::Duration.new(self * 2.weeks, [[:weeks, self * 2]])
+ ActiveSupport::Duration.weeks(self * 2)
end
alias :fortnight :fortnights
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 9485e74816..ea81df2bd8 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -74,7 +74,7 @@ end
class Symbol
begin
:symbol.dup # Ruby 2.4.x.
- 'symbol_from_string'.to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
+ "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
rescue TypeError
# Symbols are not duplicable:
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 4eabce79e2..7a2fc5c1b5 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -128,10 +128,10 @@ class String
# Removes the module part from the constant expression in the string.
#
- # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
- # 'Inflections'.demodulize # => "Inflections"
- # '::Inflections'.demodulize # => "Inflections"
- # ''.demodulize # => ''
+ # 'ActiveSupport::Inflector::Inflections'.demodulize # => "Inflections"
+ # 'Inflections'.demodulize # => "Inflections"
+ # '::Inflections'.demodulize # => "Inflections"
+ # ''.demodulize # => ''
#
# See also +deconstantize+.
def demodulize
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 227c34b032..94ce3f6a61 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -152,18 +152,16 @@ module ActiveSupport #:nodoc:
def [](*args)
if args.size < 2
super
- else
- if html_safe?
- new_safe_buffer = super
-
- if new_safe_buffer
- new_safe_buffer.instance_variable_set :@html_safe, true
- end
+ elsif html_safe?
+ new_safe_buffer = super
- new_safe_buffer
- else
- to_str[*args]
+ if new_safe_buffer
+ new_safe_buffer.instance_variable_set :@html_safe, true
end
+
+ new_safe_buffer
+ else
+ to_str[*args]
end
end
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 7655fa4f99..930d71e8d2 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -18,7 +18,7 @@ module ActiveSupport
#
# Using the default deprecator:
# ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
- # # => [:aaa, :bbb, :ccc]
+ # # => Fred
#
# Fred.aaa
# # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index 1c6618b19a..ce39e9a232 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -122,10 +122,11 @@ module ActiveSupport
# (Backtrace information…)
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
- def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
+ def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.")
@old_const = old_const
@new_const = new_const
@deprecator = deprecator
+ @message = message
end
# Returns the class of the new constant.
@@ -143,7 +144,7 @@ module ActiveSupport
end
def warn(callstack, called, args)
- @deprecator.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack)
+ @deprecator.warn(@message, callstack)
end
end
end
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index b8d200ba94..58c5c50e30 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -48,11 +48,11 @@ module ActiveSupport
private
# Outputs a deprecation warning message
#
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name)
+ # deprecated_method_warning(:method_name)
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}"
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method)
+ # deprecated_method_warning(:method_name, :another_method)
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)"
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message")
+ # deprecated_method_warning(:method_name, "Optional message")
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)"
def deprecated_method_warning(method_name, message = nil)
warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}"
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index c9e8c8fdc4..003f6203ef 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -7,13 +7,82 @@ module ActiveSupport
#
# 1.month.ago # equivalent to Time.now.advance(months: -1)
class Duration
- EPOCH = ::Time.utc(2000)
+ SECONDS_PER_MINUTE = 60
+ SECONDS_PER_HOUR = 3600
+ SECONDS_PER_DAY = 86400
+ SECONDS_PER_WEEK = 604800
+ SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year
+ SECONDS_PER_YEAR = 31556952 # length of a gregorian year (365.2425 days)
+
+ PARTS_IN_SECONDS = {
+ seconds: 1,
+ minutes: SECONDS_PER_MINUTE,
+ hours: SECONDS_PER_HOUR,
+ days: SECONDS_PER_DAY,
+ weeks: SECONDS_PER_WEEK,
+ months: SECONDS_PER_MONTH,
+ years: SECONDS_PER_YEAR
+ }.freeze
attr_accessor :value, :parts
autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
+ class << self
+ # Creates a new Duration from string formatted according to ISO 8601 Duration.
+ #
+ # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
+ # This method allows negative parts to be present in pattern.
+ # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
+ def parse(iso8601duration)
+ parts = ISO8601Parser.new(iso8601duration).parse!
+ new(calculate_total_seconds(parts), parts)
+ end
+
+ def ===(other) #:nodoc:
+ other.is_a?(Duration)
+ rescue ::NoMethodError
+ false
+ end
+
+ def seconds(value) #:nodoc:
+ new(value, [[:seconds, value]])
+ end
+
+ def minutes(value) #:nodoc:
+ new(value * SECONDS_PER_MINUTE, [[:minutes, value]])
+ end
+
+ def hours(value) #:nodoc:
+ new(value * SECONDS_PER_HOUR, [[:hours, value]])
+ end
+
+ def days(value) #:nodoc:
+ new(value * SECONDS_PER_DAY, [[:days, value]])
+ end
+
+ def weeks(value) #:nodoc:
+ new(value * SECONDS_PER_WEEK, [[:weeks, value]])
+ end
+
+ def months(value) #:nodoc:
+ new(value * SECONDS_PER_MONTH, [[:months, value]])
+ end
+
+ def years(value) #:nodoc:
+ new(value * SECONDS_PER_YEAR, [[:years, value]])
+ end
+
+ private
+
+ def calculate_total_seconds(parts)
+ parts.inject(0) do |total, (part, value)|
+ total + value * PARTS_IN_SECONDS[part]
+ end
+ end
+ end
+
def initialize(value, parts) #:nodoc:
@value, @parts = value, parts.to_h
@parts.default = 0
@@ -78,14 +147,14 @@ module ActiveSupport
# 1.day.to_i # => 86400
#
# Note that this conversion makes some assumptions about the
- # duration of some periods, e.g. months are always 30 days
- # and years are 365.25 days:
+ # duration of some periods, e.g. months are always 1/12 of year
+ # and years are 365.2425 days:
#
- # # equivalent to 30.days.to_i
- # 1.month.to_i # => 2592000
+ # # equivalent to (1.year / 12).to_i
+ # 1.month.to_i # => 2629746
#
- # # equivalent to 365.25.days.to_i
- # 1.year.to_i # => 31557600
+ # # equivalent to 365.2425.days.to_i
+ # 1.year.to_i # => 31556952
#
# In such cases, Ruby's core
# Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
@@ -105,12 +174,6 @@ module ActiveSupport
@value.hash
end
- def self.===(other) #:nodoc:
- other.is_a?(Duration)
- rescue ::NoMethodError
- false
- end
-
# Calculates a new Time or Date that is as far in the future
# as this Duration represents.
def since(time = ::Time.current)
@@ -141,16 +204,6 @@ module ActiveSupport
@value.respond_to?(method, include_private)
end
- # Creates a new Duration from string formatted according to ISO 8601 Duration.
- #
- # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
- # This method allows negative parts to be present in pattern.
- # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
- def self.parse(iso8601duration)
- parts = ISO8601Parser.new(iso8601duration).parse!
- new(EPOCH.advance(parts) - EPOCH, 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/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index f54f88eb0a..8e0dc71dca 100644
--- a/activesupport/lib/active_support/evented_file_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -17,7 +17,7 @@ module ActiveSupport
#
# Example:
#
- # checker = EventedFileUpdateChecker.new(["/tmp/foo"], -> { puts "changed" })
+ # checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" }
# checker.updated?
# # => false
# checker.execute_if_updated
@@ -32,6 +32,10 @@ module ActiveSupport
#
class EventedFileUpdateChecker #:nodoc: all
def initialize(files, dirs = {}, &block)
+ unless block
+ raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker"
+ end
+
@ph = PathHelper.new
@files = files.map { |f| @ph.xpath(f) }.to_set
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
index 3384d12d5b..ca88e7876b 100644
--- a/activesupport/lib/active_support/execution_wrapper.rb
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -19,14 +19,14 @@ module ActiveSupport
set_callback(:complete, *args, &block)
end
- class RunHook < Struct.new(:hook) # :nodoc:
+ RunHook = Struct.new(:hook) do # :nodoc:
def before(target)
hook_state = target.send(:hook_state)
hook_state[hook] = hook.run
end
end
- class CompleteHook < Struct.new(:hook) # :nodoc:
+ CompleteHook = Struct.new(:hook) do # :nodoc:
def before(target)
hook_state = target.send(:hook_state)
if hook_state.key?(hook)
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 2dbbfadac1..2b5e3c1350 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -38,6 +38,10 @@ module ActiveSupport
# changes. The array of files and list of directories cannot be changed
# after FileUpdateChecker has been initialized.
def initialize(files, dirs = {}, &block)
+ unless block
+ raise ArgumentError, "A block is required to initialize a FileUpdateChecker"
+ end
+
@files = files.freeze
@glob = compile_glob(dirs)
@block = block
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index d3f7b46e77..79e7feaf47 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -269,6 +269,10 @@ module ActiveSupport
dup.tap { |hash| hash.transform_values!(*args, &block) }
end
+ def compact
+ dup.compact!
+ end
+
# Convert to a regular hash with string keys.
def to_hash
_new_hash = Hash.new
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index b94368df14..b749913ee9 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -2,6 +2,8 @@ require "active_support"
require "active_support/file_update_checker"
require "active_support/core_ext/array/wrap"
+# :enddoc:
+
module I18n
class Railtie < Rails::Railtie
config.i18n = ActiveSupport::OrderedOptions.new
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 8fea96a82a..8ccb735c6d 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -198,10 +198,10 @@ module ActiveSupport
# Removes the module part from the expression in the string.
#
- # demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections"
- # demodulize('Inflections') # => "Inflections"
- # demodulize('::Inflections') # => "Inflections"
- # demodulize('') # => ""
+ # demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections"
+ # demodulize('Inflections') # => "Inflections"
+ # demodulize('::Inflections') # => "Inflections"
+ # demodulize('') # => ""
#
# See also #deconstantize.
def demodulize(path)
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 3e78986e8e..de6dd2720b 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -57,6 +57,8 @@ module ActiveSupport
# transliterate('Jürgen')
# # => "Juergen"
def transliterate(string, replacement = "?".freeze)
+ raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
+
I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
replacement: replacement)
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 7b33dc3481..0671469788 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -14,10 +14,10 @@ module ActiveSupport
# where you don't want users to be able to determine the value of the payload.
#
# salt = SecureRandom.random_bytes(64)
- # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..."
- # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
- # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
- # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
+ # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, 32) # => "\x89\xE0\x156\xAC..."
+ # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
+ # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
+ # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
class MessageEncryptor
DEFAULT_CIPHER = "aes-256-cbc"
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 65d6259a06..8c58466556 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -16,7 +16,8 @@ module ActiveSupport #:nodoc:
# through the +mb_chars+ method. Methods which would normally return a
# String object now return a Chars object so methods can be chained.
#
- # 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string"
+ # 'The Perfect String '.mb_chars.downcase.strip.normalize
+ # # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
#
# Chars objects are perfectly interchangeable with String objects as long as
# no explicit class checks are made. If certain methods do explicitly check
@@ -134,7 +135,7 @@ module ActiveSupport #:nodoc:
# Converts characters in the string to the opposite case.
#
- # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
+ # 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN"
def swapcase
chars Unicode.swapcase(@wrapped_string)
end
@@ -148,8 +149,8 @@ module ActiveSupport #:nodoc:
# Capitalizes the first letter of every word, when possible.
#
- # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
- # "日本語".mb_chars.titleize # => "日本語"
+ # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
+ # "日本語".mb_chars.titleize.to_s # => "日本語"
def titleize
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) })
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 05cfb249c3..0912912aba 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -9,7 +9,7 @@ module ActiveSupport
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = "8.0.0"
+ UNICODE_VERSION = "9.0.0"
# The default normalization used for operations that require
# normalization. It can be set to any of the normalizations
@@ -57,9 +57,12 @@ module ActiveSupport
previous = codepoints[pos - 1]
current = codepoints[pos]
+ # See http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules
should_break =
+ if pos == eoc
+ true
# GB3. CR X LF
- if previous == database.boundary[:cr] && current == database.boundary[:lf]
+ elsif previous == database.boundary[:cr] && current == database.boundary[:lf]
false
# GB4. (Control|CR|LF) ÷
elsif previous && in_char_class?(previous, [:control, :cr, :lf])
@@ -76,11 +79,8 @@ module ActiveSupport
# GB8. (LVT|T) X (T)
elsif in_char_class?(previous, [:lvt, :t]) && database.boundary[:t] === current
false
- # GB8a. Regional_Indicator X Regional_Indicator
- elsif database.boundary[:regional_indicator] === previous && database.boundary[:regional_indicator] === current
- false
- # GB9. X Extend
- elsif database.boundary[:extend] === current
+ # GB9. X (Extend | ZWJ)
+ elsif in_char_class?(current, [:extend, :zwj])
false
# GB9a. X SpacingMark
elsif database.boundary[:spacingmark] === current
@@ -88,7 +88,17 @@ module ActiveSupport
# GB9b. Prepend X
elsif database.boundary[:prepend] === previous
false
- # GB10. Any ÷ Any
+ # GB10. (E_Base | EBG) Extend* X E_Modifier
+ elsif (marker...pos).any? { |i| in_char_class?(codepoints[i], [:e_base, :e_base_gaz]) && codepoints[i + 1...pos].all? { |c| database.boundary[:extend] === c } } && database.boundary[:e_modifier] === current
+ false
+ # GB11. ZWJ X (Glue_After_Zwj | EBG)
+ elsif database.boundary[:zwj] === previous && in_char_class?(current, [:glue_after_zwj, :e_base_gaz])
+ false
+ # GB12. ^ (RI RI)* RI X RI
+ # GB13. [^RI] (RI RI)* RI X RI
+ elsif codepoints[marker..pos].all? { |c| database.boundary[:regional_indicator] === c } && codepoints[marker..pos].count { |c| database.boundary[:regional_indicator] === c }.even?
+ false
+ # GB999. Any ÷ Any
else
true
end
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 6000ea44be..880340ca86 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -45,7 +45,7 @@ module ActiveSupport
#
# number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
# # => "(755) 6123-4567"
- # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)
# # => "133-1234-5678"
def number_to_phone(number, options = {})
NumberToPhoneConverter.convert(number, options)
@@ -78,7 +78,7 @@ module ActiveSupport
# (defaults to "%u%n"). Fields are <tt>%u</tt> for the
# currency, and <tt>%n</tt> for the number.
# * <tt>:negative_format</tt> - Sets the format for negative
- # numbers (defaults to prepending an hyphen to the formatted
+ # numbers (defaults to prepending a hyphen to the formatted
# number given by <tt>:format</tt>). Accepts the same fields
# than <tt>:format</tt>, except <tt>%n</tt> is here the
# absolute value of the number.
diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb
index 09e1cbb28d..90eac89c9e 100644
--- a/activesupport/lib/active_support/string_inquirer.rb
+++ b/activesupport/lib/active_support/string_inquirer.rb
@@ -18,7 +18,7 @@ module ActiveSupport
private
def respond_to_missing?(method_name, include_private = false)
- method_name[-1] == "?"
+ (method_name[-1] == "?") || super
end
def method_missing(method_name, *arguments)
diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb
index 3108e3e549..a18788f38e 100644
--- a/activesupport/lib/active_support/testing/autorun.rb
+++ b/activesupport/lib/active_support/testing/autorun.rb
@@ -2,8 +2,8 @@ gem "minitest"
require "minitest"
-if Minitest.respond_to?(:run_via) && !Minitest.run_via[:rails]
- Minitest.run_via[:ruby] = true
+if Minitest.respond_to?(:run_via) && !Minitest.run_via.set?
+ Minitest.run_via = :ruby
end
Minitest.autorun
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 14edd13a8f..54c3263efa 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -43,7 +43,7 @@ module ActiveSupport
end
}
end
- result = Marshal.dump(self.dup)
+ result = Marshal.dump(dup)
end
write.puts [result].pack("m")
@@ -78,7 +78,7 @@ module ActiveSupport
"ISOLATION_OUTPUT" => tmpfile.path
}
- test_opts = "-n#{self.class.name}##{self.name}"
+ test_opts = "-n#{self.class.name}##{name}"
load_path_args = []
$-I.each do |p|
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index e2f008b4b7..07c9be0604 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -10,7 +10,7 @@ module ActiveSupport
@stubs = Concurrent::Map.new { |h, k| h[k] = {} }
end
- def stub_object(object, method_name, return_value)
+ def stub_object(object, method_name, &block)
if stub = stubbing(object, method_name)
unstub_object(stub)
end
@@ -20,7 +20,7 @@ module ActiveSupport
@stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name)
object.singleton_class.send :alias_method, new_name, method_name
- object.define_singleton_method(method_name) { return_value }
+ object.define_singleton_method(method_name, &block)
end
def unstub_all!
@@ -134,9 +134,9 @@ module ActiveSupport
now = date_or_time.to_time.change(usec: 0)
end
- simple_stubs.stub_object(Time, :now, now)
- simple_stubs.stub_object(Date, :today, now.to_date)
- simple_stubs.stub_object(DateTime, :now, now.to_datetime)
+ simple_stubs.stub_object(Time, :now) { at(now.to_i) }
+ simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) }
+ simple_stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) }
if block_given?
begin
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 889f71c4f3..857cc1a664 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -427,7 +427,8 @@ module ActiveSupport
end
def freeze
- period; utc; time # preload instance variables before freezing
+ # preload instance variables before freezing
+ period; utc; time; to_datetime
super
end
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index dd2c178fb6..f7d9c48bbe 100644
--- a/activesupport/lib/active_support/values/unicode_tables.dat
+++ b/activesupport/lib/active_support/values/unicode_tables.dat
Binary files differ
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 44b0bdb7dc..cde2967132 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -74,5 +74,7 @@ module LibXML #:nodoc:
end
end
+# :enddoc:
+
LibXML::XML::Document.include(LibXML::Conversions::Document)
LibXML::XML::Node.include(LibXML::Conversions::Node)
diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
index 4edb0bd2b1..8a43b05b17 100644
--- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb
+++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
@@ -73,7 +73,7 @@ module ActiveSupport
LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER)
parser = LibXML::XML::SaxParser.io(data)
- document = self.document_class.new
+ document = document_class.new
parser.callbacks = document
parser.parse
diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
index b8b85146c5..7388bea5d8 100644
--- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
@@ -76,7 +76,7 @@ module ActiveSupport
{}
else
data.ungetc(char)
- document = self.document_class.new
+ document = document_class.new
parser = Nokogiri::XML::SAX::Parser.new(document)
parser.parse(data)
document.hash
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 4e564591b4..c4f34c0abf 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -3,8 +3,8 @@ ORIG_ARGV = ARGV.dup
require "active_support/core_ext/kernel/reporting"
silence_warnings do
- Encoding.default_internal = "UTF-8"
- Encoding.default_external = "UTF-8"
+ Encoding.default_internal = Encoding::UTF_8
+ Encoding.default_external = Encoding::UTF_8
end
require "active_support/testing/autorun"
@@ -24,16 +24,16 @@ ActiveSupport.to_time_preserves_timezone = ENV["PRESERVE_TIMEZONES"] == "1"
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
-# Skips the current run on Rubinius using Minitest::Assertions#skip
-def rubinius_skip(message = "")
- skip message if RUBY_ENGINE == "rbx"
-end
-
-# Skips the current run on JRuby using Minitest::Assertions#skip
-def jruby_skip(message = "")
- skip message if defined?(JRUBY_VERSION)
-end
-
class ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
+
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
+ private def rubinius_skip(message = "")
+ skip message if RUBY_ENGINE == "rbx"
+ end
+
+ # Skips the current run on JRuby using Minitest::Assertions#skip
+ private def jruby_skip(message = "")
+ skip message if defined?(JRUBY_VERSION)
+ end
end
diff --git a/activesupport/test/array_inquirer_test.rb b/activesupport/test/array_inquirer_test.rb
index 4d3f5b001c..5b2bc82905 100644
--- a/activesupport/test/array_inquirer_test.rb
+++ b/activesupport/test/array_inquirer_test.rb
@@ -38,4 +38,24 @@ class ArrayInquirerTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::ArrayInquirer, result
assert_equal @array_inquirer, result
end
+
+ def test_respond_to_fallback_to_array_respond_to
+ Array.class_eval do
+ def respond_to_missing?(name, include_private = false)
+ (name == :foo) || super
+ end
+ end
+ arr = ActiveSupport::ArrayInquirer.new([:x])
+
+ assert_respond_to arr, :can_you_hear_me?
+ assert_respond_to arr, :foo
+ assert_not_respond_to arr, :nope
+ ensure
+ Array.class_eval do
+ undef_method :respond_to_missing?
+ def respond_to_missing?(name, include_private = false)
+ super
+ end
+ end
+ end
end
diff --git a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
index 829ee917d2..e477ab21d0 100644
--- a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
+++ b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
@@ -1,4 +1,4 @@
RaisesArbitraryException = 1
_ = A::B # Autoloading recursion, also expected to be watched and discarded.
-raise Exception, "arbitray exception message"
+raise Exception, "arbitrary exception message"
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 28caa30bf1..4f00afb581 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -23,10 +23,6 @@ module CallbacksTest
method_name
end
- def callback_string(callback_method)
- "history << [#{callback_method.to_sym.inspect}, :string]"
- end
-
def callback_proc(callback_method)
Proc.new { |model| model.history << [callback_method, :proc] }
end
@@ -61,7 +57,6 @@ module CallbacksTest
[:before_save, :after_save].each do |callback_method|
callback_method_sym = callback_method.to_sym
send(callback_method, callback_symbol(callback_method_sym))
- ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method_sym)) }
send(callback_method, callback_proc(callback_method_sym))
send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, "")))
send(callback_method, CallbackClass)
@@ -197,10 +192,12 @@ module CallbacksTest
before_save Proc.new { |r| r.history << [:before_save, :symbol] }, unless: :no
before_save Proc.new { |r| r.history << "b00m" }, unless: :yes
# string
- before_save Proc.new { |r| r.history << [:before_save, :string] }, if: "yes"
- before_save Proc.new { |r| r.history << "b00m" }, if: "no"
- before_save Proc.new { |r| r.history << [:before_save, :string] }, unless: "no"
- before_save Proc.new { |r| r.history << "b00m" }, unless: "yes"
+ ActiveSupport::Deprecation.silence do
+ before_save Proc.new { |r| r.history << [:before_save, :string] }, if: "yes"
+ before_save Proc.new { |r| r.history << "b00m" }, if: "no"
+ before_save Proc.new { |r| r.history << [:before_save, :string] }, unless: "no"
+ before_save Proc.new { |r| r.history << "b00m" }, unless: "yes"
+ end
# Combined if and unless
before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, if: :yes, unless: :no
before_save Proc.new { |r| r.history << "b00m" }, if: :yes, unless: :yes
@@ -272,7 +269,6 @@ module CallbacksTest
set_callback :save, :before, :nope, if: :no
set_callback :save, :before, :nope, unless: :yes
set_callback :save, :after, :tweedle
- ActiveSupport::Deprecation.silence { set_callback :save, :before, "tweedle_dee" }
set_callback :save, :before, proc { |m| m.history << "yup" }
set_callback :save, :before, :nope, if: proc { false }
set_callback :save, :before, :nope, unless: proc { true }
@@ -302,10 +298,6 @@ module CallbacksTest
yield
end
- def tweedle_dee
- @history << "tweedle dee"
- end
-
def tweedle_dum
@history << "tweedle dum pre"
yield
@@ -421,7 +413,6 @@ module CallbacksTest
around = AroundPerson.new
around.save
assert_equal [
- "tweedle dee",
"yup", "yup",
"tweedle dum pre",
"w0tyes before",
@@ -540,7 +531,6 @@ module CallbacksTest
assert_equal [], person.history
person.save
assert_equal [
- [:before_save, :string],
[:before_save, :proc],
[:before_save, :object],
[:before_save, :block],
@@ -548,7 +538,6 @@ module CallbacksTest
[:after_save, :class],
[:after_save, :object],
[:after_save, :proc],
- [:after_save, :string],
[:after_save, :symbol]
], person.history
end
@@ -567,7 +556,6 @@ module CallbacksTest
[:after_save, :class],
[:after_save, :object],
[:after_save, :proc],
- [:after_save, :string],
[:after_save, :symbol]
], person.history
end
@@ -580,7 +568,6 @@ module CallbacksTest
person.save
assert_equal [
[:before_save, :symbol],
- [:before_save, :string],
[:before_save, :proc],
[:before_save, :object],
[:before_save, :class],
@@ -589,7 +576,6 @@ module CallbacksTest
[:after_save, :class],
[:after_save, :object],
[:after_save, :proc],
- [:after_save, :string],
[:after_save, :symbol]
], person.history
end
@@ -883,34 +869,8 @@ module CallbacksTest
end
end
- class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase
- def test_returning_false_does_not_halt_callback_if_config_variable_is_not_set
- obj = CallbackFalseTerminator.new
- obj.save
- assert_nil obj.halted
- assert obj.saved
- end
- end
-
- class CallbackFalseTerminatorWithConfigTrueTest < ActiveSupport::TestCase
- def setup
- ActiveSupport::Callbacks.halt_and_display_warning_on_return_false = true
- end
-
- def test_returning_false_does_not_halt_callback_if_config_variable_is_true
- obj = CallbackFalseTerminator.new
- obj.save
- assert_nil obj.halted
- assert obj.saved
- end
- end
-
- class CallbackFalseTerminatorWithConfigFalseTest < ActiveSupport::TestCase
- def setup
- ActiveSupport::Callbacks.halt_and_display_warning_on_return_false = false
- end
-
- def test_returning_false_does_not_halt_callback_if_config_variable_is_false
+ class CallbackFalseTerminatorTest < ActiveSupport::TestCase
+ def test_returning_false_does_not_halt_callback
obj = CallbackFalseTerminator.new
obj.save
assert_nil obj.halted
@@ -939,7 +899,6 @@ module CallbacksTest
writer.save
assert_equal [
[:before_save, :symbol],
- [:before_save, :string],
[:before_save, :proc],
[:before_save, :object],
[:before_save, :class],
@@ -948,7 +907,6 @@ module CallbacksTest
[:after_save, :class],
[:after_save, :object],
[:after_save, :proc],
- [:after_save, :string],
[:after_save, :symbol]
], writer.history
end
@@ -1162,14 +1120,6 @@ module CallbacksTest
assert_equal 1, calls.length
end
- def test_add_eval
- calls = []
- klass = ActiveSupport::Deprecation.silence { build_class("bar") }
- klass.class_eval { define_method(:bar) { calls << klass } }
- klass.new.run
- assert_equal 1, calls.length
- end
-
def test_skip_class # removes one at a time
calls = []
callback = Class.new {
@@ -1204,7 +1154,7 @@ module CallbacksTest
def test_skip_string # raises error
calls = []
- klass = ActiveSupport::Deprecation.silence { build_class("bar") }
+ klass = build_class(:bar)
klass.class_eval { define_method(:bar) { calls << klass } }
assert_raises(ArgumentError) { klass.skip "bar" }
klass.new.run
@@ -1231,11 +1181,22 @@ module CallbacksTest
end
class DeprecatedWarningTest < ActiveSupport::TestCase
- def test_deprecate_string_callback
+ def test_deprecate_string_conditional_options
klass = Class.new(Record)
- assert_deprecated do
- klass.send :before_save, "tweedle_dee"
+ assert_deprecated { klass.before_save :tweedle, if: "true" }
+ assert_deprecated { klass.after_save :tweedle, unless: "false" }
+ assert_deprecated { klass.skip_callback :save, :before, :tweedle, if: "true" }
+ assert_deprecated { klass.skip_callback :save, :after, :tweedle, unless: "false" }
+ end
+ end
+
+ class NotPermittedStringCallbackTest < ActiveSupport::TestCase
+ def test_passing_string_callback_is_not_permitted
+ klass = Class.new(Record)
+
+ assert_raises(ArgumentError) do
+ klass.before_save "tweedle"
end
end
end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 5a90210bb8..50bb1004f7 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -395,6 +395,10 @@ class DateExtBehaviorTest < ActiveSupport::TestCase
assert Date.new.acts_like_date?
end
+ def test_blank?
+ assert_not Date.new.blank?
+ end
+
def test_freeze_doesnt_clobber_memoized_instance_methods
assert_nothing_raised do
Date.today.freeze.inspect
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index e9be181749..e3b31c10f5 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -318,6 +318,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert DateTime.new.acts_like_time?
end
+ def test_blank?
+ assert_not DateTime.new.blank?
+ end
+
def test_utc?
assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12).utc?
assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc?
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index fc0dd41d0e..6a275d1d5b 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -237,6 +237,29 @@ class DurationTest < ActiveSupport::TestCase
assert_equal(1, (61 <=> 1.minute))
end
+ def test_twelve_months_equals_one_year
+ assert_equal 12.months, 1.year
+ end
+
+ def test_thirty_days_does_not_equal_one_month
+ assert_not_equal 30.days, 1.month
+ end
+
+ def test_adding_one_month_maintains_day_of_month
+ (1..11).each do |month|
+ [1, 14, 28].each do |day|
+ assert_equal Date.civil(2016, month + 1, day), Date.civil(2016, month, day) + 1.month
+ end
+ end
+
+ assert_equal Date.civil(2017, 1, 1), Date.civil(2016, 12, 1) + 1.month
+ assert_equal Date.civil(2017, 1, 14), Date.civil(2016, 12, 14) + 1.month
+ assert_equal Date.civil(2017, 1, 28), Date.civil(2016, 12, 28) + 1.month
+
+ assert_equal Date.civil(2015, 2, 28), Date.civil(2015, 1, 31) + 1.month
+ assert_equal Date.civil(2016, 2, 29), Date.civil(2016, 1, 31) + 1.month
+ end
+
# ISO8601 string examples are taken from ISO8601 gem at https://github.com/arnau/ISO8601/blob/b93d466840/spec/iso8601/duration_spec.rb
# published under the conditions of MIT license at https://github.com/arnau/ISO8601/blob/b93d466840/LICENSE
#
@@ -345,6 +368,21 @@ class DurationTest < ActiveSupport::TestCase
end
end
+ def test_iso8601_parsing_equivalence_with_numeric_extensions_over_long_periods
+ with_env_tz eastern_time_zone do
+ with_tz_default "Eastern Time (US & Canada)" do
+ assert_equal 3.months, ActiveSupport::Duration.parse("P3M")
+ assert_equal 3.months.to_i, ActiveSupport::Duration.parse("P3M").to_i
+ assert_equal 10.months, ActiveSupport::Duration.parse("P10M")
+ assert_equal 10.months.to_i, ActiveSupport::Duration.parse("P10M").to_i
+ assert_equal 3.years, ActiveSupport::Duration.parse("P3Y")
+ assert_equal 3.years.to_i, ActiveSupport::Duration.parse("P3Y").to_i
+ assert_equal 10.years, ActiveSupport::Duration.parse("P10Y")
+ assert_equal 10.years.to_i, ActiveSupport::Duration.parse("P10Y").to_i
+ end
+ end
+ end
+
def test_adding_durations_do_not_hold_prior_states
time = Time.parse("Nov 29, 2016")
# If the implementation adds and subtracts 3 months, the
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index bea0a84b53..05813ad388 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -589,6 +589,16 @@ class HashExtTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
end
+ def test_indifferent_compact
+ hash_contain_nil_value = @strings.merge("z" => nil)
+ hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
+ compacted_hash = hash.compact
+
+ assert_equal(@strings, compacted_hash)
+ assert_equal(hash_contain_nil_value, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash
+ end
+
def test_indifferent_to_hash
# Should convert to a Hash with String keys.
assert_equal @strings, @mixed.with_indifferent_access.to_hash
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index db0008b735..26f5088ede 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -49,19 +49,3 @@ class KernelSuppressTest < ActiveSupport::TestCase
suppress(LoadError, ArgumentError) { raise ArgumentError }
end
end
-
-class MockStdErr
- attr_reader :output
- def puts(message)
- @output ||= []
- @output << message
- end
-
- def info(message)
- puts(message)
- end
-
- def write(message)
- puts(message)
- end
-end
diff --git a/activesupport/test/core_ext/module/attribute_aliasing_test.rb b/activesupport/test/core_ext/module/attribute_aliasing_test.rb
index d8c2dfd6b8..fdfa868851 100644
--- a/activesupport/test/core_ext/module/attribute_aliasing_test.rb
+++ b/activesupport/test/core_ext/module/attribute_aliasing_test.rb
@@ -52,8 +52,8 @@ class AttributeAliasingTest < ActiveSupport::TestCase
assert_equal "No, really, this is not a joke.", e.Data
assert e.Data?
- e.Data = "Uppercased methods are teh suck"
- assert_equal "Uppercased methods are teh suck", e.body
+ e.Data = "Uppercased methods are the suck"
+ assert_equal "Uppercased methods are the suck", e.body
assert e.body?
end
end
diff --git a/activesupport/test/core_ext/module/introspection_test.rb b/activesupport/test/core_ext/module/introspection_test.rb
new file mode 100644
index 0000000000..db383850cd
--- /dev/null
+++ b/activesupport/test/core_ext/module/introspection_test.rb
@@ -0,0 +1,37 @@
+require "abstract_unit"
+require "active_support/core_ext/module/introspection"
+
+module ParentA
+ module B
+ module C; end
+ module FrozenC; end
+ FrozenC.freeze
+ end
+
+ module FrozenB; end
+ FrozenB.freeze
+end
+
+class IntrospectionTest < ActiveSupport::TestCase
+ def test_parent_name
+ assert_equal "ParentA", ParentA::B.parent_name
+ assert_equal "ParentA::B", ParentA::B::C.parent_name
+ assert_nil ParentA.parent_name
+ end
+
+ def test_parent_name_when_frozen
+ assert_equal "ParentA", ParentA::FrozenB.parent_name
+ assert_equal "ParentA::B", ParentA::B::FrozenC.parent_name
+ end
+
+ def test_parent
+ assert_equal ParentA::B, ParentA::B::C.parent
+ assert_equal ParentA, ParentA::B.parent
+ assert_equal Object, ParentA.parent
+ end
+
+ def test_parents
+ assert_equal [ParentA::B, ParentA, Object], ParentA::B::C.parents
+ assert_equal [ParentA, Object], ParentA::B.parents
+ end
+end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index a4515d1956..a17438bf4d 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -1,30 +1,11 @@
require "abstract_unit"
require "active_support/core_ext/module"
-module One
- Constant1 = "Hello World"
- Constant2 = "What's up?"
-end
-
-class Ab
- include One
- Constant1 = "Hello World" # Will have different object id than One::Constant1
- Constant3 = "Goodbye World"
-end
-
-module Yz
- module Zy
- class Cd
- include One
- end
- end
-end
-
Somewhere = Struct.new(:street, :city) do
attr_accessor :name
end
-class Someone < Struct.new(:name, :place)
+Someone = Struct.new(:name, :place) do
delegate :street, :city, :to_f, to: :place
delegate :name=, to: :place, prefix: true
delegate :upcase, to: "place.city"
@@ -35,10 +16,10 @@ class Someone < Struct.new(:name, :place)
"some_table"
end
- FAILED_DELEGATE_LINE = __LINE__ + 1
+ self::FAILED_DELEGATE_LINE = __LINE__ + 1
delegate :foo, to: :place
- FAILED_DELEGATE_LINE_2 = __LINE__ + 1
+ self::FAILED_DELEGATE_LINE_2 = __LINE__ + 1
delegate :bar, to: :place, allow_nil: true
private
@@ -375,17 +356,6 @@ class ModuleTest < ActiveSupport::TestCase
assert_match(/undefined method `my_fake_method' for/, e.message)
end
- def test_parent
- assert_equal Yz::Zy, Yz::Zy::Cd.parent
- assert_equal Yz, Yz::Zy.parent
- assert_equal Object, Yz.parent
- end
-
- def test_parents
- assert_equal [Yz::Zy, Yz, Object], Yz::Zy::Cd.parents
- assert_equal [Yz, Object], Yz::Zy.parents
- end
-
def test_delegate_with_case
event = Event.new(Tester.new)
assert_equal 1, event.foo
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 5361b7b708..3cfbe6e7e6 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -12,7 +12,7 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
10.minutes => 600,
1.hour + 15.minutes => 4500,
2.days + 4.hours + 30.minutes => 189000,
- 5.years + 1.month + 1.fortnight => 161589600
+ 5.years + 1.month + 1.fortnight => 161624106
}
end
@@ -61,10 +61,10 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
end
def test_duration_after_conversion_is_no_longer_accurate
- assert_equal 30.days.to_i.seconds.since(@now), 1.month.to_i.seconds.since(@now)
- assert_equal 365.25.days.to_f.seconds.since(@now), 1.year.to_f.seconds.since(@now)
- assert_equal 30.days.to_i.seconds.since(@dtnow), 1.month.to_i.seconds.since(@dtnow)
- assert_equal 365.25.days.to_f.seconds.since(@dtnow), 1.year.to_f.seconds.since(@dtnow)
+ assert_equal (1.year / 12).to_i.seconds.since(@now), 1.month.to_i.seconds.since(@now)
+ assert_equal 365.2425.days.to_f.seconds.since(@now), 1.year.to_f.seconds.since(@now)
+ assert_equal (1.year / 12).to_i.seconds.since(@dtnow), 1.month.to_i.seconds.since(@dtnow)
+ assert_equal 365.2425.days.to_f.seconds.since(@dtnow), 1.year.to_f.seconds.since(@dtnow)
end
def test_add_one_year_to_leap_day
@@ -394,6 +394,10 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal "1000010.0", BigDecimal("1000010").to_s
assert_equal "10000 10.0", BigDecimal("1000010").to_s("5F")
+
+ assert_raises TypeError do
+ 1.to_s({})
+ end
end
def test_in_milliseconds
diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb
index 1bedc76320..7fd3fed042 100644
--- a/activesupport/test/core_ext/object/blank_test.rb
+++ b/activesupport/test/core_ext/object/blank_test.rb
@@ -15,7 +15,7 @@ class BlankTest < ActiveSupport::TestCase
end
BLANK = [ EmptyTrue.new, nil, false, "", " ", " \n\t \r ", " ", "\u00a0", [], {} ]
- NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 } ]
+ NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 }, Time.now ]
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 fb140a5b76..e6f3c8aef2 100644
--- a/activesupport/test/core_ext/object/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -4,8 +4,11 @@ require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/numeric/time"
class DuplicableTest < ActiveSupport::TestCase
- if RUBY_VERSION >= "2.4.0"
- RAISE_DUP = [method(:puts), Complex(1), Rational(1), 'symbol_from_string'.to_sym]
+ if 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]
+ 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]
else
RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts), Complex(1), Rational(1)]
@@ -17,7 +20,7 @@ class DuplicableTest < ActiveSupport::TestCase
"* https://github.com/rubinius/rubinius/issues/3089"
RAISE_DUP.each do |v|
- assert !v.duplicable?
+ assert !v.duplicable?, "#{ v.inspect } should not be duplicable"
assert_raises(TypeError, v.class.name) { v.dup }
end
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 00685cd952..41cc9888c6 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -1,5 +1,6 @@
require "date"
require "abstract_unit"
+require "timeout"
require "inflector_test_cases"
require "constantize_test_cases"
@@ -222,7 +223,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
original = %{\u205f\u3000 A string surrounded by various unicode spaces,
with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces( ). \u00a0\u2007}
- expected = "A string surrounded by various unicode spaces, " +
+ expected = "A string surrounded by various unicode spaces, " \
"with tabs( ), newlines( ), unicode nextlines( ) and many spaces( )."
# Make sure squish returns what we expect:
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 629666947f..1534daacb9 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -455,6 +455,10 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal false, ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone).acts_like?(:date)
end
+ def test_blank?
+ assert_not @twz.blank?
+ end
+
def test_is_a
assert_kind_of Time, @twz
assert_kind_of Time, @twz
@@ -503,6 +507,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_nothing_raised do
@twz.period
@twz.time
+ @twz.to_datetime
end
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index e772d15d53..e38d4e83e5 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -271,7 +271,8 @@ class DependenciesTest < ActiveSupport::TestCase
def test_raising_discards_autoloaded_constants
with_autoloading_fixtures do
- assert_raises(Exception, "arbitray exception message") { RaisesArbitraryException }
+ e = assert_raises(Exception) { RaisesArbitraryException }
+ assert_equal("arbitrary exception message", e.message)
assert_not defined?(A)
assert_not defined?(RaisesArbitraryException)
end
@@ -397,14 +398,17 @@ class DependenciesTest < ActiveSupport::TestCase
end
end
- def failing_test_access_thru_and_upwards_fails
- with_autoloading_fixtures do
- assert_not defined?(ModuleFolder)
- assert_raise(NameError) { ModuleFolder::Object }
- assert_raise(NameError) { ModuleFolder::NestedClass::Object }
+ # This raises only on 2.5.. (warns on ..2.4)
+ if RUBY_VERSION > "2.5"
+ def test_access_thru_and_upwards_fails
+ with_autoloading_fixtures do
+ assert_not defined?(ModuleFolder)
+ assert_raise(NameError) { ModuleFolder::Object }
+ assert_raise(NameError) { ModuleFolder::NestedClass::Object }
+ end
+ ensure
+ remove_constants(:ModuleFolder)
end
- ensure
- remove_constants(:ModuleFolder)
end
def test_non_existing_const_raises_name_error_with_fully_qualified_name
@@ -548,7 +552,6 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal autoload + "/conflict.rb", ActiveSupport::Dependencies.search_for_file("conflict")
end
-
end
def test_custom_const_missing_should_work
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 5be93f3a1a..5f72fbf662 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -285,6 +285,16 @@ class DeprecationTest < ActiveSupport::TestCase
end
end
+ def test_deprecated_constant_with_custom_message
+ deprecator = deprecator_with_messages
+
+ klass = Class.new
+ klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new("klass::OLD", "Object", deprecator, message: "foo"))
+
+ klass::OLD.to_s
+ assert_match "foo", deprecator.messages.last
+ end
+
def test_deprecated_instance_variable_with_instance_deprecator
deprecator = deprecator_with_messages
diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb
index 77d8dcb0f8..f33a5f5764 100644
--- a/activesupport/test/evented_file_update_checker_test.rb
+++ b/activesupport/test/evented_file_update_checker_test.rb
@@ -7,6 +7,7 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
def setup
skip if ENV["LISTEN"] == "0"
+ require "listen"
super
end
@@ -30,11 +31,6 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
wait # wait for the events to fire
end
- def rm_f(files)
- super
- wait
- end
-
test "notifies forked processes" do
jruby_skip "Forking not available on JRuby"
@@ -43,7 +39,7 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
checker = new_checker(tmpfiles) {}
assert !checker.updated?
- # Pipes used for flow controll across fork.
+ # Pipes used for flow control across fork.
boot_reader, boot_writer = IO.pipe
touch_reader, touch_writer = IO.pipe
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
index 48cd387196..361e7e2349 100644
--- a/activesupport/test/file_update_checker_shared_tests.rb
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -273,4 +273,10 @@ module FileUpdateCheckerSharedTests
assert checker.execute_if_updated
assert_equal 2, i
end
+
+ test "initialize raises an ArgumentError if no block given" do
+ assert_raise ArgumentError do
+ new_checker([])
+ end
+ end
end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 8d39303f9b..03d7b3fe94 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -271,35 +271,25 @@ class InflectorTest < ActiveSupport::TestCase
end
end
- # FIXME: get following tests to pass on jruby, currently skipped
- #
- # Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes
- # required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby
- # causing our tests to error out.
- # related bug http://jira.codehaus.org/browse/JRUBY-7194
def test_parameterize
- jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterized.each do |some_string, parameterized_string|
assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string))
end
end
def test_parameterize_and_normalize
- jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterizedAndNormalized.each do |some_string, parameterized_string|
assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string))
end
end
def test_parameterize_with_custom_separator
- jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterizeWithUnderscore.each do |some_string, parameterized_string|
assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, separator: "_"))
end
end
def test_parameterize_with_multi_character_separator
- jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterized.each do |some_string, parameterized_string|
assert_equal(parameterized_string.gsub("-", "__sep__"), ActiveSupport::Inflector.parameterize(some_string, separator: "__sep__"))
end
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 6d1d8f1b95..6f5051c312 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -75,12 +75,17 @@ class TestJSONDecoding < ActiveSupport::TestCase
}
TESTS.each_with_index do |(json, expected), index|
+ fail_message = "JSON decoding failed for #{json}"
+
test "json decodes #{index}" do
with_tz_default "Eastern Time (US & Canada)" do
with_parse_json_times(true) do
silence_warnings do
- assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \
- failed for #{json}"
+ if expected.nil?
+ assert_nil ActiveSupport::JSON.decode(json), fail_message
+ else
+ assert_equal expected, ActiveSupport::JSON.decode(json), fail_message
+ end
end
end
end
diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb
index 8eac246937..7e4775cec8 100644
--- a/activesupport/test/json/encoding_test_cases.rb
+++ b/activesupport/test/json/encoding_test_cases.rb
@@ -1,4 +1,8 @@
require "bigdecimal"
+require "date"
+require "time"
+require "pathname"
+require "uri"
module JSONTest
class Foo
@@ -23,7 +27,7 @@ module JSONTest
end
end
- class MyStruct < Struct.new(:name, :value)
+ MyStruct = Struct.new(:name, :value) do
def initialize(*)
@unused = "unused instance variable"
super
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 68ce42bf72..d80d340986 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -50,7 +50,7 @@ class MultibyteCharsTest < ActiveSupport::TestCase
assert_equal BYTE_STRING.length, BYTE_STRING.mb_chars.length
end
- def test_forwarded_method_with_non_string_result_should_be_returned_vertabim
+ def test_forwarded_method_with_non_string_result_should_be_returned_verbatim
str = ""
str.singleton_class.class_eval { def __method_for_multibyte_testing_with_integer_result; 1; end }
@chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing_with_integer_result; 1; end }
@@ -664,7 +664,6 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
end
def test_tidy_bytes_should_tidy_bytes
-
single_byte_cases = {
"\x21" => "!", # Valid ASCII byte, low
"\x41" => "A", # Valid ASCII byte, mid
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index 2201860d8a..a70516bb08 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -18,7 +18,7 @@ module MultibyteTestHelpers
end
UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
- CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance"
+ CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}"
FileUtils.mkdir_p(CACHE_DIR)
UNICODE_STRING = "こにちわ".freeze
diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb
index 4f58e6607a..a1d1c41dc2 100644
--- a/activesupport/test/number_helper_i18n_test.rb
+++ b/activesupport/test/number_helper_i18n_test.rb
@@ -1,5 +1,6 @@
require "abstract_unit"
require "active_support/number_helper"
+require "active_support/core_ext/hash/keys"
module ActiveSupport
class NumberHelperI18nTest < ActiveSupport::TestCase
diff --git a/activesupport/test/reloader_test.rb b/activesupport/test/reloader_test.rb
index 67d8c4b0e3..bdd80307c7 100644
--- a/activesupport/test/reloader_test.rb
+++ b/activesupport/test/reloader_test.rb
@@ -2,12 +2,15 @@ require "abstract_unit"
class ReloaderTest < ActiveSupport::TestCase
def test_prepare_callback
- prepared = false
+ prepared = completed = false
reloader.to_prepare { prepared = true }
+ reloader.to_complete { completed = true }
assert !prepared
+ assert !completed
reloader.prepare!
assert prepared
+ assert !completed
prepared = false
reloader.wrap do
@@ -17,6 +20,15 @@ class ReloaderTest < ActiveSupport::TestCase
assert !prepared
end
+ def test_prepend_prepare_callback
+ i = 10
+ reloader.to_prepare { i += 1 }
+ reloader.to_prepare(prepend: true) { i = 0 }
+
+ reloader.prepare!
+ assert_equal 1, i
+ end
+
def test_only_run_when_check_passes
r = new_reloader { true }
invoked = false
diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb
index 842bdd469d..e8f762da22 100644
--- a/activesupport/test/security_utils_test.rb
+++ b/activesupport/test/security_utils_test.rb
@@ -4,6 +4,11 @@ require "active_support/security_utils"
class SecurityUtilsTest < ActiveSupport::TestCase
def test_secure_compare_should_perform_string_comparison
assert ActiveSupport::SecurityUtils.secure_compare("a", "a")
- assert !ActiveSupport::SecurityUtils.secure_compare("a", "b")
+ 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")
end
end
diff --git a/activesupport/test/string_inquirer_test.rb b/activesupport/test/string_inquirer_test.rb
index d41e4d6800..79a715349c 100644
--- a/activesupport/test/string_inquirer_test.rb
+++ b/activesupport/test/string_inquirer_test.rb
@@ -20,4 +20,25 @@ class StringInquirerTest < ActiveSupport::TestCase
def test_respond_to
assert_respond_to @string_inquirer, :development?
end
+
+ def test_respond_to_fallback_to_string_respond_to
+ String.class_eval do
+ def respond_to_missing?(name, include_private = false)
+ (name == :bar) || super
+ end
+ end
+ str = ActiveSupport::StringInquirer.new("hello")
+
+ assert_respond_to str, :are_you_ready?
+ assert_respond_to str, :bar
+ assert_not_respond_to str, :nope
+
+ ensure
+ String.class_eval do
+ undef_method :respond_to_missing?
+ def respond_to_missing?(name, include_private = false)
+ super
+ end
+ end
+ end
end
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
index c68be329bc..e0d3fb0cf5 100644
--- a/activesupport/test/time_travel_test.rb
+++ b/activesupport/test/time_travel_test.rb
@@ -3,6 +3,10 @@ require "active_support/core_ext/date_time"
require "active_support/core_ext/numeric/time"
class TimeTravelTest < ActiveSupport::TestCase
+ class TimeSubclass < ::Time; end
+ class DateSubclass < ::Date; end
+ class DateTimeSubclass < ::DateTime; end
+
def test_time_helper_travel
Time.stub(:now, Time.now) do
begin
@@ -90,11 +94,12 @@ class TimeTravelTest < ActiveSupport::TestCase
outer_expected_time = Time.new(2004, 11, 24, 01, 04, 44)
inner_expected_time = Time.new(2004, 10, 24, 01, 04, 44)
travel_to outer_expected_time do
- assert_raises(RuntimeError, /Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing./) do
+ e = assert_raises(RuntimeError) do
travel_to(inner_expected_time) do
#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)
end
end
end
@@ -142,4 +147,19 @@ class TimeTravelTest < ActiveSupport::TestCase
end
end
end
+
+ def test_time_helper_travel_with_time_subclass
+ assert_equal TimeSubclass, TimeSubclass.now.class
+ assert_equal DateSubclass, DateSubclass.today.class
+ assert_equal DateTimeSubclass, DateTimeSubclass.now.class
+
+ travel 1.day do
+ assert_equal TimeSubclass, TimeSubclass.now.class
+ assert_equal DateSubclass, DateSubclass.today.class
+ assert_equal DateTimeSubclass, DateTimeSubclass.now.class
+ assert_equal Time.now.to_s, TimeSubclass.now.to_s
+ assert_equal Date.today.to_s, DateSubclass.today.to_s
+ assert_equal DateTime.now.to_s, DateTimeSubclass.now.to_s
+ end
+ end
end
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index 040ddd25fc..466b69bcef 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -31,4 +31,22 @@ class TransliterateTest < ActiveSupport::TestCase
def test_transliterate_should_allow_a_custom_replacement_char
assert_equal "a*b", ActiveSupport::Inflector.transliterate("a索b", "*")
end
+
+ def test_transliterate_handles_empty_string
+ assert_equal "", ActiveSupport::Inflector.transliterate("")
+ end
+
+ def test_transliterate_handles_nil
+ exception = assert_raises ArgumentError do
+ ActiveSupport::Inflector.transliterate(nil)
+ end
+ assert_equal "Can only transliterate strings. Received NilClass", exception.message
+ end
+
+ def test_transliterate_handles_unknown_object
+ exception = assert_raises ArgumentError do
+ ActiveSupport::Inflector.transliterate(Object.new)
+ end
+ assert_equal "Can only transliterate strings. Received Object", exception.message
+ end
end
diff --git a/ci/travis.rb b/ci/travis.rb
index c49a87d864..f59ce5406a 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -154,7 +154,6 @@ ENV["GEM"].split(",").each do |gem|
build = Build.new(gem, isolated: isolated)
results[build.key] = build.run!
-
end
end
diff --git a/guides/Rakefile b/guides/Rakefile
index d2591f523c..ccb42f3273 100644
--- a/guides/Rakefile
+++ b/guides/Rakefile
@@ -1,10 +1,8 @@
namespace :guides do
-
desc 'Generate guides (for authors), use ONLY=foo to process just "foo.md"'
task generate: "generate:html"
namespace :generate do
-
desc "Generate HTML guides"
task :html do
ENV["WARNINGS"] = "1" # authors can't disable this
@@ -18,7 +16,7 @@ namespace :guides do
abort "Please run `setupkindlerb` to install kindlegen"
end
unless `convert` =~ /convert/
- abort "Please install ImageMagick`"
+ abort "Please install ImageMagick"
end
ENV["KINDLE"] = "1"
Rake::Task["guides:generate:html"].invoke
@@ -33,7 +31,7 @@ namespace :guides do
desc "Show help"
task :help do
- puts <<-help
+ puts <<HELP
Guides are taken from the source directory, and the result goes into the
output directory. Assets are stored under files, and copied to output/files as
@@ -46,8 +44,9 @@ All of these processes are handled via rake tasks, here's a full list of them:
#{%x[rake -T]}
Some arguments may be passed via environment variables:
- WARNINGS=1
- Internal links (anchors) are checked, also detects duplicated IDs.
+ RAILS_VERSION=tag
+ If guides are being generated for a specific Rails version set the Git tag
+ here, otherwise the current SHA1 is going to be used to generate edge guides.
ALL=1
Force generation of all guides.
@@ -65,15 +64,12 @@ Some arguments may be passed via environment variables:
Use it when you want to generate translated guides in
source/<GUIDES_LANGUAGE> folder (such as source/es)
- EDGE=1
- Indicate generated guides should be marked as edge.
-
Examples:
- $ rake guides:generate ALL=1
- $ rake guides:generate EDGE=1
- $ rake guides:generate:kindle EDGE=1
+ $ rake guides:generate ALL=1 RAILS_VERSION=v5.1.0
+ $ rake guides:generate ONLY=migrations
+ $ rake guides:generate:kindle
$ rake guides:generate GUIDES_LANGUAGE=es
- help
+HELP
end
end
diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb
index 960d269d90..85d698f81b 100644
--- a/guides/bug_report_templates/action_controller_gem.rb
+++ b/guides/bug_report_templates/action_controller_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "rails", "5.0.0"
+ gem "rails", "5.0.1"
end
require "rack/test"
diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb
index debc46ad54..f715caec95 100644
--- a/guides/bug_report_templates/active_job_gem.rb
+++ b/guides/bug_report_templates/active_job_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "activejob", "5.0.0"
+ gem "activejob", "5.0.1"
end
require "minitest/autorun"
diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb
index 7591470440..f61518713f 100644
--- a/guides/bug_report_templates/active_job_master.rb
+++ b/guides/bug_report_templates/active_job_master.rb
@@ -8,7 +8,6 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
- gem "arel", github: "rails/arel"
end
require "active_job"
diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb
index e18302fe65..98edcb76b1 100644
--- a/guides/bug_report_templates/active_record_gem.rb
+++ b/guides/bug_report_templates/active_record_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "activerecord", "5.0.0"
+ gem "activerecord", "5.0.1"
gem "sqlite3"
end
diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb
index 8bbc1ef19e..7265a671b0 100644
--- a/guides/bug_report_templates/active_record_master.rb
+++ b/guides/bug_report_templates/active_record_master.rb
@@ -8,7 +8,6 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
- gem "arel", github: "rails/arel"
gem "sqlite3"
end
diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb
index ba80e6b4ad..ece6ae4f12 100644
--- a/guides/bug_report_templates/active_record_migrations_gem.rb
+++ b/guides/bug_report_templates/active_record_migrations_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "activerecord", "5.0.0.1"
+ gem "activerecord", "5.0.1"
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 84a4b71909..13a375d1ba 100644
--- a/guides/bug_report_templates/active_record_migrations_master.rb
+++ b/guides/bug_report_templates/active_record_migrations_master.rb
@@ -8,7 +8,6 @@ end
gemfile(true) do
source "https://rubygems.org"
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 a0b541d012..54433b34dd 100644
--- a/guides/bug_report_templates/benchmark.rb
+++ b/guides/bug_report_templates/benchmark.rb
@@ -8,7 +8,6 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
- gem "arel", github: "rails/arel"
gem "benchmark-ips"
end
diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb
index a94848e25b..fa9f94eea0 100644
--- a/guides/bug_report_templates/generic_gem.rb
+++ b/guides/bug_report_templates/generic_gem.rb
@@ -8,7 +8,7 @@ end
gemfile(true) do
source "https://rubygems.org"
# Activate the gem you are reporting the issue against.
- gem "activesupport", "5.0.0"
+ gem "activesupport", "5.0.1"
end
require "active_support/core_ext/object/blank"
diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb
index ed45726e92..d3a7ae4ac4 100644
--- a/guides/bug_report_templates/generic_master.rb
+++ b/guides/bug_report_templates/generic_master.rb
@@ -8,7 +8,6 @@ end
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails"
- gem "arel", github: "rails/arel"
end
require "active_support"
diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb
index 557d23f78c..0f611c8f2b 100644
--- a/guides/rails_guides.rb
+++ b/guides/rails_guides.rb
@@ -1,17 +1,27 @@
-pwd = File.dirname(__FILE__)
-$:.unshift pwd
+$:.unshift __dir__
-begin
- # Guides generation in the Rails repo.
- as_lib = File.join(pwd, "../activesupport/lib")
- ap_lib = File.join(pwd, "../actionpack/lib")
+as_lib = File.expand_path("../activesupport/lib", __dir__)
+ap_lib = File.expand_path("../actionpack/lib", __dir__)
+av_lib = File.expand_path("../actionview/lib", __dir__)
- $:.unshift as_lib if File.directory?(as_lib)
- $:.unshift ap_lib if File.directory?(ap_lib)
-rescue LoadError
- # Guides generation from gems.
- gem "actionpack", ">= 3.0"
-end
+$:.unshift as_lib if File.directory?(as_lib)
+$:.unshift ap_lib if File.directory?(ap_lib)
+$:.unshift av_lib if File.directory?(av_lib)
require "rails_guides/generator"
-RailsGuides::Generator.new.generate
+require "active_support/core_ext/object/blank"
+
+env_value = ->(name) { ENV[name].presence }
+env_flag = ->(name) { "1" == env_value[name] }
+
+version = env_value["RAILS_VERSION"]
+edge = `git rev-parse HEAD`.strip unless version
+
+RailsGuides::Generator.new(
+ edge: edge,
+ version: version,
+ all: env_flag["ALL"],
+ only: env_value["ONLY"],
+ kindle: env_flag["KINDLE"],
+ language: env_value["GUIDES_LANGUAGE"]
+).generate
diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index a818ca9d72..28164a3cb4 100644
--- a/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -1,54 +1,3 @@
-# ---------------------------------------------------------------------------
-#
-# This script generates the guides. It can be invoked via the
-# guides:generate rake task within the guides directory.
-#
-# Guides are taken from the source directory, and the resulting HTML goes into the
-# output directory. Assets are stored under files, and copied to output/files as
-# part of the generation process.
-#
-# Some arguments may be passed via environment variables:
-#
-# WARNINGS
-# If you are writing a guide, please work always with WARNINGS=1. Users can
-# generate the guides, and thus this flag is off by default.
-#
-# Internal links (anchors) are checked. If a reference is broken levenshtein
-# distance is used to suggest an existing one. This is useful since IDs are
-# generated by Markdown from headers and thus edits alter them.
-#
-# Also detects duplicated IDs. They happen if there are headers with the same
-# text. Please do resolve them, if any, so guides are valid XHTML.
-#
-# ALL
-# Set to "1" to force the generation of all guides.
-#
-# ONLY
-# Use ONLY if you want to generate only one or a set of guides. Prefixes are
-# enough:
-#
-# # generates only association_basics.html
-# ONLY=assoc rake guides:generate
-#
-# Separate many using commas:
-#
-# # generates only association_basics.html and command_line.html
-# ONLY=assoc,command rake guides:generate
-#
-# Note that if you are working on a guide generation will by default process
-# only that one, so ONLY is rarely used nowadays.
-#
-# GUIDES_LANGUAGE
-# Use GUIDES_LANGUAGE when you want to generate translated guides in
-# <tt>source/<GUIDES_LANGUAGE></tt> folder (such as <tt>source/es</tt>).
-# Ignore it when generating English guides.
-#
-# EDGE
-# Set to "1" to indicate generated guides should be marked as edge. This
-# inserts a badge and changes the preamble of the home page.
-#
-# ---------------------------------------------------------------------------
-
require "set"
require "fileutils"
@@ -64,46 +13,37 @@ require "rails_guides/levenshtein"
module RailsGuides
class Generator
- attr_reader :guides_dir, :source_dir, :output_dir, :edge, :warnings, :all
-
GUIDES_RE = /\.(?:erb|md)\z/
- def initialize(output = nil)
- set_flags_from_environment
+ def initialize(edge:, version:, all:, only:, kindle:, language:)
+ @edge = edge
+ @version = version
+ @all = all
+ @only = only
+ @kindle = kindle
+ @language = language
- if kindle?
+ if @kindle
check_for_kindlegen
register_kindle_mime_types
end
- initialize_dirs(output)
+ initialize_dirs
create_output_dir_if_needed
- end
-
- def set_flags_from_environment
- @edge = ENV["EDGE"] == "1"
- @warnings = ENV["WARNINGS"] == "1"
- @all = ENV["ALL"] == "1"
- @kindle = ENV["KINDLE"] == "1"
- @version = ENV["RAILS_VERSION"] || "local"
- @lang = ENV["GUIDES_LANGUAGE"]
- end
-
- def register_kindle_mime_types
- Mime::Type.register_alias("application/xml", :opf, %w(opf))
- Mime::Type.register_alias("application/xml", :ncx, %w(ncx))
+ initialize_markdown_renderer
end
def generate
generate_guides
copy_assets
- generate_mobi if kindle?
+ generate_mobi if @kindle
end
private
- def kindle?
- @kindle
+ def register_kindle_mime_types
+ Mime::Type.register_alias("application/xml", :opf, %w(opf))
+ Mime::Type.register_alias("application/xml", :ncx, %w(ncx))
end
def check_for_kindlegen
@@ -114,29 +54,35 @@ module RailsGuides
def generate_mobi
require "rails_guides/kindle"
- out = "#{output_dir}/kindlegen.out"
- Kindle.generate(output_dir, mobi, out)
+ out = "#{@output_dir}/kindlegen.out"
+ Kindle.generate(@output_dir, mobi, out)
puts "(kindlegen log at #{out})."
end
def mobi
- "ruby_on_rails_guides_#@version%s.mobi" % (@lang.present? ? ".#@lang" : "")
+ mobi = "ruby_on_rails_guides_#{@version || @edge[0, 7]}"
+ mobi += ".#{@language}" if @language
+ mobi += ".mobi"
end
- def initialize_dirs(output)
- @guides_dir = File.join(File.dirname(__FILE__), "..")
- @source_dir = "#@guides_dir/source/#@lang"
- @output_dir = if output
- output
- elsif kindle?
- "#@guides_dir/output/kindle/#@lang"
- else
- "#@guides_dir/output/#@lang"
- end.sub(%r</$>, "")
+ def initialize_dirs
+ @guides_dir = File.expand_path("..", __dir__)
+
+ @source_dir = "#{@guides_dir}/source"
+ @source_dir += "/#{@language}" if @language
+
+ @output_dir = "#{@guides_dir}/output"
+ @output_dir += "/kindle" if @kindle
+ @source_dir += "/#{@language}" if @language
end
def create_output_dir_if_needed
- FileUtils.mkdir_p(output_dir)
+ FileUtils.mkdir_p(@output_dir)
+ end
+
+ def initialize_markdown_renderer
+ Markdown::Renderer.edge = @edge
+ Markdown::Renderer.version = @version
end
def generate_guides
@@ -147,27 +93,27 @@ module RailsGuides
end
def guides_to_generate
- guides = Dir.entries(source_dir).grep(GUIDES_RE)
+ guides = Dir.entries(@source_dir).grep(GUIDES_RE)
- if kindle?
- Dir.entries("#{source_dir}/kindle").grep(GUIDES_RE).map do |entry|
+ if @kindle
+ Dir.entries("#{@source_dir}/kindle").grep(GUIDES_RE).map do |entry|
next if entry == "KINDLE.md"
guides << "kindle/#{entry}"
end
end
- ENV.key?("ONLY") ? select_only(guides) : guides
+ @only ? select_only(guides) : guides
end
def select_only(guides)
- prefixes = ENV["ONLY"].split(",").map(&:strip)
+ prefixes = @only.split(",").map(&:strip)
guides.select do |guide|
- guide.start_with?("kindle".freeze, *prefixes)
+ guide.start_with?("kindle", *prefixes)
end
end
def copy_assets
- FileUtils.cp_r(Dir.glob("#{guides_dir}/assets/*"), output_dir)
+ FileUtils.cp_r(Dir.glob("#{@guides_dir}/assets/*"), @output_dir)
end
def output_file_for(guide)
@@ -179,22 +125,28 @@ module RailsGuides
end
def output_path_for(output_file)
- File.join(output_dir, File.basename(output_file))
+ File.join(@output_dir, File.basename(output_file))
end
def generate?(source_file, output_file)
- fin = File.join(source_dir, source_file)
+ fin = File.join(@source_dir, source_file)
fout = output_path_for(output_file)
- all || !File.exist?(fout) || File.mtime(fout) < File.mtime(fin)
+ @all || !File.exist?(fout) || File.mtime(fout) < File.mtime(fin)
end
def generate_guide(guide, output_file)
output_path = output_path_for(output_file)
puts "Generating #{guide} as #{output_file}"
- layout = kindle? ? "kindle/layout" : "layout"
+ layout = @kindle ? "kindle/layout" : "layout"
File.open(output_path, "w") do |f|
- view = ActionView::Base.new(source_dir, edge: @edge, version: @version, mobi: "kindle/#{mobi}", lang: @lang)
+ view = ActionView::Base.new(
+ @source_dir,
+ edge: @edge,
+ version: @version,
+ mobi: "kindle/#{mobi}",
+ language: @language
+ )
view.extend(Helpers)
if guide =~ /\.(\w+)\.erb$/
@@ -202,10 +154,15 @@ module RailsGuides
# Passing a template handler in the template name is deprecated. So pass the file name without the extension.
result = view.render(layout: layout, formats: [$1], file: $`)
else
- body = File.read(File.join(source_dir, guide))
- result = RailsGuides::Markdown.new(view, layout).render(body)
-
- warn_about_broken_links(result) if @warnings
+ body = File.read("#{@source_dir}/#{guide}")
+ result = RailsGuides::Markdown.new(
+ view: view,
+ layout: layout,
+ edge: @edge,
+ version: @version
+ ).render(body)
+
+ warn_about_broken_links(result)
end
f.write(result)
@@ -231,7 +188,7 @@ module RailsGuides
# Footnotes.
anchors += Set.new(html.scan(/<p\s+class="footnote"\s+id="([^"]+)/).flatten)
anchors += Set.new(html.scan(/<sup\s+class="footnote"\s+id="([^"]+)/).flatten)
- return anchors
+ anchors
end
def check_fragment_identifiers(html, anchors)
diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb
index 009e5aff99..bf2cc82c7c 100644
--- a/guides/rails_guides/markdown.rb
+++ b/guides/rails_guides/markdown.rb
@@ -4,12 +4,14 @@ require "rails_guides/markdown/renderer"
module RailsGuides
class Markdown
- def initialize(view, layout)
- @view = view
- @layout = layout
+ def initialize(view:, layout:, edge:, version:)
+ @view = view
+ @layout = layout
+ @edge = edge
+ @version = version
@index_counter = Hash.new(0)
- @raw_header = ""
- @node_ids = {}
+ @raw_header = ""
+ @node_ids = {}
end
def render(body)
@@ -60,7 +62,8 @@ module RailsGuides
autolink: true,
strikethrough: true,
superscript: true,
- tables: true)
+ tables: true
+ )
end
def extract_raw_header_and_body
diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb
index deab741023..20cbd568c9 100644
--- a/guides/rails_guides/markdown/renderer.rb
+++ b/guides/rails_guides/markdown/renderer.rb
@@ -1,9 +1,7 @@
module RailsGuides
class Markdown
class Renderer < Redcarpet::Render::HTML
- def initialize(options = {})
- super
- end
+ cattr_accessor :edge, :version
def block_code(code, language)
<<-HTML
@@ -15,6 +13,16 @@ module RailsGuides
HTML
end
+ def link(url, title, content)
+ if url.start_with?("http://api.rubyonrails.org")
+ %(<a href="#{api_link(url)}">#{content}</a>)
+ elsif title
+ %(<a href="#{url}" title="#{title}">#{content}</a>)
+ else
+ %(<a href="#{url}">#{content}</a>)
+ end
+ end
+
def header(text, header_level)
# Always increase the heading level by 1, so we can use h1, h2 heading in the document
header_level += 1
@@ -23,7 +31,9 @@ HTML
end
def paragraph(text)
- if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/
+ if text =~ %r{^NOTE:\s+Defined\s+in\s+<code>(.*?)</code>\.?$}
+ %(<div class="note"><p>Defined in <code><a href="#{github_file_url($1)}">#{$1}</a></code>.</p></div>)
+ elsif text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/
convert_notes(text)
elsif text.include?("DO NOT READ THIS FILE ON GITHUB")
elsif text =~ /^\[<sup>(\d+)\]:<\/sup> (.+)$/
@@ -79,6 +89,33 @@ HTML
%(<div class="#{css_class}"><p>#{$2.strip}</p></div>)
end
end
+
+ def github_file_url(file_path)
+ tree = version || edge
+
+ root = file_path[%r{(.+)/}, 1]
+ path = case root
+ when "abstract_controller", "action_controller", "action_dispatch"
+ "actionpack/lib/#{file_path}"
+ when /\A(action|active)_/
+ "#{root.sub("_", "")}/lib/#{file_path}"
+ else
+ file_path
+ end
+
+
+ "https://github.com/rails/rails/tree/#{tree}/#{path}"
+ end
+
+ def api_link(url)
+ if url =~ %r{http://api\.rubyonrails\.org/v\d+\.}
+ url
+ elsif edge
+ url.sub("api", "edgeapi")
+ else
+ url.sub(/(?<=\.org)/, "/#{version}")
+ end
+ end
end
end
end
diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md
index 39753cbd6f..5f4be07351 100644
--- a/guides/source/5_0_release_notes.md
+++ b/guides/source/5_0_release_notes.md
@@ -150,7 +150,7 @@ The type of an attribute is given the opportunity to change how dirty
tracking is performed.
See its
-[documentation](http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html)
+[documentation](http://api.rubyonrails.org/v5.0.1/classes/ActiveRecord/Attributes/ClassMethods.html)
for a detailed write up.
@@ -242,7 +242,7 @@ Please refer to the [Changelog][railties] for detailed changes.
[Pull Request](https://github.com/rails/rails/pull/22288))
* New applications are generated with the evented file system monitor enabled
- on Linux and Mac OS X. The feature can be opted out by passing
+ on Linux and macOS. The feature can be opted out by passing
`--skip-listen` to the generator.
([commit](https://github.com/rails/rails/commit/de6ad5665d2679944a9ee9407826ba88395a1003),
[commit](https://github.com/rails/rails/commit/94dbc48887bf39c241ee2ce1741ee680d773f202))
@@ -499,6 +499,9 @@ Please refer to the [Changelog][action-view] for detailed changes.
`datetime-local`.
([Pull Request](https://github.com/rails/rails/pull/25469))
+* Allow blocks while rendering with the `render partial:` helper.
+ ([Pull Request](https://github.com/rails/rails/pull/17974))
+
Action Mailer
-------------
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
index f50bcddbe7..5bd1ea4d22 100644
--- a/guides/source/_welcome.html.erb
+++ b/guides/source/_welcome.html.erb
@@ -1,8 +1,8 @@
-<h2>Ruby on Rails Guides (<%= @edge ? @version[0, 7] : @version %>)</h2>
+<h2>Ruby on Rails Guides (<%= @edge ? @edge[0, 7] : @version %>)</h2>
<% if @edge %>
<p>
- These are <b>Edge Guides</b>, based on the current <a href="https://github.com/rails/rails/tree/<%= @version %>">master</a> branch.
+ These are <b>Edge Guides</b>, based on <a href="https://github.com/rails/rails/tree/<%= @edge %>">master@<%= @edge[0, 7] %></a>.
</p>
<p>
If you are looking for the ones for the stable version, please check
diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md
index 319277ef68..e929945dd0 100644
--- a/guides/source/action_cable_overview.md
+++ b/guides/source/action_cable_overview.md
@@ -240,8 +240,8 @@ WebNotificationsChannel.broadcast_to(
```
The `WebNotificationsChannel.broadcast_to` call places a message in the current
-subscription adapter (by default `redis` for production and `async` for development and
-test environments)'s pubsub queue under a separate broadcasting name for each user.
+subscription adapter (by default `redis` for production and `async` for development and
+test environments)'s pubsub queue under a separate broadcasting name for each user.
For a user with an ID of 1, the broadcasting name would be `web_notifications:1`.
The channel has been instructed to stream everything that arrives at
@@ -530,7 +530,7 @@ Action Cable has two required configurations: a subscription adapter and allowed
### Subscription Adapter
By default, Action Cable looks for a configuration file in `config/cable.yml`.
-The file must specify an adapter and a URL for each Rails environment. See the
+The file must specify an adapter for each Rails environment. See the
[Dependencies](#dependencies) section for additional information on adapters.
```yaml
@@ -543,7 +543,28 @@ test:
production:
adapter: redis
url: redis://10.10.3.153:6381
+ channel_prefix: appname_production
```
+#### Adapter Configuration
+
+Below is a list of the subscription adapters available for end users.
+
+##### Async Adapter
+
+The async adapter is intended for development/testing and should not be used in production.
+
+##### Redis Adapter
+
+Action Cable contains two Redis adapters: "normal" Redis and Evented Redis. Both
+of the adapters require users to provide a URL pointing to the Redis server.
+Additionally, a channel_prefix may be provided to avoid channel name collisions
+when using the same Redis server for multiple applications. See the [Redis PubSub documentation](https://redis.io/topics/pubsub#database-amp-scoping) for more details.
+
+##### PostgreSQL Adapter
+
+The PostgreSQL adapter uses Active Record's connection pool, and thus the
+application's `config/database.yml` database configuration, for its connection.
+This may change in the future. [#27214](https://github.com/rails/rails/issues/27214)
### Allowed Request Origins
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 0825d54cb7..380fdac658 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -160,8 +160,8 @@ When you call the `mail` method now, Action Mailer will detect the two templates
#### Calling the Mailer
Mailers are really just another way to render a view. Instead of rendering a
-view and sending out the HTTP protocol, they are just sending it out through the
-email protocols instead. Due to this, it makes sense to just have your
+view and sending it over the HTTP protocol, they are just sending it out through
+the email protocols instead. Due to this, it makes sense to just have your
controller tell the Mailer to send an email when a user is successfully created.
Setting this up is painfully simple.
@@ -525,7 +525,7 @@ By using the full URL, your links will now work in your emails.
#### Generating URLs with `url_for`
-`url_for` generate full URL by default in templates.
+`url_for` generates a full URL by default in templates.
If you did not configure the `:host` option globally make sure to pass it to
`url_for`.
@@ -574,7 +574,7 @@ Now you can display an image inside your email.
### Sending Multipart Emails
Action Mailer will automatically send multipart emails if you have different
-templates for the same action. So, for our UserMailer example, if you have
+templates for the same action. So, for our `UserMailer` example, if you have
`welcome_email.text.erb` and `welcome_email.html.erb` in
`app/views/user_mailer`, Action Mailer will automatically send a multipart email
with the HTML and text versions setup as different parts.
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index 868daf2435..77bd3c97e8 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -202,11 +202,9 @@ The following methods trigger callbacks:
* `create`
* `create!`
-* `decrement!`
* `destroy`
* `destroy!`
* `destroy_all`
-* `increment!`
* `save`
* `save!`
* `save(validate: false)`
@@ -290,7 +288,7 @@ Article destroyed
Conditional Callbacks
---------------------
-As with validations, we can also make the calling of a callback method conditional on the satisfaction of a given predicate. We can do this using the `:if` and `:unless` options, which can take a symbol, a string, a `Proc` or an `Array`. You may use the `:if` option when you want to specify under which conditions the callback **should** be called. If you want to specify the conditions under which the callback **should not** be called, then you may use the `:unless` option.
+As with validations, we can also make the calling of a callback method conditional on the satisfaction of a given predicate. We can do this using the `:if` and `:unless` options, which can take a symbol, a `Proc` or an `Array`. You may use the `:if` option when you want to specify under which conditions the callback **should** be called. If you want to specify the conditions under which the callback **should not** be called, then you may use the `:unless` option.
### Using `:if` and `:unless` with a `Symbol`
@@ -302,16 +300,6 @@ class Order < ApplicationRecord
end
```
-### Using `:if` and `:unless` with a String
-
-You can also use a string that will be evaluated using `eval` and hence needs to contain valid Ruby code. You should use this option only when the string represents a really short condition:
-
-```ruby
-class Order < ApplicationRecord
- before_save :normalize_card_number, if: "paid_with_card?"
-end
-```
-
### Using `:if` and `:unless` with a `Proc`
Finally, it is possible to associate `:if` and `:unless` with a `Proc` object. This option is best suited when writing short validation methods, usually one-liners:
diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md
index 665e97c470..32b38cde5e 100644
--- a/guides/source/active_record_validations.md
+++ b/guides/source/active_record_validations.md
@@ -916,18 +916,6 @@ class Order < ApplicationRecord
end
```
-### Using a String with `:if` and `:unless`
-
-You can also use a string that will be evaluated using `eval` and needs to
-contain valid Ruby code. You should use this option only when the string
-represents a really short condition.
-
-```ruby
-class Person < ApplicationRecord
- validates :surname, presence: true, if: "name.nil?"
-end
-```
-
### Using a Proc with `:if` and `:unless`
Finally, it's possible to associate `:if` and `:unless` with a `Proc` object
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 25717e04e4..68dde4482f 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -78,9 +78,9 @@ requests can mean faster loading for your application.
Sprockets concatenates all JavaScript files into one master `.js` file and all
CSS files into one master `.css` file. As you'll learn later in this guide, you
can customize this strategy to group files any way you like. In production,
-Rails inserts an MD5 fingerprint into each filename so that the file is cached
-by the web browser. You can invalidate the cache by altering this fingerprint,
-which happens automatically whenever you change the file contents.
+Rails inserts an SHA256 fingerprint into each filename so that the file is
+cached by the web browser. You can invalidate the cache by altering this
+fingerprint, which happens automatically whenever you change the file contents.
The second feature of the asset pipeline is asset minification or compression.
For CSS files, this is done by removing whitespace and comments. For JavaScript,
@@ -106,7 +106,7 @@ or in web browsers) to keep their own copy of the content. When the content is
updated, the fingerprint will change. This will cause the remote clients to
request a new copy of the content. This is generally known as _cache busting_.
-The technique sprockets uses for fingerprinting is to insert a hash of the
+The technique Sprockets uses for fingerprinting is to insert a hash of the
content into the name, usually at the end. For example a CSS file `global.css`
```
@@ -207,7 +207,7 @@ default .coffee and .scss files will not be precompiled on their own. See
precompiling works.
NOTE: You must have an ExecJS supported runtime in order to use CoffeeScript.
-If you are using Mac OS X or Windows, you have a JavaScript runtime installed in
+If you are using macOS or Windows, you have a JavaScript runtime installed in
your operating system. Check [ExecJS](https://github.com/rails/execjs#readme) documentation to know all supported JavaScript runtimes.
You can also disable generation of controller specific asset files by adding the
@@ -346,9 +346,9 @@ Provided that the pipeline is enabled within your application (and not disabled
in the current environment context), this file is served by Sprockets. If a file
exists at `public/assets/rails.png` it is served by the web server.
-Alternatively, a request for a file with an MD5 hash such as
-`public/assets/rails-af27b6a414e6da00003503148be9b409.png` is treated the same
-way. How these hashes are generated is covered in the [In
+Alternatively, a request for a file with an SHA256 hash such as
+`public/assets/rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png`
+is treated the same way. How these hashes are generated is covered in the [In
Production](#in-production) section later on in this guide.
Sprockets will also look through the paths specified in `config.assets.paths`,
@@ -654,7 +654,7 @@ In the production environment Sprockets uses the fingerprinting scheme outlined
above. By default Rails assumes assets have been precompiled and will be
served as static assets by your web server.
-During the precompilation phase an MD5 is generated from the contents of the
+During the precompilation phase an SHA256 is generated from the contents of the
compiled files, and inserted into the filenames as they are written to disk.
These fingerprinted names are used by the Rails helpers in place of the manifest
name.
@@ -743,22 +743,24 @@ Rails.application.config.assets.precompile += %w( admin.js admin.css )
NOTE. Always specify an expected compiled filename that ends with .js or .css,
even if you want to add Sass or CoffeeScript files to the precompile array.
-The task also generates a `manifest-md5hash.json` that contains a list with
-all your assets and their respective fingerprints. This is used by the Rails
-helper methods to avoid handing the mapping requests back to Sprockets. A
-typical manifest file looks like:
+The task also generates a `.sprockets-manifest-md5hash.json` (where `md5hash` is
+an MD5 hash) that contains a list with all your assets and their respective
+fingerprints. This is used by the Rails helper methods to avoid handing the
+mapping requests back to Sprockets. A typical manifest file looks like:
```ruby
-{"files":{"application-723d1be6cc741a3aabb1cec24276d681.js":{"logical_path":"application.js","mtime":"2013-07-26T22:55:03-07:00","size":302506,
-"digest":"723d1be6cc741a3aabb1cec24276d681"},"application-12b3c7dd74d2e9df37e7cbb1efa76a6d.css":{"logical_path":"application.css","mtime":"2013-07-26T22:54:54-07:00","size":1560,
-"digest":"12b3c7dd74d2e9df37e7cbb1efa76a6d"},"application-1c5752789588ac18d7e1a50b1f0fd4c2.css":{"logical_path":"application.css","mtime":"2013-07-26T22:56:17-07:00","size":1591,
-"digest":"1c5752789588ac18d7e1a50b1f0fd4c2"},"favicon-a9c641bf2b81f0476e876f7c5e375969.ico":{"logical_path":"favicon.ico","mtime":"2013-07-26T23:00:10-07:00","size":1406,
-"digest":"a9c641bf2b81f0476e876f7c5e375969"},"my_image-231a680f23887d9dd70710ea5efd3c62.png":{"logical_path":"my_image.png","mtime":"2013-07-26T23:00:27-07:00","size":6646,
-"digest":"231a680f23887d9dd70710ea5efd3c62"}},"assets":{"application.js":
-"application-723d1be6cc741a3aabb1cec24276d681.js","application.css":
-"application-1c5752789588ac18d7e1a50b1f0fd4c2.css",
-"favicon.ico":"favicona9c641bf2b81f0476e876f7c5e375969.ico","my_image.png":
-"my_image-231a680f23887d9dd70710ea5efd3c62.png"}}
+{"files":{"application-aee4be71f1288037ae78b997df388332edfd246471b533dcedaa8f9fe156442b.js":{"logical_path":"application.js","mtime":"2016-12-23T20:12:03-05:00","size":412383,
+"digest":"aee4be71f1288037ae78b997df388332edfd246471b533dcedaa8f9fe156442b","integrity":"sha256-ruS+cfEogDeueLmX3ziDMu39JGRxtTPc7aqPn+FWRCs="},
+"application-86a292b5070793c37e2c0e5f39f73bb387644eaeada7f96e6fc040a028b16c18.css":{"logical_path":"application.css","mtime":"2016-12-23T19:12:20-05:00","size":2994,
+"digest":"86a292b5070793c37e2c0e5f39f73bb387644eaeada7f96e6fc040a028b16c18","integrity":"sha256-hqKStQcHk8N+LA5fOfc7s4dkTq6tp/lub8BAoCixbBg="},
+"favicon-8d2387b8d4d32cecd93fa3900df0e9ff89d01aacd84f50e780c17c9f6b3d0eda.ico":{"logical_path":"favicon.ico","mtime":"2016-12-23T20:11:00-05:00","size":8629,
+"digest":"8d2387b8d4d32cecd93fa3900df0e9ff89d01aacd84f50e780c17c9f6b3d0eda","integrity":"sha256-jSOHuNTTLOzZP6OQDfDp/4nQGqzYT1DngMF8n2s9Dto="},
+"my_image-f4028156fd7eca03584d5f2fc0470df1e0dbc7369eaae638b2ff033f988ec493.png":{"logical_path":"my_image.png","mtime":"2016-12-23T20:10:54-05:00","size":23414,
+"digest":"f4028156fd7eca03584d5f2fc0470df1e0dbc7369eaae638b2ff033f988ec493","integrity":"sha256-9AKBVv1+ygNYTV8vwEcN8eDbxzaequY4sv8DP5iOxJM="}},
+"assets":{"application.js":"application-aee4be71f1288037ae78b997df388332edfd246471b533dcedaa8f9fe156442b.js",
+"application.css":"application-86a292b5070793c37e2c0e5f39f73bb387644eaeada7f96e6fc040a028b16c18.css",
+"favicon.ico":"favicon-8d2387b8d4d32cecd93fa3900df0e9ff89d01aacd84f50e780c17c9f6b3d0eda.ico",
+"my_image.png":"my_image-f4028156fd7eca03584d5f2fc0470df1e0dbc7369eaae638b2ff033f988ec493.png"}}
```
The default location for the manifest is the root of the location specified in
@@ -850,7 +852,7 @@ config.assets.compile = true
On the first request the assets are compiled and cached as outlined in
development above, and the manifest names used in the helpers are altered to
-include the MD5 hash.
+include the SHA256 hash.
Sprockets also sets the `Cache-Control` HTTP header to `max-age=31536000`. This
signals all caches between your server and the client browser that this content
@@ -1115,7 +1117,7 @@ config.assets.js_compressor = :uglifier
```
NOTE: You will need an [ExecJS](https://github.com/rails/execjs#readme)
-supported runtime in order to use `uglifier`. If you are using Mac OS X or
+supported runtime in order to use `uglifier`. If you are using macOS or
Windows you have a JavaScript runtime installed in your operating system.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index b0334bfe4a..de921e2705 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -108,7 +108,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`).
you don't want shown in the logs, such as passwords or credit card
numbers. By default, Rails filters out passwords by adding `Rails.application.config.filter_parameters += [:password]` in `config/initializers/filter_parameter_logging.rb`. Parameters filter works by partial matching regular expression.
-* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details.
+* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://api.rubyonrails.org/classes/ActionDispatch/SSL.html) for details.
* `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes. If you are setting a value for `config.logger` you must manually pass the value of your formatter to your logger before it is wrapped in an `ActiveSupport::TaggedLogging` instance, Rails will not do it for you.
@@ -368,6 +368,11 @@ All these configuration options are delegated to the `I18n` library.
has_many relationships to be displayed with an index as well as the error.
Defaults to `false`.
+* `config.active_record.use_schema_cache_dump` enables users to get schema cache information
+ from `db/schema_cache.yml` (generated by `bin/rails db:schema:cache:dump`), instead of
+ having to send a query to the database to get this information.
+ Defaults to `true`.
+
The MySQL adapter adds one additional configuration option:
* `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. Defaults to `true`.
@@ -626,8 +631,6 @@ 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`.
-* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns `false` (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `new_framework_defaults.rb` which sets the value to `false`. This file is *not* added when running `rails app:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code.
-
* `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.
@@ -1305,7 +1308,7 @@ end
Otherwise, in every request Rails walks the application tree to check if
anything has changed.
-On Linux and Mac OS X no additional gems are needed, but some are required
+On Linux and macOS no additional gems are needed, but some are required
[for *BSD](https://github.com/guard/listen#on-bsd) and
[for Windows](https://github.com/guard/listen#on-windows).
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index 830a546570..fe5437ae5d 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -67,7 +67,7 @@ can expect it to be marked "invalid" as soon as it's reviewed.
Sometimes, the line between 'bug' and 'feature' is a hard one to draw.
Generally, a feature is anything that adds new behavior, while a bug is
anything that causes incorrect behavior. Sometimes,
-the core team will have to make a judgement call. That said, the distinction
+the core team will have to make a judgment call. That said, the distinction
generally just affects which release your patch will get in to; we love feature
submissions! They just won't get backported to maintenance branches.
@@ -335,10 +335,12 @@ file.
#### Testing Active Record
-First, create the databases you'll need. For MySQL and PostgreSQL,
-running the SQL statements `create database activerecord_unittest` and
-`create database activerecord_unittest2` is sufficient. This is not
-necessary for SQLite3.
+First, create the databases you'll need. You can find a list of the required
+table names, usernames, and passwords in `activerecord/test/config.example.yml`.
+
+For MySQL and PostgreSQL, running the SQL statements `create database
+activerecord_unittest` and `create database activerecord_unittest2` is
+sufficient. This is not necessary for SQLite3.
This is how you run the Active Record test suite only for SQLite3:
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index df3003a6a8..ba0cdbf3af 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -683,7 +683,7 @@ Ruby instruction to be executed -- in this case, Active Support's `week` method.
51: #
52: # 2.weeks # => 14 days
53: def weeks
-=> 54: ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
+=> 54: ActiveSupport::Duration.weeks(self)
55: end
56: alias :week :weeks
57:
diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md
index 16c7e782bc..7ec038eb4d 100644
--- a/guides/source/development_dependencies_install.md
+++ b/guides/source/development_dependencies_install.md
@@ -46,7 +46,7 @@ $ cd rails
The test suite must pass with any submitted code. No matter whether you are writing a new patch, or evaluating someone else's, you need to be able to run the tests.
-Install first SQLite3 and its development files for the `sqlite3` gem. Mac OS X
+Install first SQLite3 and its development files for the `sqlite3` gem. On macOS
users are done with:
```bash
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 0020112a1c..180a786237 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -59,7 +59,7 @@ only be enhancing it, rather than changing it drastically.
To see demonstrations of other engines, check out
[Devise](https://github.com/plataformatec/devise), an engine that provides
authentication for its parent applications, or
-[Forem](https://github.com/radar/forem), an engine that provides forum
+[Thredded](https://github.com/thredded/thredded), an engine that provides forum
functionality. There's also [Spree](https://github.com/spree/spree) which
provides an e-commerce platform, and
[RefineryCMS](https://github.com/refinery/refinerycms), a CMS engine.
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 8a451ab793..57b8472462 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -86,7 +86,7 @@ your prompt will look something like `c:\source_code>`
### Installing Rails
-Open up a command line prompt. On Mac OS X open Terminal.app, on Windows choose
+Open up a command line prompt. On macOS open Terminal.app, on Windows choose
"Run" from your Start menu and type 'cmd.exe'. Any commands prefaced with a
dollar sign `$` should be run in the command line. Verify that you have a
current version of Ruby installed:
@@ -98,7 +98,7 @@ ruby 2.3.1p112
TIP: A number of tools exist to help you quickly install Ruby and Ruby
on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org),
-while Mac OS X users can use [Tokaido](https://github.com/tokaido/tokaidoapp).
+while macOS users can use [Tokaido](https://github.com/tokaido/tokaidoapp).
For more installation methods for most Operating Systems take a look at
[ruby-lang.org](https://www.ruby-lang.org/en/documentation/installation/).
@@ -206,7 +206,7 @@ folder directly to the Ruby interpreter e.g. `ruby bin\rails server`.
TIP: Compiling CoffeeScript and JavaScript asset compression requires you
have a JavaScript runtime available on your system, in the absence
of a runtime you will see an `execjs` error during asset compilation.
-Usually Mac OS X and Windows come with a JavaScript runtime installed.
+Usually macOS and Windows come with a JavaScript runtime installed.
Rails adds the `therubyracer` gem to the generated `Gemfile` in a
commented line for new apps and you can uncomment if you need it.
`therubyrhino` is the recommended runtime for JRuby users and is added by
@@ -221,7 +221,7 @@ your application in action, open a browser window and navigate to
TIP: To stop the web server, hit Ctrl+C in the terminal window where it's
running. To verify the server has stopped you should see your command prompt
-cursor again. For most UNIX-like systems including Mac OS X this will be a
+cursor again. For most UNIX-like systems including macOS this will be a
dollar sign `$`. In development mode, Rails does not generally require you to
restart the server; changes you make in files will be automatically picked up by
the server.
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index fd54bca4ff..ed8cf8a344 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -72,11 +72,13 @@ I18n.l Time.now
There are also attribute readers and writers for the following attributes:
```ruby
-load_path # Announce your custom translation files
-locale # Get and set the current locale
-default_locale # Get and set the default locale
-exception_handler # Use a different exception_handler
-backend # Use a different backend
+load_path # Announce your custom translation files
+locale # Get and set the current locale
+default_locale # Get and set the default locale
+available_locales # Whitelist locales available for the application
+enforce_available_locales # Enforce locale whitelisting (true or false)
+exception_handler # Use a different exception_handler
+backend # Use a different backend
```
So, let's internationalize a simple Rails application from the ground up in the next chapters!
@@ -124,6 +126,9 @@ The load path must be specified before any translations are looked up. To change
# Where the I18n library should search for translation files
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]
+# Whitelist locales available for the application
+I18n.available_locales = [:en, :pt]
+
# Set default locale to something other than :en
I18n.default_locale = :pt
```
@@ -404,6 +409,35 @@ NOTE: You need to restart the server when you add new locale files.
You may use YAML (`.yml`) or plain Ruby (`.rb`) files for storing your translations in SimpleStore. YAML is the preferred option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.)
+If your translations are stored in YAML files, certain keys must be escaped. They are:
+
+* true, on, yes
+* false, off, no
+
+Examples:
+
+```erb
+# config/locales/en.yml
+en:
+ success:
+ 'true': 'True!'
+ 'on': 'On!'
+ 'false': 'False!'
+ failure:
+ true: 'True!'
+ off: 'Off!'
+ false: 'False!'
+```
+
+```ruby
+I18n.t 'success.true' # => 'True!'
+I18n.t 'success.on' # => 'On!'
+I18n.t 'success.false' # => 'False!'
+I18n.t 'failure.false' # => Translation Missing
+I18n.t 'failure.off' # => Translation Missing
+I18n.t 'failure.true' # => Translation Missing
+```
+
### Passing Variables to Translations
One key consideration for successfully internationalizing an application is to
@@ -667,7 +701,7 @@ end
### Pluralization
-In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ar), [Japanese](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ja), [Russian](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). Thus, the I18n API provides a flexible pluralization feature.
+In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar), [Japanese](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ja), [Russian](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://cldr.unicode.org/index/cldr-spec/plural-rules). Thus, the I18n API provides a flexible pluralization feature.
The `:count` interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR:
diff --git a/guides/source/initialization.md b/guides/source/initialization.md
index aa7bbcc19b..3ea156c6fe 100644
--- a/guides/source/initialization.md
+++ b/guides/source/initialization.md
@@ -168,7 +168,7 @@ module Rails::Command
class << self
def invoke(namespace, args = [], **config)
namespace = namespace.to_s
- namespace = "help" if namespace.blank? || Thor::HELP_MAPPINGS.include?(namespace)
+ namespace = "help" if namespace.blank? || HELP_MAPPINGS.include?(namespace)
namespace = "version" if %w( -v --version ).include? namespace
if command = find_by_namespace(namespace)
diff --git a/guides/source/kindle/rails_guides.opf.erb b/guides/source/kindle/rails_guides.opf.erb
index 547abcbc19..63eeb007d7 100644
--- a/guides/source/kindle/rails_guides.opf.erb
+++ b/guides/source/kindle/rails_guides.opf.erb
@@ -5,7 +5,7 @@
<meta name="cover" content="cover" />
<dc-metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
- <dc:title>Ruby on Rails Guides (<%= @version %>)</dc:title>
+ <dc:title>Ruby on Rails Guides (<%= @version || "master@#{@edge[0, 7]}" %>)</dc:title>
<dc:language>en-us</dc:language>
<dc:creator>Ruby on Rails</dc:creator>
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index c8702f54fc..48bb3147f3 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -411,6 +411,8 @@ render formats: :xml
render formats: [:json, :xml]
```
+If a template with the specified format does not exist an `ActionView::MissingTemplate` error is raised.
+
#### Finding Layouts
To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions.
@@ -1155,7 +1157,7 @@ To pass a local variable to a partial in only specific cases use the `local_assi
<%= render article, full: true %>
```
-* `_articles.html.erb`
+* `_article.html.erb`
```erb
<h2><%= article.title %></h2>
diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md
index 50866350f8..de63e193f4 100644
--- a/guides/source/ruby_on_rails_guides_guidelines.md
+++ b/guides/source/ruby_on_rails_guides_guidelines.md
@@ -50,6 +50,48 @@ Use the same inline formatting as regular text:
##### The `:content_type` Option
```
+Linking to the API
+------------------
+
+Links to the API (`api.rubyonrails.org`) are processed by the guides generator in the following manner:
+
+Links that include a release tag are left untouched. For example
+
+```
+http://api.rubyonrails.org/v5.0.1/classes/ActiveRecord/Attributes/ClassMethods.html
+```
+
+is not modified.
+
+Please use these in release notes, since they should point to the corresponding version no matter the target being generated.
+
+If the link does not include a release tag and edge guides are being generated, the domain is replaced by `edgeapi.rubyonrails.org`. For example,
+
+```
+http://api.rubyonrails.org/classes/ActionDispatch/Response.html
+```
+
+becomes
+
+```
+http://edgeapi.rubyonrails.org/classes/ActionDispatch/Response.html
+```
+
+If the link does not include a release tag and release guides are being generated, the Rails version is injected. For example, if we are generating the guides for v5.1.0 the link
+
+```
+http://api.rubyonrails.org/classes/ActionDispatch/Response.html
+```
+
+becomes
+
+```
+http://api.rubyonrails.org/v5.1.0/classes/ActionDispatch/Response.html
+```
+
+Please don't link to `edgeapi.rubyonrails.org` manually.
+
+
API Documentation Guidelines
----------------------------
@@ -97,8 +139,6 @@ By default, guides that have not been modified are not processed, so `ONLY` is r
To force processing all the guides, pass `ALL=1`.
-It is also recommended that you work with `WARNINGS=1`. This detects duplicate IDs and warns about broken internal links.
-
If you want to generate guides in a language other than English, you can keep them in a separate directory under `source` (eg. `source/es`) and use the `GUIDES_LANGUAGE` environment variable:
```
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 6f783089a9..652030a733 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -8,7 +8,7 @@ This guide covers built-in mechanisms in Rails for testing your application.
After reading this guide, you will know:
* Rails testing terminology.
-* How to write unit, functional, and integration tests for your application.
+* How to write unit, functional, integration, and system tests for your application.
* Other popular testing approaches and plugins.
--------------------------------------------------------------------------------
@@ -33,18 +33,27 @@ Rails creates a `test` directory for you as soon as you create a Rails project u
```bash
$ ls -F test
-controllers/ helpers/ mailers/ test_helper.rb
-fixtures/ integration/ models/
+controllers/ helpers/ mailers/ system/ test_helper.rb
+fixtures/ integration/ models/ application_system_test_case.rb
```
The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers.
+The system test directory holds system tests, which are used for full browser
+testing of your application. System tests allow you to test your application
+the way your users experience it and help you test your JavaScript as well.
+System tests inherit from Capybara and perform in browser tests for your
+application.
+
Fixtures are a way of organizing test data; they reside in the `fixtures` directory.
A `jobs` directory will also be created when an associated test is first generated.
The `test_helper.rb` file holds the default configuration for your tests.
+The `application_system_test_case.rb` holds the default configuration for your system
+tests.
+
### The Test Environment
@@ -358,6 +367,7 @@ All the basic assertions such as `assert_equal` defined in `Minitest::Assertions
* [`ActionView::TestCase`](http://api.rubyonrails.org/classes/ActionView/TestCase.html)
* [`ActionDispatch::IntegrationTest`](http://api.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html)
* [`ActiveJob::TestCase`](http://api.rubyonrails.org/classes/ActiveJob/TestCase.html)
+* [`ActionDispatch::SystemTestCase`](http://api.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html)
Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests.
@@ -587,6 +597,177 @@ create test/fixtures/articles.yml
Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html).
+System Testing
+--------------
+
+System tests are full-browser tests that can be used to test your application's
+JavaScript and user experience. System tests use Capybara as a base.
+
+System tests allow for running tests in either a real browser or a headless
+driver for testing full user interactions with your application.
+
+For creating Rails system tests, you use the `test/system` directory in your
+application. Rails provides a generator to create a system test skeleton for you.
+
+```bash
+$ bin/rails generate system_test users_create_test
+ invoke test_unit
+ create test/system/users_create_test.rb
+```
+
+Here's what a freshly-generated system test looks like:
+
+```ruby
+require "application_system_test_case"
+
+class UsersCreateTest < ApplicationSystemTestCase
+ visit users_url
+
+ assert_selector "h1", text: "Users"
+end
+```
+
+By default, system tests are run with the Selenium driver, using the Chrome
+browser, and a screen size of 1400x1400. The next section explains how to
+change the default settings.
+
+### Changing the default settings
+
+Rails makes changing the default settings for system tests very simple. All
+the setup is abstracted away so you can focus on writing your tests.
+
+When you generate a new application or scaffold, an `application_system_test_case.rb` file
+is created in the test directory. This is where all the configuration for your
+system tests should live.
+
+If you want to change the default settings you can simply 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
+`application_system_test_case.rb` file do the following:
+
+```ruby
+require "test_helper"
+require "capybara/poltergeist"
+
+class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ driven_by :poltergeist
+end
+```
+
+The driver name is a required argument for `driven_by`. The optional arguments
+that can be passed to `driven_by` are `:using` for the browser (this will only
+be used for non-headless drivers like Selenium), `:on` for the port Puma should
+use, and `:screen_size` to change the size of the screen for screenshots.
+
+```ruby
+require "test_helper"
+
+class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :firefox
+end
+```
+
+If your Capybara configuration requires more setup than provided by Rails, all
+of that configuration can be put into the `application_system_test_case.rb` file.
+
+Please see [Capybara's documentation](https://github.com/teamcapybara/capybara#setup)
+for additional settings.
+
+### Screenshot Helper
+
+The `ScreenshotHelper` is a helper designed to capture screenshots of your tests.
+This can be helpful for viewing the browser at the point a test failed, or
+to view screenshots later for debugging.
+
+Two methods are provided: `take_screenshot` and `take_failed_screenshot`.
+`take_failed_screenshot` is automatically included in `after_teardown` inside
+Rails.
+
+The `take_screenshot` helper method can be included anywhere in your tests to
+take a screenshot of the browser.
+
+### Implementing a system test
+
+Now we're going to add a system test to our blog application. We'll demonstrate
+writing a system test by visiting the index page and creating a new blog article.
+
+If you used the scaffold generator, a system test skeleton is automatically
+created for you. If you did not use the generator start by creating a system
+test skeleton.
+
+```bash
+$ bin/rails generate system_test articles
+```
+
+It should have created a test file placeholder for us. With the output of the
+previous command you should see:
+
+```bash
+ invoke test_unit
+ create test/system/articles_test.rb
+```
+
+Now let's open that file and write our first assertion:
+
+```ruby
+require "application_system_test_case"
+
+class ArticlesTest < ApplicationSystemTestCase
+ test "viewing the index" do
+ visit articles_path
+ assert_selector "h1", text: "Articles"
+ end
+end
+```
+
+The test should see that there is an h1 on the articles index and pass.
+
+Run the system tests.
+
+```bash
+bin/rails test:system
+```
+
+#### Creating articles system test
+
+Now let's test the flow for creating a new article in our blog.
+
+```ruby
+test "creating an article" do
+ visit articles_path
+
+ click_on "New Article"
+
+ fill_in "Title", with: "Creating an Article"
+ fill_in "Body", with: "Created this article successfully!"
+
+ click_on "Create Article"
+
+ assert_text "Creating an Article"
+end
+```
+
+The first step is to call `visit articles_path`. This will take the test to the
+articles index page.
+
+Then the `click_on "New Article"` will find the "New Article" button on the
+index page. This will redirect the browser to `/articles/new`.
+
+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
+that the text from the article title is on the articles index page.
+
+#### Taking it further
+
+The beauty of system testing is that it is similar to integration testing in
+that it tests the user's interaction with your controller, model, and view, but
+system testing is much more robust and actually tests your application as if
+a real user were using it. Going forward, you can test anything that the user
+themselves would do in your application such as commenting, deleting articles,
+publishing draft articles, etc.
Integration Testing
-------------------
@@ -868,7 +1049,7 @@ class ArticlesControllerTest < ActionDispatch::IntegrationTest
assert_equal "index", @controller.action_name
assert_equal "application/x-www-form-urlencoded", @request.media_type
- assert_match "Articles", @response.body
+ assert_match "Articles", @response.body
end
end
```
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 8a3b3b84b4..8ba00a2b10 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -140,6 +140,8 @@ See [#19034](https://github.com/rails/rails/pull/19034) for more details.
### Rails Controller Testing
+#### Extraction of some helper methods to `rails-controller-testing`
+
`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.
@@ -147,6 +149,14 @@ your Gemfile.
If you are using Rspec for testing, please see the extra configuration required in the gem's
documentation.
+#### New behavior when uploading files
+
+If you are using `ActionDispatch::Http::UploadedFile` in your tests to
+upload files, you will need to change to use the similar `Rack::Test::UploadedFile`
+class instead.
+
+See [#26404](https://github.com/rails/rails/issues/26404) for more details.
+
### Autoloading is Disabled After Booting in the Production Environment
Autoloading is now disabled after booting in the production environment by
@@ -703,7 +713,7 @@ There are a few major changes related to JSON handling in Rails 4.1.
MultiJSON has reached its [end-of-life](https://github.com/rails/rails/pull/10576)
and has been removed from Rails.
-If your application currently depend on MultiJSON directly, you have a few options:
+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
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index d7d1a66863..f9294b6616 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,4 +1,58 @@
-* The `log:clear` task clear all environments log files by default.
+* Fix running multiple tests in one `rake` command
+
+ e.g. `bin/rake test:models test:controllers`
+
+ *Dominic Cleal*
+
+* Add option to configure Ruby's warning behaviour to test runner.
+
+ *Yuji Yaginuma*
+
+* Initialize git repo when generating new app, if option `--skip-git`
+ is not provided.
+
+ *Dino Maric*
+
+* Install Byebug gem as default in Windows (mingw and x64_mingw) platform.
+
+ *Junichi Ito*
+
+* Make every Rails command work within engines.
+
+ *Sean Collins*, *Yuji Yaginuma*
+
+* Don't generate HTML/ERB templates for scaffold controller with `--api` flag.
+
+ Fixes #27591.
+
+ *Prathamesh Sonpatki*
+
+* Make `Rails.env` fall back to `development` when `RAILS_ENV` and `RACK_ENV` is an empty string.
+
+ *Daniel Deng*
+
+* Remove deprecated `CONTROLLER` environment variable for `routes` task.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated tasks: `rails:update`, `rails:template`, `rails:template:copy`,
+ `rails:update:configs` and `rails:update:bin`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated file `rails/rack/debugger`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `config.serve_static_files`.
+
+ *Rafael Mendonça França*
+
+* Remove deprecated `config.static_cache_control`.
+
+ *Rafael Mendonça França*
+
+* The `log:clear` task clear all environments log files by default.
*Yuji Yaginuma*
@@ -14,7 +68,7 @@
*DHH*
-* Add Yarn support in new apps with a yarn binstub and vendor/package.json. Skippable via --skip-yarn option.
+* Add Yarn support in new apps with a yarn binstub and package.json. Skippable via --skip-yarn option.
*Liceth Ovalles*, *Guillermo Iguaran*, *DHH*
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index ee48043a50..00add5829d 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -7,6 +7,7 @@ require "active_support/dependencies/autoload"
require "active_support/core_ext/kernel/reporting"
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/object/blank"
require "rails/application"
require "rails/version"
@@ -67,7 +68,7 @@ module Rails
# Rails.env.development? # => true
# Rails.env.production? # => false
def env
- @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development")
+ @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence || "development")
end
# Sets the Rails environment.
diff --git a/railties/lib/rails/api/generator.rb b/railties/lib/rails/api/generator.rb
new file mode 100644
index 0000000000..dcc491783c
--- /dev/null
+++ b/railties/lib/rails/api/generator.rb
@@ -0,0 +1,28 @@
+require "sdoc"
+
+class RDoc::Generator::API < RDoc::Generator::SDoc # :nodoc:
+ RDoc::RDoc.add_generator self
+
+ def generate_class_tree_level(classes, visited = {})
+ # Only process core extensions on the first visit.
+ if visited.empty?
+ core_exts, classes = classes.partition { |klass| core_extension?(klass) }
+
+ super.unshift([ "Core extensions", "", "", build_core_ext_subtree(core_exts, visited) ])
+ else
+ super
+ end
+ end
+
+ private
+ def build_core_ext_subtree(classes, visited)
+ classes.map do |klass|
+ [ klass.name, klass.document_self_or_methods ? klass.path : "", "",
+ generate_class_tree_level(klass.classes_and_modules, visited) ]
+ end
+ end
+
+ def core_extension?(klass)
+ klass.name != "ActiveSupport" && klass.in_files.any? { |file| file.absolute_name.include?("core_ext") }
+ end
+end
diff --git a/railties/lib/rails/api/task.rb b/railties/lib/rails/api/task.rb
index bc670b1d75..0c0343114f 100644
--- a/railties/lib/rails/api/task.rb
+++ b/railties/lib/rails/api/task.rb
@@ -1,4 +1,5 @@
require "rdoc/task"
+require "rails/api/generator"
module Rails
module API
@@ -8,8 +9,7 @@ module Rails
include: %w(
README.rdoc
lib/active_support/**/*.rb
- ),
- exclude: "lib/active_support/vendor/*"
+ )
},
"activerecord" => {
@@ -69,7 +69,11 @@ module Rails
README.rdoc
lib/**/*.rb
),
- exclude: "lib/rails/generators/rails/**/templates/**/*.rb"
+ exclude: %w(
+ lib/rails/generators/**/templates/**/*.rb
+ lib/rails/test_unit/*
+ lib/rails/api/generator.rb
+ )
}
}
@@ -80,7 +84,7 @@ module Rails
# Be lazy computing stuff to have as light impact as possible to
# the rest of tasks.
before_running_rdoc do
- load_and_configure_sdoc
+ configure_sdoc
configure_rdoc_files
setup_horo_variables
end
@@ -91,20 +95,15 @@ module Rails
# no-op
end
- def load_and_configure_sdoc
- require "sdoc"
-
+ def configure_sdoc
self.title = "Ruby on Rails API"
self.rdoc_dir = api_dir
options << "-m" << api_main
options << "-e" << "UTF-8"
- options << "-f" << "sdoc"
+ options << "-f" << "api"
options << "-T" << "rails"
- rescue LoadError
- $stderr.puts %(Unable to load SDoc, please add\n\n gem 'sdoc', require: false\n\nto the Gemfile.)
- exit 1
end
def configure_rdoc_files
@@ -147,7 +146,7 @@ module Rails
end
class RepoTask < Task
- def load_and_configure_sdoc
+ def configure_sdoc
super
options << "-g" # link to GitHub, SDoc flag
end
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index f96432c89f..1a6aed7ce4 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -72,7 +72,7 @@ module Rails
# on one of the applications to create a copy of the application which shares
# the configuration.
#
- # If you decide to define rake tasks, runners, or initializers in an
+ # If you decide to define Rake tasks, runners, or initializers in an
# application other than +Rails.application+, then you must run them manually.
class Application < Engine
autoload :Bootstrap, "rails/application/bootstrap"
@@ -265,8 +265,8 @@ module Rails
end
end
- # If you try to define a set of rake tasks on the instance, these will get
- # passed up to the rake tasks defined on the application's class.
+ # If you try to define a set of Rake tasks on the instance, these will get
+ # passed up to the Rake tasks defined on the application's class.
def rake_tasks(&block)
self.class.rake_tasks(&block)
end
@@ -511,7 +511,7 @@ module Rails
def validate_secret_key_config! #:nodoc:
if secrets.secret_key_base.blank?
- ActiveSupport::Deprecation.warn "You didn't set `secret_key_base`. " +
+ ActiveSupport::Deprecation.warn "You didn't set `secret_key_base`. " \
"Read the upgrade documentation to learn more about this new config option."
if secrets.secret_token.blank?
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 11da271501..6102af3fff 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -48,8 +48,8 @@ INFO
logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDERR))
logger.level = ActiveSupport::Logger::WARN
logger.warn(
- "Rails Error: Unable to access log file. Please ensure that #{path} exists and is writable " +
- "(ie, make it writable for user and group: chmod 0664 #{path}). " +
+ "Rails Error: Unable to access log file. Please ensure that #{path} exists and is writable " \
+ "(ie, make it writable for user and group: chmod 0664 #{path}). " \
"The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
)
logger
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 810750ed35..b0d33f87a3 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -3,9 +3,6 @@ require "active_support/file_update_checker"
require "rails/engine/configuration"
require "rails/source_annotation_extractor"
-require "active_support/deprecation"
-require "active_support/core_ext/string/strip" # for strip_heredoc
-
module Rails
class Application
class Configuration < ::Rails::Engine::Configuration
@@ -19,11 +16,11 @@ module Rails
:beginning_of_week, :filter_redirect, :x, :enable_dependency_loading
attr_writer :log_level
- attr_reader :encoding, :api_only, :static_cache_control
+ attr_reader :encoding, :api_only
def initialize(*)
super
- self.encoding = "utf-8"
+ self.encoding = Encoding::UTF_8
@allow_concurrency = nil
@consider_all_requests_local = false
@filter_parameters = []
@@ -56,35 +53,6 @@ module Rails
@enable_dependency_loading = false
end
- def static_cache_control=(value)
- ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
- `config.static_cache_control` is deprecated and will be removed in Rails 5.1.
- Please use
- `config.public_file_server.headers = { 'Cache-Control' => '#{value}' }`
- instead.
- eow
-
- @static_cache_control = value
- end
-
- def serve_static_files
- ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
- `config.serve_static_files` is deprecated and will be removed in Rails 5.1.
- Please use `config.public_file_server.enabled` instead.
- eow
-
- @public_file_server.enabled
- end
-
- def serve_static_files=(value)
- ActiveSupport::Deprecation.warn <<-eow.strip_heredoc
- `config.serve_static_files` is deprecated and will be removed in Rails 5.1.
- Please use `config.public_file_server.enabled = #{value}` instead.
- eow
-
- @public_file_server.enabled = value
- end
-
def encoding=(value)
@encoding = value
silence_warnings do
@@ -161,7 +129,7 @@ module Rails
def colorize_logging=(val)
ActiveSupport::LogSubscriber.colorize_logging = val
- self.generators.colorize_logging = val
+ generators.colorize_logging = val
end
def session_store(new_session_store = nil, **options)
diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb
index d070aca2dd..8fe48feefb 100644
--- a/railties/lib/rails/application/default_middleware_stack.rb
+++ b/railties/lib/rails/application/default_middleware_stack.rb
@@ -19,7 +19,6 @@ module Rails
if config.public_file_server.enabled
headers = config.public_file_server.headers || {}
- headers["Cache-Control".freeze] = config.static_cache_control if config.static_cache_control
middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers
end
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index a855e8fab0..c027d06663 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -124,6 +124,7 @@ module Rails
# the hook are taken into account.
initializer :set_routes_reloader_hook do |app|
reloader = routes_reloader
+ reloader.eager_load = app.config.eager_load
reloader.execute_if_updated
reloaders << reloader
app.reloader.to_run do
diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb
index cf0a4e128f..e02ef629f2 100644
--- a/railties/lib/rails/application/routes_reloader.rb
+++ b/railties/lib/rails/application/routes_reloader.rb
@@ -4,11 +4,13 @@ module Rails
class Application
class RoutesReloader
attr_reader :route_sets, :paths
- delegate :execute_if_updated, :execute, :updated?, to: :updater
+ attr_accessor :eager_load
+ delegate :updated?, to: :updater
def initialize
@paths = []
@route_sets = []
+ @eager_load = false
end
def reload!
@@ -19,6 +21,19 @@ module Rails
revert
end
+ def execute
+ ret = updater.execute
+ route_sets.each(&:eager_load!) if eager_load
+ ret
+ end
+
+ def execute_if_updated
+ if updated = updater.execute_if_updated
+ route_sets.each(&:eager_load!) if eager_load
+ end
+ updated
+ end
+
private
def updater
diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb
index ddb953543f..13f3b90b6d 100644
--- a/railties/lib/rails/command.rb
+++ b/railties/lib/rails/command.rb
@@ -15,6 +15,8 @@ module Rails
include Behavior
+ HELP_MAPPINGS = %w(-h -? --help)
+
class << self
def hidden_commands # :nodoc:
@hidden_commands ||= []
@@ -27,7 +29,7 @@ module Rails
# Receives a namespace, arguments and the behavior to invoke the command.
def invoke(namespace, args = [], **config)
namespace = namespace.to_s
- namespace = "help" if namespace.blank? || Thor::HELP_MAPPINGS.include?(namespace)
+ namespace = "help" if namespace.blank? || HELP_MAPPINGS.include?(namespace)
namespace = "version" if %w( -v --version ).include? namespace
if command = find_by_namespace(namespace)
@@ -37,7 +39,7 @@ module Rails
end
end
- # Rails finds namespaces similar to thor, it only adds one rule:
+ # Rails finds namespaces similar to Thor, it only adds one rule:
#
# Command names must end with "_command.rb". This is required because Rails
# looks in load paths and loads the command just before it's going to be used.
diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb
index 31b656ec31..8fda1c87c6 100644
--- a/railties/lib/rails/command/actions.rb
+++ b/railties/lib/rails/command/actions.rb
@@ -8,11 +8,16 @@ module Rails
Dir.chdir(File.expand_path("../../", APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
end
- if defined?(ENGINE_PATH)
- def require_application_and_environment!
- require ENGINE_PATH
+ def require_application_and_environment!
+ require ENGINE_PATH if defined?(ENGINE_PATH)
+
+ if defined?(APP_PATH)
+ require APP_PATH
+ Rails.application.require_environment!
end
+ end
+ if defined?(ENGINE_PATH)
def load_tasks
Rake.application.init("rails")
Rake.application.load_rakefile
@@ -24,11 +29,6 @@ module Rails
engine.load_generators
end
else
- def require_application_and_environment!
- require APP_PATH
- Rails.application.require_environment!
- end
-
def load_tasks
Rails.application.load_tasks
end
diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb
index 7ae190433a..1435792536 100644
--- a/railties/lib/rails/command/base.rb
+++ b/railties/lib/rails/command/base.rb
@@ -56,7 +56,7 @@ module Rails
end
def perform(command, args, config) # :nodoc:
- command = nil if Thor::HELP_MAPPINGS.include?(args.first)
+ command = nil if Rails::Command::HELP_MAPPINGS.include?(args.first)
dispatch(command, args.dup, nil, config)
end
diff --git a/railties/lib/rails/commands/help/USAGE b/railties/lib/rails/commands/help/USAGE
index 348f41861f..c5f8ab72bb 100644
--- a/railties/lib/rails/commands/help/USAGE
+++ b/railties/lib/rails/commands/help/USAGE
@@ -1,16 +1,3 @@
-Usage: bin/rails COMMAND [args] [options]
-<% if engine? %>
-The common Rails commands available for engines are:
- generate Generate new code (short-cut alias: "g")
- destroy Undo code generated with "generate" (short-cut alias: "d")
- test Run tests (short-cut alias: "t")
-
-All commands can be run with -h for more information.
-
-If you want to run any commands that need to be run in context
-of the application, like `bin/rails server` or `bin/rails console`,
-you should do it from the application's directory (typically test/dummy).
-<% else %>
The most common rails commands are:
generate Generate new code (short-cut alias: "g")
console Start the Rails console (short-cut alias: "c")
@@ -18,10 +5,11 @@ The most common rails commands are:
test Run tests (short-cut alias: "t")
dbconsole Start a console for the database specified in config/database.yml
(short-cut alias: "db")
+<% unless engine? %>
new Create a new Rails application. "rails new my_app" creates a
new application called MyApp in "./my_app"
+<% end %>
All commands can be run with -h (or --help) for more information.
-<% end %>
In addition to those commands, there are:
diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb
index 15c636103b..d58721f648 100644
--- a/railties/lib/rails/commands/server/server_command.rb
+++ b/railties/lib/rails/commands/server/server_command.rb
@@ -95,7 +95,7 @@ module Rails
module Command
class ServerCommand < Base # :nodoc:
- DEFAULT_PID_PATH = File.expand_path("tmp/pids/server.pid").freeze
+ DEFAULT_PID_PATH = "tmp/pids/server.pid".freeze
class_option :port, aliases: "-p", type: :numeric,
desc: "Runs Rails on the specified port.", banner: :port, default: 3000
@@ -141,7 +141,7 @@ module Rails
config: options[:config],
environment: environment,
daemonize: options[:daemon],
- pid: options[:pid],
+ pid: pid,
caching: options["dev-caching"],
restart_cmd: restart_command
}
@@ -165,6 +165,10 @@ module Rails
"bin/rails server #{@server} #{@original_options.join(" ")}"
end
+ def pid
+ File.expand_path(options[:pid])
+ end
+
def self.banner(*)
"rails server [puma, thin etc] [options]"
end
diff --git a/railties/lib/rails/commands/test/test_command.rb b/railties/lib/rails/commands/test/test_command.rb
index 7bf8f61137..629fb5b425 100644
--- a/railties/lib/rails/commands/test/test_command.rb
+++ b/railties/lib/rails/commands/test/test_command.rb
@@ -11,7 +11,7 @@ module Rails
def perform(*)
$LOAD_PATH << Rails::Command.root.join("test")
- Minitest.run_via[:rails] = true
+ Minitest.run_via = :rails
require "active_support/testing/autorun"
end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 2dd1fb3273..dc0b158bd4 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -109,7 +109,7 @@ module Rails
#
# == Endpoint
#
- # An engine can also be a rack application. It can be useful if you have a rack application that
+ # An engine can also be a Rack application. It can be useful if you have a Rack application that
# you would like to wrap with +Engine+ and provide with some of the +Engine+'s features.
#
# To do that, use the +endpoint+ method:
@@ -128,7 +128,7 @@ module Rails
#
# == Middleware stack
#
- # As an engine can now be a rack endpoint, it can also have a middleware
+ # As an engine can now be a Rack endpoint, it can also have a middleware
# stack. The usage is exactly the same as in <tt>Application</tt>:
#
# module MyEngine
@@ -380,7 +380,7 @@ module Rails
def isolate_namespace(mod)
engine_name(generate_railtie_name(mod.name))
- self.routes.default_scope = { module: ActiveSupport::Inflector.underscore(mod.name) }
+ routes.default_scope = { module: ActiveSupport::Inflector.underscore(mod.name) }
self.isolated = true
unless mod.respond_to?(:railtie_namespace)
@@ -499,7 +499,7 @@ module Rails
paths["app/helpers"].existent
end
- # Returns the underlying rack application for this engine.
+ # Returns the underlying Rack application for this engine.
def app
@app || @app_build_lock.synchronize {
@app ||= begin
@@ -549,7 +549,7 @@ module Rails
load(seed_file) if seed_file
end
- # Add configured load paths to ruby load paths and remove duplicates.
+ # Add configured load paths to Ruby's load path, and remove duplicate entries.
initializer :set_load_path, before: :bootstrap_hook do
_all_load_paths.reverse_each do |path|
$LOAD_PATH.unshift(path) if File.directory?(path)
@@ -573,7 +573,7 @@ module Rails
end
initializer :add_routing_paths do |app|
- routing_paths = self.paths["config/routes.rb"].existent
+ routing_paths = paths["config/routes.rb"].existent
if routes? || routing_paths.any?
app.routes_reloader.paths.unshift(*routing_paths)
@@ -661,7 +661,6 @@ module Rails
end
def self.find_root_with_flag(flag, root_path, default = nil) #:nodoc:
-
while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}")
parent = File.dirname(root_path)
root_path = parent != root_path && parent
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index a23ae44b0b..b9ef63243a 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -1,12 +1,7 @@
-require "rails/command"
+unless defined?(APP_PATH)
+ if File.exist?(File.expand_path("test/dummy/config/application.rb", ENGINE_ROOT))
+ APP_PATH = File.expand_path("test/dummy/config/application", ENGINE_ROOT)
+ end
+end
-aliases = {
- "g" => "generate",
- "d" => "destroy",
- "t" => "test"
-}
-
-command = ARGV.shift
-command = aliases[command] || command
-
-Rails::Command.invoke command, ARGV
+require "rails/commands"
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index e1980a42ad..85f66cc416 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -62,6 +62,7 @@ module Rails
stylesheets: true,
stylesheet_engine: :css,
scaffold_stylesheet: true,
+ system_tests: nil,
test_framework: false,
template_engine: :erb
}
@@ -151,6 +152,7 @@ module Rails
"#{test}:controller",
"#{test}:helper",
"#{test}:integration",
+ "#{test}:system",
"#{test}:mailer",
"#{test}:model",
"#{test}:scaffold",
@@ -218,7 +220,7 @@ module Rails
[[ "rails", rails ]] + groups.sort.to_a
end
- # Rails finds namespaces similar to thor, it only adds one rule:
+ # Rails finds namespaces similar to Thor, it only adds one rule:
#
# Generators names must end with "_generator.rb". This is required because Rails
# looks in load paths and loads the generator just before it's going to be used.
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index d058d82cea..0bd0615b7e 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -269,7 +269,7 @@ module Rails
if args.size == 1
say args.first.to_s unless options.quiet?
else
- args << (self.behavior == :invoke ? :green : :red)
+ args << (behavior == :invoke ? :green : :red)
say_status(*args)
end
end
diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb
index f677e545e5..d06609e91e 100644
--- a/railties/lib/rails/generators/actions/create_migration.rb
+++ b/railties/lib/rails/generators/actions/create_migration.rb
@@ -54,8 +54,8 @@ module Rails
say_status :skip, :yellow
else
say_status :conflict, :red
- raise Error, "Another migration is already named #{migration_file_name}: " +
- "#{existing_migration}. Use --force to replace this migration " +
+ raise Error, "Another migration is already named #{migration_file_name}: " \
+ "#{existing_migration}. Use --force to replace this migration " \
"or --skip to ignore conflicted file."
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index ef698bd4ac..04f6341471 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -82,6 +82,9 @@ module Rails
class_option :skip_test, type: :boolean, aliases: "-T", default: false,
desc: "Skip test files"
+ class_option :skip_system_test, type: :boolean, default: false,
+ desc: "Skip system test files"
+
class_option :dev, type: :boolean, default: false,
desc: "Setup the #{name} with Gemfile pointing to your Rails checkout"
@@ -190,7 +193,7 @@ module Rails
def webserver_gemfile_entry # :doc:
return [] if options[:skip_puma]
comment = "Use Puma as the app server"
- GemfileEntry.new("puma", "~> 3.0", comment)
+ GemfileEntry.new("puma", "~> 3.7", comment)
end
def include_all_railties? # :doc:
@@ -243,7 +246,6 @@ module Rails
def rails_gemfile_entry
dev_edge_common = [
- GemfileEntry.github("arel", "rails/arel")
]
if options.dev?
[
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index cc6ae3860f..a650c52626 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -256,8 +256,8 @@ module Rails
last = extract_last_module(nesting)
if last && last.const_defined?(last_name.camelize, false)
- raise Error, "The name '#{class_name}' is either already used in your application " <<
- "or reserved by Ruby on Rails. Please choose an alternative and run " <<
+ raise Error, "The name '#{class_name}' is either already used in your application " \
+ "or reserved by Ruby on Rails. Please choose an alternative and run " \
"this generator again."
end
end
@@ -331,7 +331,7 @@ module Rails
def self.prepare_for_invocation(name, value) #:nodoc:
return super unless value.is_a?(String) || value.is_a?(Symbol)
- if value && constants = self.hooks[name]
+ if value && constants = hooks[name]
value = name if TrueClass === value
Rails::Generators.find_by_namespace(value, *constants)
elsif klass = Rails::Generators.find_by_namespace(value)
diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
index 677f8041ae..3f1d9932f6 100644
--- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
+++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb
@@ -9,10 +9,10 @@ module Erb # :nodoc:
view_base_path = File.join("app/views", class_path, file_name + "_mailer")
empty_directory view_base_path
- if self.behavior == :invoke
+ if behavior == :invoke
formats.each do |format|
layout_path = File.join("app/views/layouts", class_path, filename_with_extensions("mailer", format))
- template filename_with_extensions(:layout, format), layout_path
+ template filename_with_extensions(:layout, format), layout_path unless File.exist?(layout_path)
end
end
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index 0d63b9a5c9..82481169c3 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -35,7 +35,7 @@ module Rails
end
def set_migration_assigns!(destination)
- destination = File.expand_path(destination, self.destination_root)
+ destination = File.expand_path(destination, destination_root)
migration_dir = File.dirname(destination)
@migration_number = self.class.next_migration_number(migration_dir)
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index e3660b012a..02557b098a 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -82,6 +82,10 @@ module Rails
!options[:skip_namespace] && namespace
end
+ def namespace_dirs
+ @namespace_dirs ||= namespace.name.split("::").map(&:underscore)
+ end
+
def file_path # :doc:
@file_path ||= (class_path + [file_name]).join("/")
end
@@ -95,11 +99,11 @@ module Rails
end
def namespaced_class_path # :doc:
- @namespaced_class_path ||= [namespaced_path] + @class_path
+ @namespaced_class_path ||= namespace_dirs + @class_path
end
def namespaced_path # :doc:
- @namespaced_path ||= namespace.name.split("::").first.underscore
+ @namespaced_path ||= namespace_dirs.join("/")
end
def class_name # :doc:
@@ -219,7 +223,7 @@ module Rails
#
def self.check_class_collision(options = {}) # :doc:
define_method :check_class_collision do
- name = if self.respond_to?(:controller_class_name) # for ScaffoldBase
+ name = if respond_to?(:controller_class_name) # for ScaffoldBase
controller_class_name
else
class_name
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 8efdfdcb44..86326d98ed 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -53,6 +53,12 @@ module Rails
template "gitignore", ".gitignore"
end
+ def version_control
+ if !options[:skip_git] && !options[:pretend]
+ run "git init"
+ end
+ end
+
def app
directory "app"
@@ -144,6 +150,12 @@ module Rails
template "test/test_helper.rb"
end
+ def system_test
+ empty_directory_with_keep_file "test/system"
+
+ template "test/application_system_test_case.rb"
+ end
+
def tmp
empty_directory_with_keep_file "tmp"
empty_directory "tmp/cache"
@@ -154,7 +166,7 @@ module Rails
empty_directory_with_keep_file "vendor"
unless options[:skip_yarn]
- template "package.json", "vendor/package.json"
+ template "package.json"
end
end
end
@@ -181,10 +193,6 @@ module Rails
def initialize(*args)
super
- unless app_path
- raise Error, "Application name should be provided in arguments. For details run: rails --help"
- end
-
if !options[:skip_active_record] && !DATABASES.include?(options[:database])
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
@@ -205,6 +213,7 @@ module Rails
build(:configru)
build(:gitignore) unless options[:skip_git]
build(:gemfile) unless options[:skip_gemfile]
+ build(:version_control)
end
def create_app_files
@@ -259,6 +268,10 @@ module Rails
build(:test) unless options[:skip_test]
end
+ def create_system_test_files
+ build(:system_test) unless options[:skip_system_test] || options[:skip_test] || options[:api]
+ end
+
def create_tmp_files
build(:tmp)
end
@@ -267,7 +280,7 @@ module Rails
build(:vendor)
if options[:skip_yarn]
- remove_file "vendor/package.json"
+ remove_file "package.json"
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index f1015b16d5..b082d70cba 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -31,7 +31,12 @@ end
<% if RUBY_ENGINE == 'ruby' -%>
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
- gem 'byebug', platform: :mri
+ gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
+ <%- unless options.skip_system_test? || options.api? -%>
+ # Adds support for Capybara system testing and selenium driver
+ gem 'capybara', '~> 2.7.0'
+ gem 'selenium-webdriver'
+ <%- end -%>
end
group :development do
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn b/railties/lib/rails/generators/rails/app/templates/bin/yarn
index 872438cecb..4ae896a8d3 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/yarn
+++ b/railties/lib/rails/generators/rails/app/templates/bin/yarn
@@ -1,4 +1,4 @@
-VENDOR_PATH = File.expand_path('../vendor', __dir__)
+VENDOR_PATH = File.expand_path('..', __dir__)
Dir.chdir(VENDOR_PATH) do
begin
exec "yarnpkg #{ARGV.join(" ")}"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml
index 0bbde6f74f..1da4913082 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/cable.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml
@@ -7,3 +7,4 @@ test:
production:
adapter: redis
url: redis://localhost:6379/1
+ channel_prefix: <%= app_name %>_production
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
index c223d6bc62..a21555e573 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
@@ -1,4 +1,4 @@
-# SQL Server (2005 or higher recommended)
+# SQL Server (2012 or higher recommended)
#
# Install the adapters and driver
# gem install tiny_tds
@@ -8,29 +8,12 @@
# gem 'tiny_tds'
# gem 'activerecord-sqlserver-adapter'
#
-# You should make sure freetds is configured correctly first.
-# freetds.conf contains host/port/protocol_versions settings.
-# http://freetds.schemamania.org/userguide/freetdsconf.htm
-#
-# A typical Microsoft server
-# [mssql]
-# host = mssqlserver.yourdomain.com
-# port = 1433
-# tds version = 7.1
-
-# If you can connect with "tsql -S servername", your basic FreeTDS installation is working.
-# 'man tsql' for more info
-# Set timeout to a larger number if valid queries against a live db fail
-#
default: &default
adapter: sqlserver
encoding: utf8
- pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
- reconnect: false
- username: <%= app_name %>
- password:
- timeout: 25
- dataserver: from_freetds.conf
+ username: sa
+ password: <%= ENV['SA_PASSWORD'] %>
+ host: localhost
development:
<<: *default
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 7deab5dbb1..4a39e43e57 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
@@ -88,7 +88,7 @@ Rails.application.configure do
if ENV["RAILS_LOG_TO_STDOUT"].present?
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter
- config.logger = ActiveSupport::TaggedLogging.new(logger)
+ config.logger = ActiveSupport::TaggedLogging.new(logger)
end
<%- unless options.skip_active_record? -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
index f5d03fb117..51196ae743 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
@@ -7,7 +7,7 @@ Rails.application.config.assets.version = '1.0'
# Rails.application.config.assets.paths << Emoji.images_path
<%- unless options[:skip_yarn] -%>
# Add Yarn node_modules folder to the asset load path.
-Rails.application.config.assets.paths << Rails.root.join('vendor/node_modules')
+Rails.application.config.assets.paths << Rails.root.join('node_modules')
<%- end -%>
# Precompile additional assets.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt
index 3ad3eba98a..bd844f0503 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt
@@ -24,9 +24,6 @@ ActiveSupport.to_time_preserves_timezone = <%= options[:update] ? false : true %
# Require `belongs_to` associations by default. Previous versions had false.
Rails.application.config.active_record.belongs_to_required_by_default = <%= options[:update] ? false : true %>
<%- end -%>
-
-# Do not halt callback chains when a callback returns false. Previous versions had true.
-ActiveSupport.halt_callback_chains_on_return_false = <%= options[:update] ? true : false %>
<%- unless options[:update] -%>
# Configure SSL options to enable HSTS with subdomains. Previous versions had false.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml
index 0653957166..decc5a8573 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml
@@ -16,6 +16,16 @@
#
# This would use the information in config/locales/es.yml.
#
+# The following keys must be escaped otherwise they will not be retrieved by
+# the default I18n backend:
+#
+# true, false, on, off, yes, no
+#
+# Instead, surround them with single quotes.
+#
+# en:
+# 'true': 'foo'
+#
# To learn more, please read the Rails Internationalization guide
# available at http://guides.rubyonrails.org/i18n.html.
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index 709b341387..7221c26729 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -22,7 +22,8 @@
<% end -%>
<% unless options[:skip_yarn] -%>
-/vendor/node_modules
+/node_modules
+/yarn-error.log
<% end -%>
.byebug_history
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
new file mode 100644
index 0000000000..d19212abd5
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/test/application_system_test_case.rb
@@ -0,0 +1,5 @@
+require "test_helper"
+
+class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
+end
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 4cf4f8cd9a..be211e016d 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -91,6 +91,7 @@ task default: :test
opts[:skip_bundle] = true
opts[:api] = options.api?
opts[:skip_listen] = true
+ opts[:skip_git] = true
invoke Rails::Generators::AppGenerator,
[ File.expand_path(dummy_path, destination_root) ], opts
@@ -112,7 +113,6 @@ task default: :test
def test_dummy_clean
inside dummy_path do
- remove_file ".gitignore"
remove_file "db/seeds.rb"
remove_file "doc"
remove_file "Gemfile"
@@ -186,7 +186,7 @@ task default: :test
desc: "Skip gemspec file"
class_option :skip_gemfile_entry, type: :boolean, default: false,
- desc: "If creating plugin in application's directory " +
+ desc: "If creating plugin in application's directory " \
"skip adding entry to Gemfile"
class_option :api, type: :boolean, default: false,
@@ -195,10 +195,6 @@ task default: :test
def initialize(*args)
@dummy_path = nil
super
-
- unless plugin_path
- raise Error, "Plugin name should be provided in arguments. For details run: rails plugin new --help"
- end
end
public_task :set_default_accessors!
@@ -415,7 +411,6 @@ task default: :test
require 'rake/testtask'
Rake::TestTask.new(:test) do |t|
- t.libs << 'lib'
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = false
diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
index 56e7925c6b..c03d9953d4 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt
@@ -3,6 +3,7 @@
ENGINE_ROOT = File.expand_path('../..', __FILE__)
ENGINE_PATH = File.expand_path('../../lib/<%= namespaced_name -%>/engine', __FILE__)
+APP_PATH = File.expand_path('../../<%= dummy_path -%>/config/application', __FILE__)
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
index c0fbb84a93..35a9bf8c8b 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/bin/test.tt
@@ -5,6 +5,6 @@ require 'rails/test_unit/minitest_plugin'
Rails::TestUnitReporter.executable = 'bin/test'
-Minitest.run_via[:rails] = true
+Minitest.run_via = :rails
require "active_support/testing/autorun"
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
new file mode 100644
index 0000000000..d19212abd5
--- /dev/null
+++ b/railties/lib/rails/generators/rails/plugin/templates/test/application_system_test_case.rb
@@ -0,0 +1,5 @@
+require "test_helper"
+
+class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
+end
diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
index ed6bf7f7d7..12d6bc85b2 100644
--- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb
@@ -6,6 +6,7 @@ module Rails
remove_hook_for :resource_controller
remove_class_option :actions
+ class_option :api, type: :boolean
class_option :stylesheets, type: :boolean, desc: "Generate Stylesheets"
class_option :stylesheet_engine, desc: "Engine for Stylesheets"
class_option :assets, type: :boolean
@@ -15,10 +16,13 @@ module Rails
def handle_skip
@options = @options.merge(stylesheets: false) unless options[:assets]
@options = @options.merge(stylesheet_engine: false) unless options[:stylesheets] && options[:scaffold_stylesheet]
+ @options = @options.merge(system_tests: false) if options[:api]
end
hook_for :scaffold_controller, required: true
+ hook_for :system_tests, as: :system
+
hook_for :assets do |assets|
invoke assets, [controller_name]
end
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index e4f3161ffd..cf97c22160 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -20,7 +20,11 @@ module Rails
template template_file, File.join("app/controllers", controller_class_path, "#{controller_file_name}_controller.rb")
end
- hook_for :template_engine, :test_framework, as: :scaffold
+ hook_for :template_engine, as: :scaffold do |template_engine|
+ invoke template_engine unless options.api?
+ end
+
+ hook_for :test_framework, as: :scaffold
# Invoke the helper using the controller name (pluralized)
hook_for :helper, as: :scaffold do |invoked|
diff --git a/railties/lib/rails/generators/rails/system_test/USAGE b/railties/lib/rails/generators/rails/system_test/USAGE
new file mode 100644
index 0000000000..f11a99e008
--- /dev/null
+++ b/railties/lib/rails/generators/rails/system_test/USAGE
@@ -0,0 +1,10 @@
+Description:
+ Stubs out a new system test. Pass the name of the test, either
+ CamelCased or under_scored, as an argument.
+
+ This generator invokes the current system tool, which defaults to
+ TestUnit.
+
+Example:
+ `rails generate system_test GeneralStories` creates a GeneralStories
+ system test in test/system/general_stories_test.rb
diff --git a/railties/lib/rails/generators/rails/system_test/system_test_generator.rb b/railties/lib/rails/generators/rails/system_test/system_test_generator.rb
new file mode 100644
index 0000000000..901120e892
--- /dev/null
+++ b/railties/lib/rails/generators/rails/system_test/system_test_generator.rb
@@ -0,0 +1,7 @@
+module Rails
+ module Generators
+ class SystemTestGenerator < NamedBase # :nodoc:
+ hook_for :system_tests, as: :system
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index 978b053308..e7cb722473 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -59,7 +59,7 @@ module Rails
end
# Loads the ORM::Generators::ActiveModel class. This class is responsible
- # to tell scaffold entities how to generate an specific method for the
+ # to tell scaffold entities how to generate a specific method for the
# ORM. Check Rails::Generators::ActiveModel for more information.
def orm_class
@orm_class ||= begin
diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb
index dea7e22196..118e0f1271 100644
--- a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb
+++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb
@@ -1,7 +1,9 @@
require 'test_helper'
+<% module_namespacing do -%>
class <%= class_name %>Test < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end
+<% end -%>
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 8840a86d0d..292db35121 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -22,7 +22,7 @@ module TestUnit # :nodoc:
def fixture_name
@fixture_name ||=
if mountable_engine?
- "%s_%s" % [namespaced_path, table_name]
+ (namespace_dirs + [table_name]).join("_")
else
table_name
end
diff --git a/railties/lib/rails/generators/test_unit/system/system_generator.rb b/railties/lib/rails/generators/test_unit/system/system_generator.rb
new file mode 100644
index 0000000000..aec415a4e5
--- /dev/null
+++ b/railties/lib/rails/generators/test_unit/system/system_generator.rb
@@ -0,0 +1,17 @@
+require "rails/generators/test_unit"
+
+module TestUnit # :nodoc:
+ module Generators # :nodoc:
+ class SystemGenerator < Base # :nodoc:
+ check_class_collision suffix: "Test"
+
+ def create_test_files
+ if !File.exist?(File.join("test/application_system_test_case.rb"))
+ template "application_system_test_case.rb", File.join("test", "application_system_test_case.rb")
+ end
+
+ template "system_test.rb", File.join("test/system", "#{file_name.pluralize}_test.rb")
+ end
+ end
+ end
+end
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
new file mode 100644
index 0000000000..d19212abd5
--- /dev/null
+++ b/railties/lib/rails/generators/test_unit/system/templates/application_system_test_case.rb
@@ -0,0 +1,5 @@
+require "test_helper"
+
+class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
+ driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
+end
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
new file mode 100644
index 0000000000..b5ce2ba5c8
--- /dev/null
+++ b/railties/lib/rails/generators/test_unit/system/templates/system_test.rb
@@ -0,0 +1,9 @@
+require "application_system_test_case"
+
+class <%= class_name.pluralize %>Test < ApplicationSystemTestCase
+ # test "visiting the index" do
+ # visit <%= plural_table_name %>_url
+ #
+ # assert_selector "h1", text: "<%= class_name %>"
+ # end
+end
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index 5d4acd6f6b..fc064dac32 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -1,9 +1,9 @@
require "cgi"
module Rails
- # This module helps build the runtime properties used to display in the
- # Rails::InfoController responses. Including the active Rails version, Ruby
- # version, Rack version, and so on.
+ # This module helps build the runtime properties that are displayed in
+ # Rails::InfoController responses. These include the active Rails version,
+ # Ruby version, Rack version, and so on.
module Info
mattr_accessor :properties
class << (@@properties = [])
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 10925de8b2..af3be10a31 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -2,12 +2,12 @@ module Rails
module Paths
# This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system.
# It allows you to collect information about how you want to structure your application
- # paths by a Hash like API. It requires you to give a physical path on initialization.
+ # paths through a Hash-like API. It requires you to give a physical path on initialization.
#
# root = Root.new "/rails"
# root.add "app/controllers", eager_load: true
#
- # The command above creates a new root object and adds "app/controllers" as a path.
+ # The above command creates a new root object and adds "app/controllers" as a path.
# This means we can get a <tt>Rails::Paths::Path</tt> object back like below:
#
# path = root["app/controllers"]
diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb
deleted file mode 100644
index dccfa3e9bb..0000000000
--- a/railties/lib/rails/rack/debugger.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-require "active_support/deprecation"
-
-ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.")
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 3a48c4c496..e9088c44ce 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -13,7 +13,7 @@
# start with the tag optionally followed by a colon. Everything up to the end
# of the line (or closing ERB comment tag) is considered to be their text.
class SourceAnnotationExtractor
- class Annotation < Struct.new(:line, :tag, :text)
+ Annotation = Struct.new(:line, :tag, :text) do
def self.directories
@@directories ||= %w(app config db lib test) + (ENV["SOURCE_ANNOTATION_DIRECTORIES"] || "").split(",")
end
diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb
index 48675b3845..c0a50e5bda 100644
--- a/railties/lib/rails/tasks.rb
+++ b/railties/lib/rails/tasks.rb
@@ -12,6 +12,7 @@ require "rake"
restart
routes
tmp
+ yarn
).tap { |arr|
arr << "statistics" if Rake.application.current_scope.empty?
}.each do |task|
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index a2167796eb..f5586b53f0 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -1,5 +1,3 @@
-require "active_support/deprecation"
-
namespace :app do
desc "Update configs and some other initially generated files (or use just update:configs or update:bin)"
task update: [ "update:configs", "update:bin", "update:upgrade_guide_info" ]
@@ -73,15 +71,3 @@ namespace :app do
end
end
end
-
-namespace :rails do
- %i(update template templates:copy update:configs update:bin).each do |task_name|
- task "#{task_name}" do
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Running #{task_name} with the rails: namespace is deprecated in favor of app: namespace.
- Run bin/rails app:#{task_name} instead.
- MSG
- Rake.application.invoke_task("app:#{task_name}")
- end
- end
-end
diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake
index f5e5b9ae87..215fb2ceb5 100644
--- a/railties/lib/rails/tasks/routes.rake
+++ b/railties/lib/rails/tasks/routes.rake
@@ -1,5 +1,3 @@
-require "active_support/deprecation"
-require "active_support/core_ext/string/strip" # for strip_heredoc
require "optparse"
desc "Print out all defined routes in match order, with names. Target specific controller with -c option, or grep routes using -g option"
@@ -7,15 +5,8 @@ task routes: :environment do
all_routes = Rails.application.routes.routes
require "action_dispatch/routing/inspector"
inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
- if ARGV.any? { |argv| argv.start_with? "CONTROLLER" }
- puts <<-eow.strip_heredoc
- Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1.
- Please use `bin/rails routes -c controller_name` instead.
- eow
- end
routes_filter = nil
- routes_filter = { controller: ENV["CONTROLLER"] } if ENV["CONTROLLER"]
OptionParser.new do |opts|
opts.banner = "Usage: rails routes [options]"
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index ba1697186e..cb569be58b 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -17,6 +17,7 @@ STATS_DIRECTORIES = [
%w(Mailer\ tests test/mailers),
%w(Job\ tests test/jobs),
%w(Integration\ tests test/integration),
+ %w(System\ tests test/system),
].collect do |name, dir|
[ name, "#{File.dirname(Rake.application.rakefile_location)}/#{dir}" ]
end.select { |name, dir| File.directory?(dir) }
diff --git a/railties/lib/rails/tasks/yarn.rake b/railties/lib/rails/tasks/yarn.rake
new file mode 100644
index 0000000000..87476b1b8c
--- /dev/null
+++ b/railties/lib/rails/tasks/yarn.rake
@@ -0,0 +1,11 @@
+namespace :yarn do
+ desc "Install all JavaScript dependencies as specified via Yarn"
+ task :install do
+ system("./bin/yarn install --no-progress")
+ end
+end
+
+# Run Yarn prior to Sprockets assets precompilation, so dependencies are available for use.
+if Rake::Task.task_defined?("assets:precompile")
+ Rake::Task["assets:precompile"].enhance [ "yarn:install" ]
+end
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index db341dd847..09931c108a 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -11,13 +11,19 @@ require "rails/generators/test_case"
require "active_support/testing/autorun"
+if defined?(Capbyara)
+ require "action_dispatch/system_test_case"
+end
+
if defined?(ActiveRecord::Base)
ActiveRecord::Migration.maintain_test_schema!
- class ActiveSupport::TestCase
- include ActiveRecord::TestFixtures
- self.fixture_path = "#{Rails.root}/test/fixtures/"
- self.file_fixture_path = self.fixture_path + "files"
+ module ActiveSupport
+ class TestCase
+ include ActiveRecord::TestFixtures
+ self.fixture_path = "#{Rails.root}/test/fixtures/"
+ self.file_fixture_path = fixture_path + "files"
+ end
end
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
@@ -27,6 +33,8 @@ if defined?(ActiveRecord::Base)
end
end
+# :enddoc:
+
class ActionController::TestCase
def before_setup # :nodoc:
@routes = Rails.application.routes
@@ -40,3 +48,12 @@ class ActionDispatch::IntegrationTest
super
end
end
+
+if defined? Capybara
+ class ActionDispatch::SystemTestCase
+ def before_setup # :nodoc:
+ @routes = Rails.application.routes
+ super
+ end
+ end
+end
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index 6e196a32ab..e44fe78bbd 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -52,21 +52,25 @@ module Minitest
options[:color] = value
end
+ opts.on("-w", "--warnings",
+ "Enable ruby warnings") do
+ $VERBOSE = true
+ end
+
options[:color] = true
options[:output_inline] = true
- options[:patterns] = defined?(@rake_patterns) ? @rake_patterns : opts.order!
+ options[:patterns] = opts.order! unless run_via.rake?
end
- # Running several Rake tasks in a single command would trip up the runner,
- # as the patterns would also contain the other Rake tasks.
def self.rake_run(patterns) # :nodoc:
- @rake_patterns = patterns
+ self.run_via = :rake unless run_via.set?
+ ::Rails::TestRequirer.require_files(patterns)
autorun
end
module RunRespectingRakeTestopts
def run(args = [])
- if defined?(@rake_patterns)
+ if run_via.rake?
args = Shellwords.split(ENV["TESTOPTS"] || "")
end
@@ -81,8 +85,9 @@ module Minitest
def self.plugin_rails_init(options)
ENV["RAILS_ENV"] = options[:environment] || "test"
- # If run via `ruby` we've been passed the files to run directly.
- unless run_via[:ruby]
+ # If run via `ruby` we've been passed the files to run directly, or if run
+ # via `rake` then they have already been eagerly required.
+ unless run_via.ruby? || run_via.rake?
::Rails::TestRequirer.require_files(options[:patterns])
end
@@ -97,7 +102,33 @@ module Minitest
reporter << ::Rails::TestUnitReporter.new(options[:io], options)
end
- mattr_accessor(:run_via) { Hash.new }
+ def self.run_via=(runner)
+ if run_via.set?
+ raise ArgumentError, "run_via already assigned"
+ else
+ run_via.runner = runner
+ end
+ end
+
+ class RunVia
+ attr_accessor :runner
+ alias set? runner
+
+ # Backwardscompatibility with Rails 5.0 generated plugin test scripts.
+ def []=(runner, *)
+ @runner = runner
+ end
+
+ def ruby?
+ runner == :ruby
+ end
+
+ def rake?
+ runner == :rake
+ end
+ end
+
+ mattr_reader(:run_via) { RunVia.new }
end
# Put Rails as the first plugin minitest initializes so other plugins
diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb
index 746120e6a1..9cc3f73a9c 100644
--- a/railties/lib/rails/test_unit/railtie.rb
+++ b/railties/lib/rails/test_unit/railtie.rb
@@ -11,6 +11,7 @@ module Rails
fixture_replacement: nil
c.integration_tool :test_unit
+ c.system_tests :test_unit
end
initializer "test_unit.line_filtering" do
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 4c157c1262..4dde3d3c97 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -47,4 +47,9 @@ namespace :test do
$: << "test"
Minitest.rake_run(["test/controllers", "test/mailers", "test/functional"])
end
+
+ task system: "test:prepare" do
+ $: << "test"
+ Minitest.rake_run(["test/system"])
+ end
end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index fd1e1b9662..e4b2d0457d 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -17,15 +17,15 @@ module TestApp
end
end
-# Skips the current run on Rubinius using Minitest::Assertions#skip
-def rubinius_skip(message = "")
- skip message if RUBY_ENGINE == "rbx"
-end
-# Skips the current run on JRuby using Minitest::Assertions#skip
-def jruby_skip(message = "")
- skip message if defined?(JRUBY_VERSION)
-end
-
class ActiveSupport::TestCase
include ActiveSupport::Testing::Stream
+
+ # Skips the current run on Rubinius using Minitest::Assertions#skip
+ private def rubinius_skip(message = "")
+ skip message if RUBY_ENGINE == "rbx"
+ end
+ # Skips the current run on JRuby using Minitest::Assertions#skip
+ private def jruby_skip(message = "")
+ skip message if defined?(JRUBY_VERSION)
+ end
end
diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb
index f62313f3e1..0fb995900f 100644
--- a/railties/test/application/bin_setup_test.rb
+++ b/railties/test/application/bin_setup_test.rb
@@ -38,9 +38,12 @@ module ApplicationTests
app_file "db/schema.rb", ""
output = `bin/setup 2>&1`
+
+ # Ignore line that's only output by Bundler < 1.14
+ output.sub!(/^Resolving dependencies\.\.\.\n/, "")
+
assert_equal(<<-OUTPUT, output)
== Installing dependencies ==
-Resolving dependencies...
The Gemfile's dependencies are satisfied
== Preparing database ==
diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb
index 3c675eec71..8360b7bf4b 100644
--- a/railties/test/application/configuration/custom_test.rb
+++ b/railties/test/application/configuration/custom_test.rb
@@ -10,7 +10,6 @@ module ApplicationTests
def teardown
teardown_app
- FileUtils.rm_rf(new_app) if File.directory?(new_app)
end
test "access custom configuration point" do
@@ -30,28 +29,14 @@ module ApplicationTests
assert_equal false, x.hyper_debugger
assert_nil x.nil_debugger
assert_nil x.i_do_not_exist.zomg
- end
- test "custom configuration responds to all messages" do
- x = Rails.configuration.x
+ # test that custom configuration responds to all messages
assert_equal true, x.respond_to?(: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
private
- def new_app
- File.expand_path("#{app_path}/../new_app")
- end
-
- def copy_app
- FileUtils.cp_r(app_path, new_app)
- end
-
- def app
- @app ||= Rails.application
- end
-
def require_environment
require "#{app_path}/config/environment"
end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 31c3b5cf52..14433fbba0 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -51,7 +51,7 @@ module ApplicationTests
def setup
build_app
- supress_default_config
+ suppress_default_config
end
def teardown
@@ -59,7 +59,7 @@ module ApplicationTests
FileUtils.rm_rf(new_app) if File.directory?(new_app)
end
- def supress_default_config
+ def suppress_default_config
FileUtils.mv("#{app_path}/config/environments", "#{app_path}/config/__environments__")
end
@@ -78,6 +78,18 @@ module ApplicationTests
end
end
+ test "Rails.env falls back to development if RAILS_ENV is blank and RACK_ENV is nil" do
+ with_rails_env("") do
+ assert_equal "development", Rails.env
+ end
+ end
+
+ test "Rails.env falls back to development if RACK_ENV is blank and RAILS_ENV is nil" do
+ with_rack_env("") do
+ assert_equal "development", Rails.env
+ end
+ end
+
test "By default logs tags are not set in development" do
restore_default_config
@@ -369,26 +381,6 @@ module ApplicationTests
end
end
- test "config.serve_static_files is deprecated" do
- make_basic_app do |application|
- assert_deprecated do
- application.config.serve_static_files = true
- end
-
- assert application.config.public_file_server.enabled
- end
- end
-
- test "config.static_cache_control is deprecated" do
- make_basic_app do |application|
- assert_deprecated do
- application.config.static_cache_control = "public, max-age=60"
- end
-
- assert_equal application.config.static_cache_control, "public, max-age=60"
- end
- end
-
test "Use key_generator when secret_key_base is set" do
make_basic_app do |application|
application.secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33"
@@ -631,9 +623,10 @@ module ApplicationTests
assert_equal "", app.config.secret_token
assert_nil app.secrets.secret_key_base
- assert_raise ArgumentError, /\AA secret is required/ do
+ e = assert_raise ArgumentError do
app.key_generator
end
+ assert_match(/\AA secret is required/, e.message)
end
test "that nested keys are symbolized the same as parents for hashes more than one level deep" do
@@ -992,7 +985,7 @@ module ApplicationTests
class ::OmgController < ActionController::Base
def index
- render plain: env["action_dispatch.show_exceptions"]
+ render plain: request.env["action_dispatch.show_exceptions"]
end
end
@@ -1192,11 +1185,12 @@ module ApplicationTests
end
test "config.session_store with :active_record_store without activerecord-session_store gem" do
- assert_raise RuntimeError, /activerecord-session_store/ do
+ e = assert_raise RuntimeError do
make_basic_app do |application|
application.config.session_store :active_record_store
end
end
+ assert_match(/activerecord-session_store/, e.message)
end
test "default session store initializer does not overwrite the user defined session store even if it is disabled" do
diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb
index 790aca2aa4..c3a360e5d4 100644
--- a/railties/test/application/mailer_previews_test.rb
+++ b/railties/test/application/mailer_previews_test.rb
@@ -671,6 +671,40 @@ module ApplicationTests
assert_match %r[<p>Hello, World!</p>], last_response.body
end
+ test "multipart mailer preview with empty parts" do
+ mailer "notifier", <<-RUBY
+ class Notifier < ActionMailer::Base
+ default from: "from@example.com"
+
+ def foo
+ mail to: "to@example.org"
+ end
+ end
+ RUBY
+
+ text_template "notifier/foo", <<-RUBY
+ RUBY
+
+ html_template "notifier/foo", <<-RUBY
+ RUBY
+
+ mailer_preview "notifier", <<-RUBY
+ class NotifierPreview < ActionMailer::Preview
+ def foo
+ Notifier.foo
+ end
+ end
+ RUBY
+
+ app("development")
+
+ get "/rails/mailers/notifier/foo?part=text/plain"
+ assert_equal 200, last_response.status
+
+ get "/rails/mailers/notifier/foo?part=text/html"
+ assert_equal 200, last_response.status
+ end
+
private
def build_app
super
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 51db634b75..c63f23fa0a 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -1,5 +1,4 @@
require "isolation/abstract_unit"
-require "active_support/core_ext/string/strip"
module ApplicationTests
module RakeTests
@@ -175,7 +174,7 @@ module ApplicationTests
`bin/rails generate model book title:string;
bin/rails db:migrate db:structure:dump`
structure_dump = File.read("db/structure.sql")
- assert_match(/CREATE TABLE \"books\"/, structure_dump)
+ assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"books\"/, structure_dump)
`bin/rails environment db:drop db:structure:load`
assert_match expected_database, ActiveRecord::Base.connection_config[:database]
require "#{app_path}/app/models/book"
@@ -203,7 +202,7 @@ module ApplicationTests
stderr_output = capture(:stderr) { `bin/rails db:structure:dump` }
assert_empty stderr_output
structure_dump = File.read("db/structure.sql")
- assert_match(/CREATE TABLE \"posts\"/, structure_dump)
+ assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"posts\"/, structure_dump)
end
end
diff --git a/railties/test/application/rake/framework_test.rb b/railties/test/application/rake/framework_test.rb
index 7ac37b7700..40488a6aab 100644
--- a/railties/test/application/rake/framework_test.rb
+++ b/railties/test/application/rake/framework_test.rb
@@ -1,5 +1,4 @@
require "isolation/abstract_unit"
-require "active_support/core_ext/string/strip"
module ApplicationTests
module RakeTests
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index d80a45a83f..1b64a0a1ca 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -132,48 +132,6 @@ module ApplicationTests
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
- def test_rails_routes_with_controller_environment
- app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- get '/cart', to: 'cart#show'
- get '/basketball', to: 'basketball#index'
- end
- RUBY
-
- output = Dir.chdir(app_path) { `bin/rails routes CONTROLLER=cart` }
- assert_equal ["Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1.",
- "Please use `bin/rails routes -c controller_name` instead.",
- "Prefix Verb URI Pattern Controller#Action",
- " cart GET /cart(.:format) cart#show\n"].join("\n"), output
-
- output = Dir.chdir(app_path) { `bin/rails routes -c cart` }
- assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
- end
-
- def test_rails_routes_with_namespaced_controller_environment
- app_file "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- namespace :admin do
- resource :post
- end
- end
- RUBY
- expected_output = [" Prefix Verb URI Pattern Controller#Action",
- " new_admin_post GET /admin/post/new(.:format) admin/posts#new",
- "edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit",
- " admin_post GET /admin/post(.:format) admin/posts#show",
- " PATCH /admin/post(.:format) admin/posts#update",
- " PUT /admin/post(.:format) admin/posts#update",
- " DELETE /admin/post(.:format) admin/posts#destroy",
- " POST /admin/post(.:format) admin/posts#create\n"].join("\n")
-
- output = Dir.chdir(app_path) { `bin/rails routes -c Admin::PostController` }
- assert_equal expected_output, output
-
- output = Dir.chdir(app_path) { `bin/rails routes -c PostController` }
- assert_equal expected_output, output
- end
-
def test_singular_resource_output_in_rake_routes
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
@@ -232,6 +190,30 @@ module ApplicationTests
assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output
end
+ def test_rails_routes_with_namespaced_controller_search_key
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ namespace :admin do
+ resource :post
+ end
+ end
+ RUBY
+ expected_output = [" Prefix Verb URI Pattern Controller#Action",
+ " new_admin_post GET /admin/post/new(.:format) admin/posts#new",
+ "edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit",
+ " admin_post GET /admin/post(.:format) admin/posts#show",
+ " PATCH /admin/post(.:format) admin/posts#update",
+ " PUT /admin/post(.:format) admin/posts#update",
+ " DELETE /admin/post(.:format) admin/posts#destroy",
+ " POST /admin/post(.:format) admin/posts#create\n"].join("\n")
+
+ output = Dir.chdir(app_path) { `bin/rails routes -c Admin::PostController` }
+ assert_equal expected_output, output
+
+ output = Dir.chdir(app_path) { `bin/rails routes -c PostController` }
+ assert_equal expected_output, output
+ end
+
def test_rails_routes_displays_message_when_no_routes_are_defined
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
@@ -341,16 +323,6 @@ module ApplicationTests
assert_no_match(/Errors running/, output)
end
- def test_db_test_clone_when_using_sql_format
- add_to_config "config.active_record.schema_format = :sql"
- output = Dir.chdir(app_path) do
- `bin/rails generate scaffold user username:string;
- bin/rails db:migrate;
- bin/rails db:test:clone 2>&1 --trace`
- end
- assert_match(/Execute db:test:clone_structure/, output)
- end
-
def test_db_test_prepare_when_using_sql_format
add_to_config "config.active_record.schema_format = :sql"
output = Dir.chdir(app_path) do
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index c515e2b270..6742da20cc 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -263,7 +263,10 @@ module ApplicationTests
assert_equal "WIN", last_response.body
end
- { "development" => "baz", "production" => "bar" }.each do |mode, expected|
+ {
+ "development" => ["baz", "http://www.apple.com", "/dashboard"],
+ "production" => ["bar", "http://www.microsoft.com", "/profile"]
+ }.each do |mode, (expected_action, expected_url, expected_mapping)|
test "reloads routes when configuration is changed in #{mode}" do
controller :foo, <<-RUBY
class FooController < ApplicationController
@@ -274,12 +277,40 @@ module ApplicationTests
def baz
render plain: "baz"
end
+
+ def custom
+ render plain: custom_url
+ end
+
+ def mapping
+ render plain: url_for(User.new)
+ end
+ end
+ RUBY
+
+ app_file "app/models/user.rb", <<-RUBY
+ class User
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ def model_name
+ @_model_name ||= ActiveModel::Name.new(self.class, nil, "User")
+ end
+
+ def persisted?
+ false
+ end
end
RUBY
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get 'foo', to: 'foo#bar'
+ get 'custom', to: 'foo#custom'
+ get 'mapping', to: 'foo#mapping'
+
+ direct(:custom) { "http://www.microsoft.com" }
+ resolve("User") { "/profile" }
end
RUBY
@@ -288,16 +319,33 @@ module ApplicationTests
get "/foo"
assert_equal "bar", last_response.body
+ get "/custom"
+ assert_equal "http://www.microsoft.com", last_response.body
+
+ get "/mapping"
+ assert_equal "/profile", last_response.body
+
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get 'foo', to: 'foo#baz'
+ get 'custom', to: 'foo#custom'
+ get 'mapping', to: 'foo#mapping'
+
+ direct(:custom) { "http://www.apple.com" }
+ resolve("User") { "/dashboard" }
end
RUBY
sleep 0.1
get "/foo"
- assert_equal expected, last_response.body
+ assert_equal expected_action, last_response.body
+
+ get "/custom"
+ assert_equal expected_url, last_response.body
+
+ get "/mapping"
+ assert_equal expected_mapping, last_response.body
end
end
@@ -358,6 +406,14 @@ module ApplicationTests
def index
render plain: "foo"
end
+
+ def custom
+ render plain: custom_url
+ end
+
+ def mapping
+ render plain: url_for(User.new)
+ end
end
RUBY
@@ -369,6 +425,21 @@ module ApplicationTests
end
RUBY
+ app_file "app/models/user.rb", <<-RUBY
+ class User
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ def model_name
+ @_model_name ||= ActiveModel::Name.new(self.class, nil, "User")
+ end
+
+ def persisted?
+ false
+ end
+ end
+ RUBY
+
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get 'foo', to: 'foo#index'
@@ -389,6 +460,12 @@ module ApplicationTests
Rails.application.routes.draw do
get 'foo', to: 'foo#index'
get 'bar', to: 'bar#index'
+
+ get 'custom', to: 'foo#custom'
+ direct(:custom) { 'http://www.apple.com' }
+
+ get 'mapping', to: 'foo#mapping'
+ resolve('User') { '/profile' }
end
RUBY
@@ -402,6 +479,14 @@ module ApplicationTests
assert_equal "bar", last_response.body
assert_equal "/bar", Rails.application.routes.url_helpers.bar_path
+ get "/custom"
+ assert_equal "http://www.apple.com", last_response.body
+ assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.custom_url
+
+ get "/mapping"
+ assert_equal "/profile", last_response.body
+ assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new)
+
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get 'foo', to: 'foo#index'
@@ -419,6 +504,18 @@ module ApplicationTests
assert_raises NoMethodError do
assert_equal "/bar", Rails.application.routes.url_helpers.bar_path
end
+
+ get "/custom"
+ assert_equal 404, last_response.status
+ assert_raises NoMethodError do
+ assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.custom_url
+ end
+
+ get "/mapping"
+ assert_equal 404, last_response.status
+ assert_raises NoMethodError do
+ assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new)
+ end
end
test "named routes are cleared when reloading" do
@@ -440,19 +537,41 @@ module ApplicationTests
end
RUBY
+ app_file "app/models/user.rb", <<-RUBY
+ class User
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ def model_name
+ @_model_name ||= ActiveModel::Name.new(self.class, nil, "User")
+ end
+
+ def persisted?
+ false
+ end
+ end
+ RUBY
+
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get ':locale/foo', to: 'foo#index', as: 'foo'
+ get 'users', to: 'foo#users', as: 'users'
+ direct(:microsoft) { 'http://www.microsoft.com' }
+ resolve('User') { '/profile' }
end
RUBY
get "/en/foo"
assert_equal "foo", last_response.body
assert_equal "/en/foo", Rails.application.routes.url_helpers.foo_path(locale: "en")
+ assert_equal "http://www.microsoft.com", Rails.application.routes.url_helpers.microsoft_url
+ assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new)
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get ':locale/bar', to: 'bar#index', as: 'foo'
+ get 'users', to: 'foo#users', as: 'users'
+ direct(:apple) { 'http://www.apple.com' }
end
RUBY
@@ -464,6 +583,12 @@ module ApplicationTests
get "/en/bar"
assert_equal "bar", last_response.body
assert_equal "/en/bar", Rails.application.routes.url_helpers.foo_path(locale: "en")
+ assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.apple_url
+ assert_equal "/users", Rails.application.routes.url_helpers.polymorphic_path(User.new)
+
+ assert_raises NoMethodError do
+ assert_equal "http://www.microsoft.com", Rails.application.routes.url_helpers.microsoft_url
+ end
end
test "resource routing with irregular inflection" do
@@ -493,5 +618,63 @@ module ApplicationTests
get "/yazilar"
assert_equal 200, last_response.status
end
+
+ test "reloading routes removes methods and doesn't undefine them" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get '/url', to: 'url#index'
+ end
+ RUBY
+
+ app_file "app/models/url_helpers.rb", <<-RUBY
+ module UrlHelpers
+ def foo_path
+ "/foo"
+ end
+ end
+ RUBY
+
+ app_file "app/models/context.rb", <<-RUBY
+ class Context
+ include UrlHelpers
+ include Rails.application.routes.url_helpers
+ end
+ RUBY
+
+ controller "url", <<-RUBY
+ class UrlController < ApplicationController
+ def index
+ context = Context.new
+ render plain: context.foo_path
+ end
+ end
+ RUBY
+
+ get "/url"
+ assert_equal "/foo", last_response.body
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get '/url', to: 'url#index'
+ get '/bar', to: 'foo#index', as: 'foo'
+ end
+ RUBY
+
+ Rails.application.reload_routes!
+
+ get "/url"
+ assert_equal "/bar", last_response.body
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get '/url', to: 'url#index'
+ end
+ RUBY
+
+ Rails.application.reload_routes!
+
+ get "/url"
+ assert_equal "/foo", last_response.body
+ end
end
end
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index 0939587960..ee03d8b86c 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -15,6 +15,16 @@ module ApplicationTests
teardown_app
end
+ def test_run_via_backwardscompatibility
+ require "rails/test_unit/minitest_plugin"
+
+ assert_nothing_raised do
+ Minitest.run_via[:ruby] = true
+ end
+
+ assert_predicate Minitest.run_via, :ruby?
+ end
+
def test_run_single_file
create_test_file :models, "foo"
create_test_file :models, "bar"
@@ -536,6 +546,32 @@ module ApplicationTests
assert_match "seed=1234", output, "passing TEST= should run selected test"
end
+ def test_rake_runs_multiple_test_tasks
+ create_test_file :models, "account"
+ create_test_file :controllers, "accounts_controller"
+ output = Dir.chdir(app_path) { `bin/rake test:models test:controllers TESTOPTS='-v'` }
+ assert_match "AccountTest#test_truth", output
+ assert_match "AccountsControllerTest#test_truth", output
+ end
+
+ def test_rake_db_and_test_tasks_parses_args_correctly
+ create_test_file :models, "account"
+ output = Dir.chdir(app_path) { `bin/rake db:migrate test:models TESTOPTS='-v' && echo ".tables" | rails dbconsole` }
+ assert_match "AccountTest#test_truth", output
+ assert_match "ar_internal_metadata", output
+ end
+
+ def test_warnings_option
+ app_file "test/models/warnings_test.rb", <<-RUBY
+ require 'test_helper'
+ def test_warnings
+ a = 1
+ end
+ RUBY
+ assert_match(/warning: assigned but unused variable/,
+ capture(:stderr) { run_test_command("test/models/warnings_test.rb -w") })
+ end
+
private
def run_test_command(arguments = "test/unit/test_test.rb")
Dir.chdir(app_path) { `bin/rails t #{arguments}` }
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 527529aa52..e3dfc3e82b 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -122,11 +122,11 @@ class Rails::ServerTest < ActiveSupport::TestCase
end
def test_default_options
- old_default_options = parse_arguments
+ server = Rails::Server.new
+ old_default_options = server.default_options
Dir.chdir("..") do
- default_options = parse_arguments
- assert_equal old_default_options, default_options
+ assert_equal old_default_options, server.default_options
end
end
diff --git a/railties/test/engine/commands_tasks_test.rb b/railties/test/engine/commands_tasks_test.rb
deleted file mode 100644
index 817175b9ef..0000000000
--- a/railties/test/engine/commands_tasks_test.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require "abstract_unit"
-
-class Rails::Engine::CommandsTasksTest < ActiveSupport::TestCase
- def setup
- @destination_root = Dir.mktmpdir("bukkits")
- Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --mountable` }
- end
-
- def teardown
- FileUtils.rm_rf(@destination_root)
- end
-
- def test_help_command_work_inside_engine
- output = capture(:stderr) do
- Dir.chdir(plugin_path) { `bin/rails --help` }
- end
- assert_no_match "NameError", output
- end
-
- private
- def plugin_path
- "#{@destination_root}/bukkits"
- end
-end
diff --git a/railties/test/engine/commands_test.rb b/railties/test/engine/commands_test.rb
new file mode 100644
index 0000000000..b1c937143f
--- /dev/null
+++ b/railties/test/engine/commands_test.rb
@@ -0,0 +1,96 @@
+require "abstract_unit"
+begin
+ require "pty"
+rescue LoadError
+end
+
+class Rails::Engine::CommandsTest < ActiveSupport::TestCase
+ def setup
+ @destination_root = Dir.mktmpdir("bukkits")
+ Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --mountable` }
+ end
+
+ def teardown
+ FileUtils.rm_rf(@destination_root)
+ end
+
+ def test_help_command_work_inside_engine
+ output = capture(:stderr) do
+ Dir.chdir(plugin_path) { `bin/rails --help` }
+ end
+ assert_no_match "NameError", output
+ end
+
+ def test_runner_command_work_inside_engine
+ output = capture(:stdout) do
+ Dir.chdir(plugin_path) { system("bin/rails runner 'puts Rails.env'") }
+ end
+
+ assert_equal "test", output.strip
+ end
+
+ def test_console_command_work_inside_engine
+ skip "PTY unavailable" unless available_pty?
+
+ master, slave = PTY.open
+ spawn_command("console", slave)
+ assert_output(">", master)
+ ensure
+ master.puts "quit"
+ end
+
+ def test_dbconsole_command_work_inside_engine
+ skip "PTY unavailable" unless available_pty?
+
+ master, slave = PTY.open
+ spawn_command("dbconsole", slave)
+ assert_output("sqlite>", master)
+ ensure
+ master.puts ".exit"
+ end
+
+ def test_server_command_work_inside_engine
+ skip "PTY unavailable" unless available_pty?
+
+ master, slave = PTY.open
+ pid = spawn_command("server", slave)
+ assert_output("Listening on", master)
+ ensure
+ kill(pid)
+ end
+
+ private
+ def plugin_path
+ "#{@destination_root}/bukkits"
+ end
+
+ def assert_output(expected, io, timeout = 10)
+ timeout = Time.now + timeout
+
+ output = ""
+ until output.include?(expected) || Time.now > timeout
+ if IO.select([io], [], [], 0.1)
+ output << io.read(1)
+ end
+ end
+
+ assert_includes output, expected, "#{expected.inspect} expected, but got:\n\n#{output}"
+ end
+
+ def spawn_command(command, fd)
+ Process.spawn(
+ "#{plugin_path}/bin/rails #{command}",
+ in: fd, out: fd, err: fd
+ )
+ end
+
+ def available_pty?
+ defined?(PTY) && PTY.respond_to?(:open)
+ end
+
+ def kill(pid)
+ Process.kill("TERM", pid)
+ Process.wait(pid)
+ rescue Errno::ESRCH
+ end
+end
diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb
index c54d9cc599..bdef1798f8 100644
--- a/railties/test/generators/api_app_generator_test.rb
+++ b/railties/test/generators/api_app_generator_test.rb
@@ -117,7 +117,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase
public/apple-touch-icon-precomposed.png
public/apple-touch-icon.png
public/favicon.icon
- vendor/package.json
+ package.json
)
end
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 20de2258c5..1ac2b4cde0 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -42,6 +42,7 @@ DEFAULT_APP_FILES = %w(
test/helpers
test/mailers
test/integration
+ test/system
vendor
tmp
tmp/cache
@@ -208,7 +209,6 @@ class AppGeneratorTest < Rails::Generators::TestCase
quietly { generator.send(:update_config_files) }
assert_file "#{app_root}/config/initializers/new_framework_defaults.rb" do |content|
- assert_match(/ActiveSupport\.halt_callback_chains_on_return_false = true/, content)
assert_match(/Rails\.application\.config.active_record\.belongs_to_required_by_default = false/, content)
assert_no_match(/Rails\.application\.config\.ssl_options/, content)
end
@@ -340,7 +340,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_defaults_to_puma_version
run_generator [destination_root]
- assert_gem "puma", "'~> 3.0'"
+ assert_gem "puma", "'~> 3.7'"
end
def test_generator_if_skip_puma_is_given
@@ -422,7 +422,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_if_skip_yarn_is_given
run_generator [destination_root, "--skip-yarn"]
- assert_no_file "vendor/package.json"
+ assert_no_file "package.json"
assert_no_file "bin/yarn"
end
@@ -497,17 +497,22 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_for_yarn
run_generator([destination_root])
- assert_file "vendor/package.json", /dependencies/
+ assert_file "package.json", /dependencies/
assert_file "config/initializers/assets.rb", /node_modules/
end
def test_generator_for_yarn_skipped
run_generator([destination_root, "--skip-yarn"])
- assert_no_file "vendor/package.json"
+ assert_no_file "package.json"
assert_file "config/initializers/assets.rb" do |content|
assert_no_match(/node_modules/, content)
end
+
+ assert_file ".gitignore" do |content|
+ assert_no_match(/node_modules/, content)
+ assert_no_match(/yarn-error\.log/, content)
+ end
end
def test_inclusion_of_jbuilder
@@ -592,6 +597,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_pretend_option
output = run_generator [File.join(destination_root, "myapp"), "--pretend"]
assert_no_match(/run bundle install/, output)
+ assert_no_match(/run git init/, output)
end
def test_application_name_with_spaces
@@ -731,6 +737,11 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_version_control_initializes_git_repo
+ run_generator [destination_root]
+ assert_directory ".git"
+ end
+
def test_create_keeps
run_generator
folders_with_keep = %w(
@@ -777,7 +788,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
template
end
- sequence = ["install", "exec spring binstub --all", "echo ran after_bundle"]
+ sequence = ["git init", "install", "exec spring binstub --all", "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}"
@@ -792,11 +803,29 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- assert_equal 3, @sequence_step
+ assert_equal 4, @sequence_step
end
- private
+ def test_system_tests_directory_generated
+ run_generator
+ assert_file("test/system/.keep")
+ assert_directory("test/system")
+ end
+
+ def test_system_tests_are_not_generated_on_system_test_skip
+ run_generator [destination_root, "--skip-system-test"]
+
+ assert_no_directory("test/system")
+ end
+
+ def test_system_tests_are_not_generated_on_test_skip
+ run_generator [destination_root, "--skip-test"]
+
+ assert_no_directory("test/system")
+ end
+
+ private
def stub_rails_application(root)
Rails.application.config.root = root
Rails.application.class.stub(:name, "Myapp") do
diff --git a/railties/test/generators/integration_test_generator_test.rb b/railties/test/generators/integration_test_generator_test.rb
index 8bcc02440a..9358b63bd4 100644
--- a/railties/test/generators/integration_test_generator_test.rb
+++ b/railties/test/generators/integration_test_generator_test.rb
@@ -3,10 +3,14 @@ require "rails/generators/rails/integration_test/integration_test_generator"
class IntegrationTestGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
- arguments %w(integration)
def test_integration_test_skeleton_is_created
- run_generator
+ run_generator %w(integration)
assert_file "test/integration/integration_test.rb", /class IntegrationTest < ActionDispatch::IntegrationTest/
end
+
+ def test_namespaced_integration_test_skeleton_is_created
+ run_generator %w(iguchi/integration)
+ assert_file "test/integration/iguchi/integration_test.rb", /class Iguchi::IntegrationTest < ActionDispatch::IntegrationTest/
+ end
end
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 6e1d1b70a9..f46278cefe 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -309,6 +309,17 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_add_migration_to_configured_path
+ old_paths = Rails.application.config.paths["db/migrate"]
+ Rails.application.config.paths.add "db/migrate", with: "db2/migrate"
+
+ migration = "migration_in_custom_path"
+ run_generator [migration]
+ assert_migration "db2/migrate/#{migration}.rb", /.*/
+ ensure
+ Rails.application.config.paths["db/migrate"] = old_paths
+ end
+
private
def with_singular_table_name
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 2b9f3ed7f2..99490af3a9 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -220,6 +220,17 @@ class ModelGeneratorTest < Rails::Generators::TestCase
ActiveRecord::Base.timestamped_migrations = true
end
+ def test_migration_with_configured_path
+ old_paths = Rails.application.config.paths["db/migrate"]
+ Rails.application.config.paths.add "db/migrate", with: "db2/migrate"
+
+ run_generator
+
+ assert_migration "db2/migrate/create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration\[[0-9.]+\]/
+ ensure
+ Rails.application.config.paths["db/migrate"] = old_paths
+ end
+
def test_model_with_references_attribute_generates_belongs_to_associations
run_generator ["product", "name:string", "supplier:references"]
assert_file "app/models/product.rb", /belongs_to :supplier/
diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb
index 9921a80342..eaf1199601 100644
--- a/railties/test/generators/plugin_generator_test.rb
+++ b/railties/test/generators/plugin_generator_test.rb
@@ -421,6 +421,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--full"]
assert_file "bin/rails", /ENGINE_PATH = File.expand_path\('..\/..\/lib\/bukkits\/engine', __FILE__\)/
assert_file "bin/rails", /ENGINE_ROOT = File.expand_path\('..\/..', __FILE__\)/
+ assert_file "bin/rails", %r|APP_PATH = File.expand_path\('../../test/dummy/config/application', __FILE__\)|
assert_file "bin/rails", /require 'rails\/all'/
assert_file "bin/rails", /require 'rails\/engine\/commands'/
end
@@ -490,6 +491,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase
assert_no_directory "test/dummy/doc"
assert_no_directory "test/dummy/test"
assert_no_directory "test/dummy/vendor"
+ assert_no_directory "test/dummy/.git"
end
def test_skipping_test_files
diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb
index 0bdf3b2726..0bdd2a77d2 100644
--- a/railties/test/generators/plugin_test_runner_test.rb
+++ b/railties/test/generators/plugin_test_runner_test.rb
@@ -92,6 +92,17 @@ class PluginTestRunnerTest < ActiveSupport::TestCase
assert_equal 1, result.scan(/1 runs, 1 assertions, 0 failures/).length
end
+ def test_warnings_option
+ plugin_file "test/models/warnings_test.rb", <<-RUBY
+ require 'test_helper'
+ def test_warnings
+ a = 1
+ end
+ RUBY
+ assert_match(/warning: assigned but unused variable/,
+ capture(:stderr) { run_test_command("test/models/warnings_test.rb -w") })
+ end
+
private
def plugin_path
"#{@destination_root}/bukkits"
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index bd23faf268..9971626f9f 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -230,6 +230,12 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_match(/@user\.destroy/, m)
end
end
+
+ assert_no_file "app/views/users/index.html.erb"
+ assert_no_file "app/views/users/edit.html.erb"
+ assert_no_file "app/views/users/show.html.erb"
+ assert_no_file "app/views/users/new.html.erb"
+ assert_no_file "app/views/users/_form.html.erb"
end
def test_api_controller_tests
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index 6b7e2c91d7..436fbd5d73 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -62,6 +62,11 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/patch product_line_url\(@product_line\), params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test)
end
+ # System tests
+ assert_file "test/system/product_lines_test.rb" do |test|
+ assert_match(/class ProductLinesTest < ApplicationSystemTestCase/, test)
+ end
+
# Views
assert_no_file "app/views/layouts/product_lines.html.erb"
@@ -492,6 +497,26 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_scaffold_tests_pass_by_default_inside_namespaced_mountable_engine
+ Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits-admin --mountable` }
+
+ engine_path = File.join(destination_root, "bukkits-admin")
+
+ Dir.chdir(engine_path) do
+ quietly do
+ `bin/rails g scaffold User name:string age:integer;
+ bin/rails db:migrate`
+ end
+
+ assert_file "bukkits-admin/app/controllers/bukkits/admin/users_controller.rb" do |content|
+ assert_match(/module Bukkits::Admin/, content)
+ assert_match(/class UsersController < ApplicationController/, content)
+ end
+
+ assert_match(/8 runs, 10 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`)
+ end
+ end
+
def test_scaffold_tests_pass_by_default_inside_full_engine
Dir.chdir(destination_root) { `bundle exec rails plugin new bukkits --full` }
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 08b0e34fe2..cc9d3629e9 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -109,6 +109,7 @@ module SharedGeneratorTests
def test_skip_git
run_generator [destination_root, "--skip-git", "--full"]
assert_no_file(".gitignore")
+ assert_no_directory(".git")
end
def test_skip_keeps
diff --git a/railties/test/generators/system_test_generator_test.rb b/railties/test/generators/system_test_generator_test.rb
new file mode 100644
index 0000000000..e8e561ec49
--- /dev/null
+++ b/railties/test/generators/system_test_generator_test.rb
@@ -0,0 +1,12 @@
+require "generators/generators_test_helper"
+require "rails/generators/rails/system_test/system_test_generator"
+
+class SystemTestGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+ arguments %w(user)
+
+ def test_system_test_skeleton_is_created
+ run_generator
+ assert_file "test/system/users_test.rb", /class UsersTest < ApplicationSystemTestCase/
+ end
+end
diff --git a/railties/test/rack_logger_test.rb b/railties/test/rack_logger_test.rb
index 7dd91a2465..33b4bc6a3a 100644
--- a/railties/test/rack_logger_test.rb
+++ b/railties/test/rack_logger_test.rb
@@ -20,7 +20,7 @@ module Rails
def development?; false; end
end
- class Subscriber < Struct.new(:starts, :finishes)
+ Subscriber = Struct.new(:starts, :finishes) do
def initialize(starts = [], finishes = [])
super
end
diff --git a/tools/test.rb b/tools/test.rb
index ce546b382d..71349a5974 100644
--- a/tools/test.rb
+++ b/tools/test.rb
@@ -16,5 +16,5 @@ end
ActiveSupport::TestCase.extend Rails::LineFiltering
Rails::TestUnitReporter.executable = "bin/test"
-Minitest.run_via[:rails] = true
+Minitest.run_via = :rails
require "active_support/testing/autorun"